# Canalhandia Chat-only social features for the Canalhandia Minecraft server (Paper 26.2). **Nothing here touches gameplay.** No items, no world edits, no attributes, no economy. Everything is chat messages, boss bars and clickable buttons, and every module can be switched off independently. All player-facing text is Portuguese (pt-BR). --- ## Modules | Module | What it does | |---|---| | `curiosidades` | *"Sabia que o Fulano já minerou 5.966 blocos de Pedra?"* — a fact about a player, with reaction buttons. Fires on join by default. | | `adivinha` | The same fact with the name hidden, plus clickable player names. Reveals after 45s and names who guessed right. | | `luto` | A clickable `[F]` under each death message, with a count when the window closes. | | `enquete` | `/enquete Pergunta \| A \| B` — clickable voting with a live tally on the boss bar. | | `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. | | `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. | Toggle any of them: `/canalhandia modulo ` --- ## Where the data comes from Everything is derived from **vanilla statistics**. No database, no tracking code, no extra writes — the server was already recording all of it. - **Online players** use the Bukkit API (`Player#getStatistic`). - **Rankings** read `/players/stats/.json` directly, because Bukkit only exposes statistics for players who are online. `usercache.json` maps those UUIDs back to names, including Floodgate/Bedrock players whose UUIDs start with `00000000-0000-0000-0009`. Note the path: Paper writes to `/players/stats`, **not** `/stats`. `OfflineStats` checks both. --- ## Two constraints worth knowing These shaped the design, and anyone changing the code should know them before "fixing" what looks odd. ### 1. Chat messages cannot be edited after sending There is no vanilla way to update a message that is already in the chat log. So the counts baked into the reaction buttons are **frozen at send time** and never change. The live numbers appear on three other surfaces instead: - a **boss bar** while the window is open (`janela-reacao-segundos`, default 90) - an **action bar** shown to whoever just reacted - a **final tally line** broadcast when the window closes An earlier version only had the boss bar, and it read as broken — the buttons showed no number at all. ### 2. Clicks arrive late People scroll back and click minutes after a message. Reactions therefore keep counting for `reacao-validade-minutos` (default 15) even after the boss bar is gone, and the last 8 reaction sets stay in memory for that reason. Silently dropping a late click looks like a bug to the player. ### 3. Names are translated by the client, not by us Block, item and mob names are emitted as **translatable components** (`Component.translatable(material.translationKey())`), so a pt-BR client renders "Pedra" and an en-US client renders "Stone" from the same broadcast. There is no translation table to maintain. The consequence: the client only supplies the **singular** form. Every sentence is therefore phrased so the number never has to agree with the noun — *"5.966 blocos de Pedra"*, never *"5.966 Pedras"*. Keep that rule when adding sentences to `CuriosityFactory`. Note that the **server console** renders translatable components in English, so `[Curiosidade] ... 16 unidades de Copper Pickaxe` in `latest.log` does not mean players saw English. --- ## Commands Player-facing: ``` /curiosidade anuncia uma curiosidade agora /curiosidade anuncia sobre alguém específico /curiosidade ver [jogador] mostra só para você /curiosidade listar [jogador] lista todas as curiosidades disponíveis /curiosidade toggle entra/sai do sorteio /adivinha inicia uma rodada de "adivinhe de quem é" /enquete Pergunta | A | B abre uma enquete /enquete encerrar encerra a enquete aberta /ranking [categoria] placares do servidor /canalhandia status mostra toda a configuração /canalhandia modulos lista os módulos e seu estado ``` Admin (`canalhandia.admin`): ``` /canalhandia modulo liga/desliga um módulo /canalhandia marcos força uma verificação de marcos /canalhandia limpar [cooldown|historico|tudo] /canalhandia reload /curiosidade modo /curiosidade intervalo intervalo do modo temporizado /curiosidade atraso espera após o jogador entrar /curiosidade cooldown mínimo entre citar o mesmo jogador /curiosidade repetir quantas recentes evitar repetir /curiosidade janela duração da barra de reações /curiosidade validade por quanto tempo cliques ainda contam /curiosidade reacoes /curiosidade reacao add /curiosidade reacao remover /curiosidade categoria ``` Every setter **writes through to `config.yml` immediately**, so in-game changes survive a restart. ### Permissions | Permission | Default | Grants | |---|---|---| | `canalhandia.reagir` | everyone | react, press F | | `canalhandia.ver` | everyone | `ver`, `listar`, `/ranking` | | `canalhandia.enquete` | everyone | open polls | | `canalhandia.forcar` | op | trigger curiosities and guess rounds | | `canalhandia.admin` | op | change modules and all settings | | `canalhandia.isento` | nobody | never be the subject | Permissions are **declared explicitly** in `plugin.yml`. An undeclared Bukkit permission falls back to op-only, which would silently stop normal players from reacting. --- ## Building Requires **JDK 25**. Paper 26.2's API ships Java 25 class files, and JDK 21 fails with a misleading `cannot access org.bukkit.Bukkit` — that phrasing means the class-file version is too new, not that the dependency is missing. ```bash docker run --rm -v "$PWD":/work -v "$HOME/.m2":/root/.m2 -w /work \ maven:3.9-eclipse-temurin-25 mvn -B package ``` Output: `target/Canalhandia-1.0.0.jar` The dependency uses Paper's newer coordinate scheme: `io.papermc.paper:paper-api:26.2.build.92-stable`. ### Deploying Copy the jar into the server's `plugins/` and restart. There is no hot-reload path for a new jar — `/canalhandia reload` only re-reads `config.yml`. ```bash POD=$(microk8s kubectl get pod -n minecraft -l app=crafty-controller -o name | head -1) SRV=/crafty/servers/6e39a8b2-300b-42d6-8139-f397c23e461b microk8s kubectl cp target/Canalhandia-1.0.0.jar minecraft/${POD#pod/}:$SRV/plugins/Canalhandia-1.0.0.jar ``` --- ## Source layout | File | Role | |---|---| | `Canalhandia.java` | Plugin entry point, scheduling, broadcasting, listeners | | `CanalhandiaCommand.java` | Every command and all click callbacks | | `Settings.java` | Typed config access; all setters persist immediately | | `Module.java` / `Category.java` / `Mode.java` | Toggleable feature, fact group, trigger mode | | `CuriosityFactory.java` | Builds the Portuguese sentences from statistics | | `Stats.java` | Defensive Bukkit statistics access | | `Fact.java` | One sentence plus its category | | `Reactions.java` | Reaction state, buttons, boss bar, tally | | `GuessRound.java` | "Adivinhe de quem é" round state | | `Poll.java` | Poll state, voting, results | | `Milestones.java` | Threshold tracking, persisted to `marcos.yml` | | `OfflineStats.java` | Reads stats JSON for offline players | | `RankingMetric.java` | Leaderboard columns and their formatting | | `Msg.java` | Shared chat formatting and pt-BR number/duration formatting | ### Adding a curiosity Add one line to `CuriosityFactory.facts(...)` using the existing helpers (`material`, `entities`, `distance`, `time`, `count`), pick a `Category`, and phrase it so the count never has to agree with a translated noun. Statistic constants get renamed between Minecraft releases, so resolve them via `Stats.resolve("NEW_NAME", "OLD_NAME")` — a rename then degrades one curiosity instead of breaking the whole announcement.