Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 8 additions & 26 deletions projects/Thievery/docs/SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ See [TEST_MATRIX.md](TEST_MATRIX.md) for the manual checklist.
| Target | Lock model | Robbery | Fail |
|--------|------------|---------|------|
| Doors | Key + strength (`DoorData`) | Title **bar** | 60s on that door |
| Chests / barrels / etc. | Owner + `LockState` | **Pin grid** dialog or floating **lockpick ring**, then hidden **GUI** probe with seized pins | Either minigame: 60s on that chest, both halves of a double chest (`fail-cooldown-ms`). Probe: access-map cooldown |
| Chests / barrels / etc. | Owner + `LockState` | Floating **lockpick ring**, then hidden **GUI** probe with seized pins | Ring: 60s on that chest, both halves of a double chest (`fail-cooldown-ms`). Probe: access-map cooldown |
| IF furniture, armor stands, item frames | Owner + `LockState` (chest model) | Same **bar** as doors | Same 60s as doors (`fail-cooldown-ms`) |

## Chest hopper automation
Expand All @@ -18,28 +18,11 @@ When a **block hopper** moves items (`InventoryMoveItemEvent`), allow pull/depos

Displays do not use the chest GUI. Doors and displays share one engine.

## Chest lock minigames
## Chest lock minigame

Right-clicking a chest with a lockpick runs every existing chest check (trait, access, owner online, clues, already picking, held lockpick, access-map cooldown), then `LockMinigameManager` opens a lock minigame instead of the probe menu: the floating lockpick ring for `minigame.dial-chance` (50%) of attempts, otherwise the pin grid dialog. Both are `LockMinigame` subclasses (`PinGridGame`, `RingDialGame`) and share the end-of-game pause, the failure rules and the cooldown below. The ring shows its state on a boss bar; the pin grid does not, because the dialog's blur hides the bar. Staff can force either with `/thievery testpick [grid|dial]` while looking at a container and holding a lockpick; it skips the trait, clue, ownership and access checks.
Right-clicking a chest with a lockpick runs every existing chest check (trait, access, owner online, clues, already picking, held lockpick, access-map cooldown), then `LockMinigameManager` opens the floating lockpick ring (`RingDialGame`) instead of the probe menu. It is a `LockMinigame`, like the pickpocket ring, and shows its state on a boss bar. Staff can run it with `/thievery testpick` while looking at a container and holding a lockpick, even with the minigame off; it skips the trait, clue, ownership and access checks.

Both halves of a double chest are one lock (`ContainerManager.lockBlock`, the left half): they share the fail cooldown, and only one thief at a time can work either half, in a minigame or the probe menu. The access cooldown is checked again when the minigame is solved, because a guildmate's pick nearby can start it while the lock is being worked. While a thief works a lock, Thievery's door picking and grave looting ignore their clicks; those listeners handle cancelled clicks, so the ring's cancel alone does not stop them.

### Pin grid (dialog)

Modelled on the NoPixel thermite minigame and shown as a vanilla dialog (`GridDialogs`), so it works on unmodded clients. Each cell is a square 20x20 button showing a block texture: an unlit redstone lamp, a lit lamp while memorising, a sea lantern once set, a redstone block for a wrong cell and a gold block for a pin missed on a fail. `minigame.grid.sprites: false` draws coloured squares instead. The puzzle state lives in `PinGrid`; `GridScreen` is one frame of the dialog.

With `minigame.grid.pack: true` the dialog is drawn as a lit board from the `thievery:lockpick` font, which ships in the server pack (ServerAssets `configs/ItemsAdder/contents/tfmc_thievery`, made by `tools/make_thievery_lockpick_font.py`). Each cell's label is a tile glyph between negative spaces, so it measures nothing and the client centres it without scrolling or clipping. The tile reaches 1 px over the gap to each neighbour, so the tiles join into one dark board, with a rim on the outer edges. It leaves the button's own one-pixel outline showing, black, or white under the cursor. Pins are cyan while memorising and green once set, a wrong cell is a red cross and a missed pin is outlined in amber. The status line becomes pips for pins set and slips. The dialog clips anything below its last row of buttons, so "Give up" joins the grid as its last row: a strip that closes the board's bottom around a red plate, with no separate exit button. Turn `pack` on only once the server pack carries the font, or players see boxes. The default is off.

