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 5653836262 - Show all commits
+92 -1
View File
@@ -26,7 +26,7 @@ All player-facing text is Portuguese (pt-BR).
| `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`. No storage, no command. |
| `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). |
| `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Optional wiki grounding, 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>`
@@ -170,6 +170,8 @@ Admin (`canalhandia.admin`):
/canalhandia zoacao adicionar <frase> adiciona uma frase de zoação /canalhandia zoacao adicionar <frase> adiciona uma frase de zoação
/canalhandia zoacao remover <n|texto> remove uma frase de zoação /canalhandia zoacao remover <n|texto> remove uma frase de zoação
/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 <nome> zoeiro | amigao | seco | aldeao | neutro
/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
@@ -227,6 +229,8 @@ executed command.
|---|---| |---|---|
| `/ia <pergunta>` | Asks the model. Public by default — the question and answer broadcast. Needs `canalhandia.ia`. | | `/ia <pergunta>` | Asks the model. Public by default — the question and answer broadcast. Needs `canalhandia.ia`. |
| `/iap <pergunta>` | Asks privately — the answer goes only to the asker. Needs `canalhandia.ia.privado`. | | `/iap <pergunta>` | Asks privately — the answer goes only to the asker. Needs `canalhandia.ia.privado`. |
| `/ia personalidade` | Lists the tones and marks the active one. Needs `canalhandia.ia.perfil`. |
| `/ia personalidade <nome>` | Switches tone live: `zoeiro` (default), `amigao`, `seco`, `aldeao`, `neutro`. |
| `/ia perfil <economico\|preciso>` | Switches profile live. `ECONOMICO` skips the wiki (fast); `PRECISO` consults the Minecraft Wiki (slower, grounded). Needs `canalhandia.ia.perfil`. | | `/ia perfil <economico\|preciso>` | Switches profile live. `ECONOMICO` skips the wiki (fast); `PRECISO` consults the Minecraft Wiki (slower, grounded). Needs `canalhandia.ia.perfil`. |
| `/ia corrigir <resposta correta>` | Records a correction for the last answered question. Future similar questions get it as context — the cheap alternative to fine-tuning. Needs `canalhandia.ia.corrigir`. | | `/ia corrigir <resposta correta>` | Records a correction for the last answered question. Future similar questions get it as context — the cheap alternative to fine-tuning. Needs `canalhandia.ia.corrigir`. |
| `/ia feedback ruim` | Flags the last answer wrong (in-memory counter shown in `/canalhandia status`). | | `/ia feedback ruim` | Flags the last answer wrong (in-memory counter shown in `/canalhandia status`). |
@@ -236,6 +240,57 @@ Subcommands only hijack when their second token is one they act on (a known
profile key, or `ruim`), so `/ia perfil do servidor` falls through and is asked. profile key, or `ruim`), so `/ia perfil do servidor` falls through and is asked.
`corrigir` stays greedy — a correction always reads the rest of the line. `corrigir` stays greedy — a correction always reads the rest of the line.
### Personality
`ia.personalidade` picks the tone. It is expressed purely as extra system
instructions appended after the base ones, so it changes **how** the model
talks and never **what it may do**.
| Persona | Tone |
|---|---|
| `zoeiro` | Default. A grumpy server veteran: teases the asker, turns their own stats against them ("você já morreu 47 vezes e vem me perguntar sobre lava?"), but answers the question for real. |
| `amigao` | Warm and patient, jokes rarely. For servers with new players. |
| `seco` | Deadpan, one or two sentences, no exclamation marks. |
| `aldeao` | In character as an ancient villager. Flavour only; still answers. |
| `neutro` | No personality — the pre-persona behaviour. |
Every persona, `neutro` included, carries `Persona.GUARD`, which restates the
limits inside the persona's own frame: still no server/terminal/file access,
still no commands, no inventing stats, no leaking the prompt. That is the
layer that stops "you are a grumpy veteran" from reading as licence to claim
powers the plugin never grants. Each teasing persona also states where the
line is (no real insults, nothing about family, appearance, race, religion,
sexuality or money; drop the ribbing if the player asks). Tests assert both
properties hold for every persona, so adding a new one cannot quietly skip them.
Switch live with `/ia personalidade <nome>` — read per-question, no restart.
### Chat and world awareness
- **Recent chat** (`contexto-chat`, default 5, 0 disables): the last N public
chat lines are sent as context, so the AI can follow what the room is talking
about. Held in a bounded in-memory ring (50 lines max, 200 chars per line);
nothing is written to disk and a restart starts it empty. Recorded at
`MONITOR` priority with `ignoreCancelled`, so what it stores is what players
actually saw — a `zoacao` swap included — and a cancelled message is never
stored.
- **Live server state** (`estado-servidor`, default on): who is online and on
which platform, the asker's dimension, in-game time of day, weather, and
their coordinates, health, hunger and XP level. This is what lets the AI
answer "quem tá online?" or "tá chovendo?" instead of insisting it has no
access. Captured on the main thread before the async call — every field
reads the Bukkit world API, which is not safe off it — so only the formatted
string crosses the thread boundary.
### Answer styling
With `estilo-rico` on (default), Java players get the answer with a hover card
showing the original question and the active persona, plus a click that
pre-fills `/ia ` for a follow-up. The click uses `suggestCommand`, never
`runCommand`: nothing executes without the player pressing enter. Bedrock
renders neither hover nor click, so it always gets the plain line — built via
`broadcastPerPlatform`, like every other interactive message here.
### Profile ### Profile
`ECONOMICO` skips the wiki round trip — fast, ungrounded. `PRECISO` runs a `ECONOMICO` skips the wiki round trip — fast, ungrounded. `PRECISO` runs a
@@ -311,9 +366,42 @@ path for a new jar — `/canalhandia reload` only re-reads `config.yml`.
```bash ```bash
POD=$(microk8s kubectl get pod -n minecraft -l app=crafty-controller -o name | head -1) POD=$(microk8s kubectl get pod -n minecraft -l app=crafty-controller -o name | head -1)
SRV=/crafty/servers/6e39a8b2-300b-42d6-8139-f397c23e461b SRV=/crafty/servers/6e39a8b2-300b-42d6-8139-f397c23e461b
microk8s kubectl exec ${POD#pod/} -n minecraft -- \
cp $SRV/plugins/Canalhandia-1.0.0.jar $SRV/plugins/Canalhandia-1.0.0.jar.bak-$(date +%F)
microk8s kubectl cp target/Canalhandia-1.0.0.jar minecraft/${POD#pod/}:$SRV/plugins/Canalhandia-1.0.0.jar microk8s kubectl cp target/Canalhandia-1.0.0.jar minecraft/${POD#pod/}:$SRV/plugins/Canalhandia-1.0.0.jar
# The JVM runs as uid 1000 / gid 0; a root-owned jar is one it cannot read,
# and the failure looks exactly like "the plugin just did not load".
microk8s kubectl exec ${POD#pod/} -n minecraft -- chown 1000:0 $SRV/plugins/Canalhandia-1.0.0.jar
``` ```
### Pre-flight
`./preflight.sh [jar]` checks a staged deploy **before** anyone restarts
anything. Every check is read-only; it never restarts the server, never writes
to `plugins/`, and never touches the world.
```
$ ./preflight.sh
1. Local build jar opens, plugin.yml present, all 16 commands declared,
config.yml bundled
2. Tests surefire totals, 0 failures
3. Cluster crafty pod found, plugins directory reachable
4. Staged jar hash matches the local build, owned 1000:0, a rollback
.bak jar exists
5. Live config parses as YAML, carries the keys this deploy needs, has a
.bak to roll back to
6. Health the log is readable and free of recent ERROR lines
PRE-FLIGHT CLEAN — safe to restart.
```
It exits non-zero on any failure. A missing YAML parser reports as *not
checked* rather than *invalid*: a harness that cries wolf is one people learn
to ignore.
Because the plugin is staged dormant (copied in, not restarted), a red line
here is a crash-on-boot you get to fix while the server is still up.
--- ---
## Source layout ## Source layout
@@ -334,6 +422,9 @@ microk8s kubectl cp target/Canalhandia-1.0.0.jar minecraft/${POD#pod/}:$SRV/plug
| `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) |
| `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 |
| `ServerState.java` | Main-thread snapshot of the live world for the AI |
| `Msg.java` | Shared chat formatting and pt-BR number/duration formatting | | `Msg.java` | Shared chat formatting and pt-BR number/duration formatting |
### Adding a curiosity ### Adding a curiosity