f1d210ddc487fcda552f7f33a7b4e37c1945ef1a
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4ec9817636 |
Add weekly rankings, spontaneous AI lines and BlueMap note markers
Weekly rankings (/ranking semanal [metrica]). On a server with three regulars an all-time board is decided by whoever started first and then stops being a contest. Subtracting a baseline taken at the start of the week 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, so a server that restarts nightly does not reset the week every day. There is a test for exactly that. Players who did not move are dropped, since the point is who is playing this week; a player missing from the baseline counts their whole value, having joined during the week; a negative difference is dropped rather than shown, because statistics only go up and a negative means a stale baseline, not a result worth printing. Spontaneous AI lines (ia.comentar-eventos, ia.saudacao — both off by default). The persona comments on a run of deaths, and greets players as they join using their own numbers. Both are opt-in: a chatty AI nobody asked for is the fastest way to make players hate the feature. Budget is what makes this tolerable rather than obnoxious, and it is the piece worth reading. 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, so three limits all have to pass: a gap between any two lines, a daily cap of its own separate from the /ia cap, and a per-subject cooldown so 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, because two events in the same tick would otherwise both pass allows() and fire together — the exact double-message the gap exists to prevent. The subject map drops expired entries on each spend, or a long-lived server would leak one entry per player who ever triggered a line. A death streak now decays: 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. The five-minute sweep no longer hides behind the marcos toggle. It was a milestones-only task, so turning off marcos also silenced achievements and froze the weekly board; each of the three now checks its own module inside the body. BlueMap markers (notas.no-mapa, default on). Public notes already carry a world and coordinates and the server already runs BlueMap, so this joins the two. BlueMapBridge is the only class that touches BlueMap's 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' because BlueMap ships those classes itself; a second copy inside our jar would shadow them and break the real plugin, and preflight now fails if the scope is ever dropped. Markers are rebuilt rather than 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 — ampersand first, or the other replacements would be double-escaped. Private notes are never drawn, at any setting. 295 tests, up from 263. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pEyCmrAYHBFgpYjwFxKxh |
||
|
|
bc711a48c2 |
Add chat notes: /save and /nota, public and private
Players had nowhere to write anything down. Coordinates got pasted into
Discord, or lost. This adds notes to chat, with two scopes.
A private note is visible only to its author and everyone may write one
(canalhandia.nota, default true). A public note is broadcast and readable by
all, and writing one needs canalhandia.nota.publica (default op) — public
notes are a curated board, not a graffiti wall. Both permissions are declared
in plugin.yml: an undeclared Bukkit permission silently falls back to op-only,
which would have stopped normal players writing anything.
Every note stores where it was written. On a Minecraft server a note is nearly
always about a place — where the base is, where the spawner was found — so
coordinates are part of the model rather than optional metadata. Java players
get click-to-copy on them, the same affordance the death-coords message uses;
Bedrock renders no click event and gets the plain text. No teleport: the
plugin does not touch gameplay.
/save is the quick path. /save and /save coords pin the spot; /save <texto>
pins it with a note. Private on purpose — it is the command someone types
without reading help first, and the safe default there is the one that cannot
surprise anyone by broadcasting. /nota publica <texto> is the explicit way to
share.
Private notes are never sent to the AI, at any setting. The AI call leaves
this server for a third-party API, so a private note reaching it would be a
disclosure the author never agreed to. The filter lives inside
Notes.publicSummary — the only method the AI path calls — rather than at the
call site, so a future caller cannot get it wrong by accident. Public notes
are sent (ia.contexto-notas, default 10), which is what lets the AI answer
"onde fica a base?" from what players actually wrote down.
A note the viewer cannot see is reported as missing rather than as forbidden:
saying "that one is private" would confirm it exists, which is itself a leak.
Search runs through the same visibility filter, so it cannot become a way to
probe for someone else's text. Ids are never reused after a deletion, or
"/nota ver 2" would point at a different note than the one someone wrote down
a minute ago. Text is stripped of the section sign and control characters,
because a note is echoed into chat and could otherwise forge a line that looks
like it came from the server.
Scope parsing accepts the masculine and plural forms ("publico", "publicas")
alongside the canonical ones. Spelled out rather than derived: a blanket a-to-o
rewrite turns "privada" into "privodo", and the plural is exactly what tab
completion suggests, so it has to parse or the suggested command fails.
preflight.sh now checks all 18 commands and both new permissions.
218 tests, up from 176.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pEyCmrAYHBFgpYjwFxKxh
|
||
|
|
0726ce3794 |
Give the AI a personality, chat awareness and live server state
The AI could only see three static facts about the server, so it answered "quem tá online?" and "tá chovendo?" by insisting it had no access — true of the model, but not of the plugin, which has all of it on hand. It also had no tone: correct answers delivered like a manual, on a server whose whole point is people ribbing each other. Persona: five tones (zoeiro, amigao, seco, aldeao, neutro), switchable live with /ia personalidade <nome>. Personality is expressed only as extra system instructions and changes how the model talks, never what it may do. Every persona — including the blank one — carries Persona.GUARD, which restates the no-commands/no-server-access limits inside the persona's own frame, so a roleplay instruction cannot read as licence to claim powers the plugin does not grant it. The guard also forbids inventing stats, which matters now that real numbers are being fed in. The teasing personas each state where the line is; a test asserts every one of them does. ChatLog: a 50-line in-memory ring of public chat, the last few lines handed to the model so a follow-up like "quem tá reclamando aí?" has a referent. Written from the chat event (off the main thread) and read from /ia, so it is synchronised; a concurrency test hammers it from eight threads, because an unsynchronised deque here would throw ConcurrentModification into a player's answer. Recorded at MONITOR priority so what is stored is what the room saw — a zoacao swap included — and cancelled messages are never stored. Nothing touches disk. ServerState: who is online with their platform, dimension, time of day, weather, and the asker's coordinates, health, hunger and XP. Captured on the main thread before the async call — every field reads the Bukkit world API, which is not safe off it — and only the formatted string crosses the thread boundary. Formatting is pure and tested, including the negative-tick case a raw modulo would drop through every band. Styling: Java players get a hover card with the original question and the active persona, plus a click that pre-fills "/ia " for a follow-up. suggestCommand, not runCommand: nothing executes without the player pressing enter. Bedrock renders neither hover nor click, so it keeps the plain line, built through broadcastPerPlatform like every other interactive message here. preflight.sh: a read-only pre-restart harness. It verifies the jar opens, that plugin.yml declares all 16 commands, that the staged jar's hash matches the local build and is owned 1000:0, that the live config parses as YAML and carries the keys this deploy depends on, and that a rollback jar and config backup both exist. It never restarts anything. A missing YAML parser reports as "not checked" rather than "invalid" — a harness that cries wolf gets ignored. 176 tests, up from 101. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pEyCmrAYHBFgpYjwFxKxh |