diff --git a/README.md b/README.md index bc56880..6b60d8f 100644 --- a/README.md +++ b/README.md @@ -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. | | `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. | -| `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). | | `notas` | Notes in chat. `/save` pins where you are; `/nota add ` writes a private one (only you see it, everyone may write); `/nota publica ` 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 ` — 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 ` 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 ` @@ -166,6 +168,11 @@ Player-facing: /nota buscar procura no texto das anotações /nota ver mostra uma anotação inteira /nota remover apaga uma anotação sua +/recado 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`): @@ -182,6 +189,8 @@ Admin (`canalhandia.admin`): /canalhandia zoacao limpar volta para as frases padrão /ia personalidade lista as personalidades da IA /ia personalidade zoeiro | amigao | seco | aldeao | neutro +/ia eventos IA comenta mortes seguidas sozinha +/ia saudacao IA dá as boas-vindas de quem entra /canalhandia reload /curiosidade modo /curiosidade intervalo intervalo do modo temporizado @@ -211,6 +220,7 @@ survive a restart. | `canalhandia.isento` | nobody | never be the subject | | `canalhandia.nota` | everyone | `/save` and private notes | | `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.privado` | op | `/iap` (private question, answer only to the asker) | | `canalhandia.ia.corrigir` | op | `/ia corrigir ` — 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 ` and `/ia saudacao ` 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`) 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) | | `RankingMetric.java` | Leaderboard columns and their formatting | | `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 | | `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 |