The dialog's blur hides the boss bar, so the pin grid has no boss bar and draws its countdown in the dialog. The title counts down whole seconds, rounded up ("Set the pins 路 3"), and turns red for the last three. With the pack, the board's top edge holds a strip, drawn from the top row's last button so it lies over the whole row, that drains gold while memorising and green, then red, while setting pins. The dialog redraws every 4 ticks for the strip, or each second for the title alone (`PinGridGame.TIMER_REDRAW_TICKS`). Click callbacks live 30 seconds, since a fresh frame always arrives well within that.

1. Prepare (`prepare-seconds`): every lamp unlit.
2. Scan: the pins light row by row, two ticks a row, with a rising tick.
3. Memorise (`memorise-seconds`): every pin lit.
4. Recall (`recall-seconds` + `recall-seconds-per-dexterity` x Dexterity): the lamps go dark and the thief clicks the pins. Each correct pin plays the next note of a pentatonic scale; a wrong cell plays a bass note. The countdown in the title (and the strip, with the pack) turns red for the last 3 seconds, with a tick each second.

The dialog cannot be closed with Escape; "Give up" is the way out and counts as a failed attempt. The dialog is redrawn when something changes, and while a countdown runs (see above). A game's click actions are made once and reused by every frame, so frequent frames do not pile up click callbacks. A quick thief often clicks again before the redrawn frame arrives, so a cell click counts whichever frame it came from; a cell is the same cell on every frame, and clicking a cell twice does nothing. Buttons from an old dialog stop working once that game ends, is cancelled or is replaced by a new one.

A dialog button whose label is only a texture draws nothing on 1.21.10 clients, so each label is a one-pixel dot in the button's face colour (no shadow) followed by the texture, tinted white.
Both halves of a double chest are one lock (`ContainerManager.lockBlock`, the left half): they share the fail cooldown, and only one thief at a time can work either half, in the ring or the probe menu. The access cooldown is checked again when the minigame is solved, because a guildmate's pick nearby can start it while the lock is being worked. While a thief works a lock, Thievery's door picking and grave looting ignore their clicks; those listeners handle cancelled clicks, so the ring's cancel alone does not stop them.

### Lockpick ring (display entities)

Expand All @@ -51,16 +34,15 @@ Modelled on the NoPixel lockpick minigame. `RingView` floats a ring of 24 dots i
- Quiet clicks mark each notch the needle passes; they rise in pitch inside the zone.
- A press is judged where the needle was `ping / 50ms` ticks earlier (up to `dial.max-lag-ticks`), which is what the thief saw. A pass only runs out that many ticks after its end, so a laggy press for a zone near the end still arrives in time. A hit pops a pin with a green puff of dust; too soon, too late, the wrong key, two keys at once (fumbled) or a full pass without a press is a slip, with a red flash and a red puff. The puffs go off just past the needle's outer tip, clear of the zone, scale with the ring, and are sent to the thief only.
- The ring floats at `dial.distance` blocks, slightly above the crosshair to clear the action bar, and shrinks as it comes closer so it always looks the same size. Because of that, each point of the ring stays on one sight line from the eye; placement traces a 5x5 grid of sight lines across the ring's outline (needle, pins, hint and labels) and keeps every point at most 60% of the way to the block behind it, but no closer than 0.25 blocks. Checking only the crosshair let the ring sink into a chest's lid when looking down at it, or into a raised chest when looking up.
- Setting `dial.tumblers` (4) solves the lock. `dial.mistakes-to-fail` slips (1) fail it, separately from the grid's `mistakes-to-fail` (3). A solved ring therefore has no slips, so it adds no seized pins unless that setting is raised.
- Setting `dial.tumblers` (4) solves the lock. `dial.mistakes-to-fail` slips (1) fail it. A solved ring therefore has no slips, so it adds no seized pins unless that setting is raised.
- You cannot start the ring while riding or gliding.

### Failure and interruptions

