i18n: per-player EN/PT via Adventure GlobalTranslator #1

Merged
masi merged 20 commits from feat/ia-grounding into main 2026-08-12 15:53:28 +00:00
Showing only changes of commit 67a2cbb37d - Show all commits
+95 -1
View File
@@ -24,9 +24,11 @@ All player-facing text is Portuguese (pt-BR).
| `enquete` | `/enquete Pergunta \| A \| B` — clickable voting with a live tally on the boss bar. | | `enquete` | `/enquete Pergunta \| A \| B` — clickable voting with a live tally on the boss bar. |
| `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. | | `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. | | `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. |
| `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player on respawn (the death screen swallows chat sent during the event), so they can run back to their dropped items. Respects `keepInventory`. No storage, no command. | | `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player on respawn (the death screen swallows chat sent during the event), so they can run back to their dropped items. Respects `keepInventory`. `/mortes` lists your last 10 deaths with the cause and where each happened (`mortes.yml`) — your own only, since where someone died is where their stuff is. |
| `zoacao` | A chat message matching the trigger (a pattern + a match mode) is swapped for a random line from `zoacao.mensagens` — a chat gag. The player's name still prefixes it. Default: a bare `f`/`F` (trimmed) → a random gag line. Match modes: `igual` (equals), `contem` (contains), `comeca` (starts with), `termina` (ends with), `regex`. The mode, the pattern, and the message list are all editable in-game with `/canalhandia zoacao ...`. Pure chat swap; the `luto` tribute is unaffected (paying respects still needs the `[F]` button or `/f`). Affects Bedrock chat too (it's a chat event, not a click). | | `zoacao` | A chat message matching the trigger (a pattern + a match mode) is swapped for a random line from `zoacao.mensagens` — a chat gag. The player's name still prefixes it. Default: a bare `f`/`F` (trimmed) → a random gag line. Match modes: `igual` (equals), `contem` (contains), `comeca` (starts with), `termina` (ends with), `regex`. The mode, the pattern, and the message list are all editable in-game with `/canalhandia zoacao ...`. Pure chat swap; the `luto` tribute is unaffected (paying respects still needs the `[F]` button or `/f`). Affects Bedrock chat too (it's a chat event, not a click). |
| `notas` | Notes in chat. `/save` pins where you are; `/nota add <texto>` writes a private one (only you see it, everyone may write); `/nota publica <texto>` writes one everyone reads, and needs `canalhandia.nota.publica` (default op). Every note stores its coordinates — click-to-copy on Java. Persisted to `notas.yml`. **No teleport**: nothing here touches gameplay. | | `notas` | Notes in chat. `/save` pins where you are; `/nota add <texto>` writes a private one (only you see it, everyone may write); `/nota publica <texto>` writes one everyone reads, and needs `canalhandia.nota.publica` (default op). Every note stores its coordinates — click-to-copy on Java. Persisted to `notas.yml`. **No teleport**: nothing here touches gameplay. |
| `recados` | `/recado <jogador> <texto>` — a line for someone who is offline, delivered on their next join. `/recados` shows how many of yours are still unread. Persisted to `recados.yml`. |
| `conquistas` | Named achievements beyond the numeric milestones: "Casca Grossa" (50h, under 10 deaths), "Turista" (100h, barely mined), "Imortal às Avessas". `/conquistas` lists them all and marks yours. Persisted to `conquistas.yml`. |
| `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Has a personality (`zoeiro` by default — it will tease you), sees the last few chat lines and the live server state, and grounds answers in the asker's real stats. Optional wiki lookup, per-player memory, operator corrections. | | `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Has a personality (`zoeiro` by default — it will tease you), sees the last few chat lines and the live server state, and grounds answers in the asker's real stats. Optional wiki lookup, per-player memory, operator corrections. |
Toggle any of them: `/canalhandia modulo <nome> <on|off>` Toggle any of them: `/canalhandia modulo <nome> <on|off>`
@@ -166,6 +168,11 @@ Player-facing:
/nota buscar <texto> procura no texto das anotações /nota buscar <texto> procura no texto das anotações
/nota ver <n> mostra uma anotação inteira /nota ver <n> mostra uma anotação inteira
/nota remover <n> apaga uma anotação sua /nota remover <n> apaga uma anotação sua
/recado <jogador> <texto> recado entregue quando a pessoa entrar
/recados quantos recados seus ainda não foram lidos
/mortes suas últimas mortes, com causa e lugar
/conquistas conquistas, com as suas marcadas
/ranking semanal [métrica] só o que foi ganho nesta semana
``` ```
Admin (`canalhandia.admin`): Admin (`canalhandia.admin`):
@@ -182,6 +189,8 @@ Admin (`canalhandia.admin`):
/canalhandia zoacao limpar volta para as frases padrão /canalhandia zoacao limpar volta para as frases padrão
/ia personalidade lista as personalidades da IA /ia personalidade lista as personalidades da IA
/ia personalidade <nome> zoeiro | amigao | seco | aldeao | neutro /ia personalidade <nome> zoeiro | amigao | seco | aldeao | neutro
/ia eventos <on|off> IA comenta mortes seguidas sozinha
/ia saudacao <on|off> IA dá as boas-vindas de quem entra
/canalhandia reload /canalhandia reload
/curiosidade modo <entrada|intervalo|ambos|manual> /curiosidade modo <entrada|intervalo|ambos|manual>
/curiosidade intervalo <min> intervalo do modo temporizado /curiosidade intervalo <min> intervalo do modo temporizado
@@ -211,6 +220,7 @@ survive a restart.
| `canalhandia.isento` | nobody | never be the subject | | `canalhandia.isento` | nobody | never be the subject |
| `canalhandia.nota` | everyone | `/save` and private notes | | `canalhandia.nota` | everyone | `/save` and private notes |
| `canalhandia.nota.publica` | op | write notes everyone can read | | `canalhandia.nota.publica` | op | write notes everyone can read |
| `canalhandia.recado` | everyone | leave messages for other players |
| `canalhandia.ia` | op | `/ia` (public question, broadcast to chat) | | `canalhandia.ia` | op | `/ia` (public question, broadcast to chat) |
| `canalhandia.ia.privado` | op | `/iap` (private question, answer only to the asker) | | `canalhandia.ia.privado` | op | `/iap` (private question, answer only to the asker) |
| `canalhandia.ia.corrigir` | op | `/ia corrigir <resposta>` — register a correction for the last answer | | `canalhandia.ia.corrigir` | op | `/ia corrigir <resposta>` — register a correction for the last answer |
@@ -289,6 +299,84 @@ text.
--- ---
## Weekly rankings
`/ranking semanal [métrica]` shows only what was **gained since the start of the
week**. On a server with three regulars an all-time board is decided by whoever
started first and then stops being a contest; subtracting a weekly baseline
makes it one again.
Rotation is time-based and idempotent: the snapshot carries the timestamp it was
taken at and is replaced only once a week has actually elapsed. **A restart
never rotates it** — a server that restarts nightly would otherwise reset the
week every day, which is the failure this design exists to avoid.
Players who did not move are dropped (the point is who is playing *this* week).
Someone missing from the baseline counts their whole value, having joined during
the week. A negative difference is dropped rather than shown: statistics only go
up, so a negative means a stale baseline or a reset stats file, not a result.
---
## Spontaneous AI lines
Off by default (`ia.comentar-eventos`, `ia.saudacao`). The persona can comment
on a run of deaths and greet players as they join, using their own numbers. Both
are opt-in because a chatty AI nobody asked for is the fastest way to make
players hate the feature.
`/ia eventos <on|off>` and `/ia saudacao <on|off>` toggle them live.
### Why the budget is strict
A player question is self-limiting — someone chose to spend it. A line the AI
decides to make on its own is not, and it costs money every time. `Budget`
enforces three limits, **all** of which must pass:
| Limit | Default | Why |
|---|---|---|
| gap between any two lines | 10 min | stops chat spam |
| daily cap, separate from `/ia`'s | 20 | protects the spend |
| per-subject cooldown | 30 min | one unlucky player is not narrated all evening |
`allows()` does not spend, so a caller that decides not to fire (nobody online,
the model returned nothing) has burned nothing. `saySomething` spends **up
front** rather than on success: two events landing in the same tick would
otherwise both pass `allows()` and fire together — the exact double-message the
gap exists to prevent.
A death streak decays after 15 minutes. Three deaths across an evening is not a
streak; three in ten minutes is. Spontaneous lines are silent on failure —
nobody asked for it, so nobody should see it fail.
---
## Notes on the BlueMap web map
Public notes are drawn as markers on BlueMap (`notas.no-mapa`, default on).
Notes already carry a world and coordinates and the server already runs BlueMap,
so this joins the two. **Private notes are never drawn, at any setting.**
BlueMap is an **optional** dependency. `BlueMapBridge` is the only class that
touches its API, and every entry point catches `NoClassDefFoundError` as well as
`Exception` — the failure mode of a missing optional dependency is a linkage
error, not an exception — so a server without BlueMap logs one fine-level line
and carries on.
The dependency is `provided` scope because BlueMap ships those classes itself; a
second copy inside this jar would shadow them and break the real plugin.
`preflight.sh` fails if that scope is ever dropped.
Markers are **rebuilt**, not incrementally patched: BlueMap discards everything
when it unloads and expects addons to re-create markers on its enable callback,
and a full rebuild of a tiny list cannot drift out of sync the way a missed
delete would. Notes are matched to the map that renders their world, or a Nether
note would be drawn at the same numeric coordinates in the overworld, pointing
at nothing. Note text is player-written and lands in a web page, so it is
HTML-escaped.
---
## IA (`/ia`) ## IA (`/ia`)
Chat Q&A backed by an OpenAI-compatible endpoint (default MiniMax). Gated to Chat Q&A backed by an OpenAI-compatible endpoint (default MiniMax). Gated to
@@ -501,6 +589,12 @@ here is a crash-on-boot you get to fix while the server is still up.
| `OfflineStats.java` | Reads stats JSON for offline players (rankings + the asker's stat summary for the IA) | | `OfflineStats.java` | Reads stats JSON for offline players (rankings + the asker's stat summary for the IA) |
| `RankingMetric.java` | Leaderboard columns and their formatting | | `RankingMetric.java` | Leaderboard columns and their formatting |
| `DeathFlavor.java` | Comic pt-BR verb phrases for each death cause (used by the `mortes` module) | | `DeathFlavor.java` | Comic pt-BR verb phrases for each death cause (used by the `mortes` module) |
| `Mail.java` | Offline messages and their YAML storage |
| `DeathLog.java` | Recent deaths per player, for `/mortes` |
| `Achievement.java` / `Achievements.java` | The named-achievement catalogue (pure) and its award bookkeeping |
| `WeeklyStats.java` | Weekly ranking baseline and the delta arithmetic (pure) |
| `Budget.java` | The three-limit gate on spontaneous AI lines (pure) |
| `BlueMapBridge.java` | Public notes as markers on the BlueMap web map (optional dependency) |
| `Note.java` / `Notes.java` | One note (scope, text, place, visibility rules) and its YAML storage | | `Note.java` / `Notes.java` | One note (scope, text, place, visibility rules) and its YAML storage |
| `Persona.java` | The AI's five tones, each carrying the safety guard | | `Persona.java` | The AI's five tones, each carrying the safety guard |
| `ChatLog.java` | Bounded, thread-safe ring of recent public chat for the AI | | `ChatLog.java` | Bounded, thread-safe ring of recent public chat for the AI |