diff --git a/README.md b/README.md index f981640..5167541 100644 --- a/README.md +++ b/README.md @@ -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. | | `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). | -| `ia` | `/ia ` asks an OpenAI-compatible model in chat. Optional wiki grounding, per-player memory, operator corrections. | +| `ia` | `/ia ` 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 ` @@ -170,6 +170,8 @@ Admin (`canalhandia.admin`): /canalhandia zoacao adicionar adiciona uma frase de zoação /canalhandia zoacao remover remove uma frase de zoação /canalhandia zoacao limpar volta para as frases padrão +/ia personalidade lista as personalidades da IA +/ia personalidade zoeiro | amigao | seco | aldeao | neutro /canalhandia reload /curiosidade modo /curiosidade intervalo intervalo do modo temporizado @@ -227,6 +229,8 @@ executed command. |---|---| | `/ia ` | Asks the model. Public by default — the question and answer broadcast. Needs `canalhandia.ia`. | | `/iap ` | 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 ` | Switches tone live: `zoeiro` (default), `amigao`, `seco`, `aldeao`, `neutro`. | | `/ia perfil ` | Switches profile live. `ECONOMICO` skips the wiki (fast); `PRECISO` consults the Minecraft Wiki (slower, grounded). Needs `canalhandia.ia.perfil`. | | `/ia corrigir ` | 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`). | @@ -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. `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 ` — 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 `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 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 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 +# 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 @@ -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) | | `RankingMetric.java` | Leaderboard columns and their formatting | | `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 | ### Adding a curiosity