Failing means `mistakes-to-fail` wrong cells, `dial.mistakes-to-fail` slips, the grid's recall timer running out, giving up (the dialog button or sneak), being hurt, or logging out mid-pick. On failure:
Failing means `dial.mistakes-to-fail` slips, giving up (sneak), being hurt, or logging out mid-pick. On failure:

- On the grid, the missed pins show as gold.
- `LockPickManager.applyCooldown` puts the chest's target id `chest:<world>:<x>:<y>:<z>` on `fail-cooldown-ms`. A new attempt is refused until it expires, and `/thievery` cooldown resets clear it.
- `fail-break-chance` rolls whether one lockpick from the main-hand stack snaps. Only the pick the thief started with can snap: during either minigame the hotbar, dropping, swapping hands and moving items are blocked, and a different item in hand is never broken.
- `fail-break-chance` rolls whether one lockpick from the main-hand stack snaps. Only the pick the thief started with can snap: during the ring the hotbar, dropping, swapping hands and moving items are blocked, and a different item in hand is never broken.

Being teleported away (another world, or more than a block) ends the pick. A teleport the thief brings on themselves, after running a command or by ender pearl or chorus fruit, counts as a failed attempt; one done to them, by staff or another plugin, does not.

Expand Down Expand Up @@ -88,7 +70,7 @@ The probe menu is a minesweeper-style puzzle instead of a random break roll. `Se
- Probing a seized pin snaps one lockpick, reveals every seized pin (iron bars) and stops probing. Slots already revealed can still be taken, as before.
- Items under seized pins cannot be reached in that session.

Pin count = chest slots x `lockpicking.chest.seized-density` (0.3) x break chance x the lock type's `break-chance-multiplier`, rounded, plus `seized-per-grid-mistake` (1) for each wrong cell on the pin grid or slip on the dial. Break chance is `1 - success chance`, from `base-success-chance`, Dexterity (`dex-map`) and pick strength, capped by `max-success-chance`. With an iron pick (0.35), a 27-slot chest at Dexterity 0 hides 5 pins, and a 54-slot double chest hides 11. At Dexterity 40 the chest hides none before grid mistakes. Risk gain per probe and clue drops are unchanged.
Pin count = chest slots x `lockpicking.chest.seized-density` (0.3) x break chance x the lock type's `break-chance-multiplier`, rounded, plus `seized-per-slip` (1) for each slip on the ring (the older `seized-per-grid-mistake` key is still read). Break chance is `1 - success chance`, from `base-success-chance`, Dexterity (`dex-map`) and pick strength, capped by `max-success-chance`. With an iron pick (0.35), a 27-slot chest at Dexterity 0 hides 5 pins, and a 54-slot double chest hides 11. At Dexterity 40 the chest hides none before slips. Risk gain per probe and clue drops are unchanged.

## Bar engine

Expand Down
22 changes: 7 additions & 15 deletions projects/Thievery/docs/TEST_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,28 @@ Run these checks on Minecraft **1.21.10** with the intended plugin dependencies
| R1 | Door lockpick bar still runs; success opens door |
| R2 | Door fail/break applies 60s on that door only |
| R3 | Walking away cancels door pick |
| R4 | Solving a minigame opens the chest probe menu; budget, clues and taking items work as before (seized pins replace the random break roll, see G13-G18) |
| R4 | Solving the ring opens the chest probe menu; budget, clues and taking items work as before (seized pins replace the random break roll, see G13-G18) |

## Chest lock minigames
## Chest lock minigame

| # | Check |
|---|--------|
| G1 | Lockpick right-click on a chest opens the pin grid dialog about half the time; lamps scan on row by row, hold for 4s, then go dark |
| G2 | Every grid cell shows its texture (unlit lamp, lit lamp, sea lantern, redstone block, gold block); no stray dots are visible, hovered or not |
| G3 | Clicking the pins plays a rising scale; a wrong cell plays a bass note and shows a redstone block; the status line counts pins and slips |
| G4 | Setting every pin opens the probe menu with seized pins (G13-G18); clues and taking behave as before |
| G5 | Three wrong cells or the timer running out shows missed pins in gold, applies the 60s cooldown and may snap the pick; the title's countdown (and the pack's strip) turns red and ticks for the last 3 seconds; no boss bar shows |
| G6 | Escape does not close the grid; "Give up" does, and counts as a failed attempt |
| G7 | Clicking pins as fast as possible sets every one; none are dropped while the dialog redraws |
| G8 | With `grid.pack: true` and the pack loaded, the grid is one dark board of square tiles with a rim; the hovered tile shows a white outline; Give up is a red plate closing the bottom of the board |
| G9 | With the pack, the strip along the board's top drains gold while memorising, then green, then red for the last 3 seconds; the title counts down the seconds |
| G4 | Setting every tumbler opens the probe menu with seized pins (G13-G18); clues and taking behave as before |
| G10 | Failing on one half of a double chest puts both halves on the fail cooldown; a second thief cannot start on either half while one is working it |
| G11 | A failed pick with a different item forced into the hand never breaks that item |
| G12 | A guildmate opening a nearby chest's probe menu while you work this lock stops your probe menu opening, with the access cooldown message |
| D1 | The other half of picks float the lockpick ring in front of the thief; nobody else nearby sees it |
| D1 | Every lockpick right-click on a chest floats the lockpick ring in front of the thief; nobody else nearby sees it |
| D2 | A white needle with a dark edge crosses the dots, stays sharp at every angle, and glides smoothly round the ring at the client's frame rate |
| D3 | The middle shows the thief's own key: W/A/S/D on QWERTY, Z/Q/S/D on AZERTY, rebound keys if rebound; pressing it in the green sets a tumbler pin |
| D4 | The hotbar never changes; the thief cannot walk or jump, the field of view does not change, but they can look around |
| D5 | Too soon, too late, the wrong key, two keys at once, or a full pass without a press is a slip, and one slip fails with the grid's penalties |
| D5 | Too soon, too late, the wrong key, two keys at once, or a full pass without a press is a slip, and one slip fails, applies the 60s cooldown and may snap the pick |
| D6 | Each tumbler narrows the zone and reverses the sweep; notch clicks rise inside the zone |
| D7 | Picking while looking at a chest or wall up close, looking down at a chest on the ground from 1-4 blocks, or looking up at a raised chest floats the whole ring in front of it, never partly inside it |
| D8 | Sneak gives up; taking damage fails; being teleported away by staff ends the pick without a penalty; an ender pearl, chorus fruit or your own command teleport counts as a failed attempt |
| D9 | Logging out mid-ring fails the attempt and the next join walks and jumps normally; a crash mid-ring is undone on the next join |
| D10 | Riding a mount or gliding refuses to start the ring; a flying staff member is set down and can fly again afterwards |
| D11 | With 100-200 ms ping, presses still land where the pick looked |
| D12 | `/thievery testpick grid` and `/thievery testpick dial` force each minigame for staff |
| D12 | `/thievery testpick` runs the ring for staff on the targeted container, even with the minigame off |
| D13 | Starting a second pick while in one says "You're already working a lock." |
| D14 | During the ring, scrolling the hotbar, dropping, swapping hands, moving items and right-clicking a horse or boat do nothing |
| D15 | Starting the ring mid-jump lands the thief normally, with no flying kick, and the ring appears in front of them once they land |
Expand All @@ -50,7 +42,7 @@ Run these checks on Minecraft **1.21.10** with the intended plugin dependencies
| G14 | Empty probed slots show the count by colour and stack size; item slots show it in the first lore line |
| G15 | Right-click (or shift-right-click) marks and unmarks a hidden slot; a marked slot cannot be probed; number keys and double clicks never probe; the title `Seized:` count drops per mark |
| G16 | Probing a seized pin snaps one lockpick, shows every seized pin, and stops probing; revealed items can still be taken |
| G17 | Two wrong grid cells or ring slips add two seized pins to the chest |
| G17 | With `dial.mistakes-to-fail` raised, two ring slips add two seized pins to the chest |
| G18 | High Dexterity or a strong pick on a small chest leaves few or no seized pins |

## Pickpocket ring
Expand Down