Puzzle
Overview
Puzzle turns a place in your world into a solvable puzzle. You choose one of nine mechanics: push blocks into holes, repeat a sequence, flip levers in order, redirect laser beams, and more. Each puzzle is an audience entry that you place in the web panel. It reports its result to the rest of TypeWriter through triggers (onStart, onComplete, onFail, onTimeout, onLifespanExpire) and an optional fact.
Most of the board is drawn client-side: blocks, highlights, previews and laser particles are packets sent to the player who solves the puzzle, so two players can work on the same board without seeing each other's progress. A few mechanics do touch the world: the item frame puzzle rotates a real item frame, and the pattern puzzle places real blocks unless ghostBlocks is on.
A puzzle belongs to the place where you built it. It only runs for a player standing in the world of its positions, within activationRadius blocks of one of them. Reaching the board starts a session; walking away ends it and clears the client-side board.
Features
- Nine puzzle types — block push, memory sequence, laser grid, lever order, pattern placement, coop pressure plates, item frame, combination lock and pedestal offering.
- Shared lifecycle — time limit, maximum wrong attempts, failure cooldown, lifespan, hints and a completion message on every puzzle.
- Triggers everywhere — start, complete, fail, timeout and lifespan expiry can each start a TypeWriter entry.
- Fact reward — a fact is increased by
amountwhen the puzzle is solved. - Puzzle chains — run several puzzles in order and react to the whole chain.
- Spatial gating — a world check and an activation radius stop a board from appearing in another dimension or far away.
- Statistics — attempts, solves and best time per player, with a leaderboard command.
- Status facts — read whether a puzzle is solved or active for a player.
- Folia-safe — world access runs on the region that owns the puzzle.
Installation
- Install the official TypeWriter
Basicextension, which provides the shared types the puzzles use (sounds, items, variables). - Put the Puzzle JAR in
plugins/Typewriter/extensions/and restart the server. - Optional: add a Puzzle Artifact entry to your pages if you want attempts, solves and best times to be saved (see the warning in Configuration).
Getting started
Build a three-lever door puzzle:
- In the TypeWriter web panel, open a manifest page and add a Lever Order entry.
- In
levers, add three levers. For each, capture thepositionof a lever block in game and setorderIndexto0,1and2, the order in which the player must flip them. - Set
amountto3so the hint reads1/3,2/3,3/3. SetresetOnWrongon, andcooldownOnFailto5if you want a pause after a mistake. - Link
onCompleteto a sequence that opens the door, andfactto a cachable fact that stores the reward. This fact is also the reliable way to remember that the puzzle was solved. - Optionally, put the puzzle under an audience filter such as a quest audience, so it only runs for the right players.
- Publish, restart or reload the extension, and walk up to the levers. Flipping them in the right order solves the puzzle. Flipping the wrong lever resets the lever states and fires
onFail.
Puzzle entries are registered when the extension loads. After adding a puzzle, reload TypeWriter or restart before using /typewriter puzzle start.
How it works
Sessions. A session starts when the player enters the radius. It clears progress, wrong attempts and the failure cooldown, starts timeLimit and lifespan, and fires onStart. So maxAttempts is a budget per session: a player who runs out can walk away and come back. A puzzle already solved by a player is never reopened for them.
A solved puzzle is inert. Once a player has solved a Block Push, Combination Lock, Lever Order or Laser Grid, it draws no board for them and no longer cancels their interactions, even if they come back into its audience (a filter that flapped, a rejoin). The levers and buttons of a solved puzzle work like ordinary blocks, and a solved Pedestal no longer swallows the pickup of its item.
Cancelled clicks. The server answers a cancelled click with the real state of the clicked block, which would erase a block drawn client-side. The board is therefore redrawn one tick after every cancelled click (Memory buttons, Pattern grid, refused placements, refused pushes), and when a player hits a drawn mirror or lever with a left click.
Completion fires once. The fact write, the completion message, onComplete and the chain progress come from one accepted result, so a repeated click cannot trigger them twice.
Progress. Each correct step raises the progress by one, up to amount. {progress} and {total} in hintMessage show the progress and amount. The puzzles do not set amount from their own size, so enter the real number of steps yourself.
Shared options
Every puzzle has these fields.
| Field | Meaning |
|---|---|
fact | A cachable fact increased by amount when the puzzle is solved. |
amount | Maximum progress, and the reward added to the fact. |
timeLimit | Seconds before the puzzle times out. 0 disables it. |
cooldownOnFail | Seconds the player must wait after a failure. |
maxAttempts | Wrong attempts allowed per session. 0 disables the limit. |
resetOnWrong | Whether a wrong step clears the progress. |
showHints, hintMessage | A chat hint after each step, with {progress} and {total}. Blank sends nothing. |
completionMessage | Sent once when solved. Blank turns it off. The default is <green>Puzzle completed!. |
onStart, onComplete, onFail, onTimeout, onLifespanExpire | Entries triggered for the player. |
lifespan | Seconds before the puzzle expires for the player, whatever they do. 0 disables it. |
activationRadius | Blocks around the puzzle positions. 0 removes the distance check, but the world check always applies. The default is 16. |
criteria | Conditions a player must meet to be part of the puzzle. |
Shared puzzles
When isShared is on and the fact of the puzzle has a group (party, team), the group plays one puzzle: each step, mistake and cooldown of a player is copied to the online players of that group. The first member to solve it solves it for the group: the fact is raised by amount once, and every member gets the completion message, onComplete and the chain step once. A member who was away gets them when they come back. A player gets the completion of a puzzle at most once, even if they change group; only an admin reset that concerns them lets them receive it again.
- The solve is kept per group. Dying or reconnecting does not reset it and rewards nothing again, unlike a solo puzzle, which is replayable after a death or a disconnect.
- Statistics and each player's board stay personal. Without a fact group, or for a player who belongs to none of it, nothing is shared. Coop Pressure ignores
isShared: it already completes the puzzle for every participant. - To play it again, use
/typewriter puzzle admin reset <player> <id>, which reopens the puzzle for the whole group of that player.
The group solve is saved in the Puzzle Artifact. Without it, the record only lives in memory: after a server restart the group can solve the puzzle again and gets paid again. Add the artifact before using isShared. Going back to an older build of the extension also loses the shared record, because that build saves the artifact without it, and the rewards are paid again. The solve is written at the next save (every 30 seconds): a crash inside that window can lose it.
Puzzle types
| Entry | What the player does | Main fields |
|---|---|---|
| Block Push | Left- or right-click a block to push it one block in the direction the player faces, until each target hole is filled. | pushBlocks, targetHoles (with acceptedMaterial), blockMaterial, holeMaterial. The number of blocks must equal the number of holes. allowPull has no effect (pulling is not implemented yet). |
| Memory Sequence | Watch buttons light up, then click them in the same order. The sequence is random for each session. | buttons (at least 2), sequenceLength, highlightDuration, stepGap, inputDelay (ticks), highlightMaterial. |
| Laser Grid | Right-click mirrors to rotate them until every receiver is hit by a beam of its color. | emitters (color, direction), mirrors (rotatable, initialDirection), receivers (requiredColor), maxReflections, maxBeamLength, beamDensity. |
| Lever Order | Right-click levers in the order of their orderIndex. A wrong lever resets all of them. | levers (position, orderIndex). |
| Pattern Placement | Place blocks on a grid to reproduce a pattern. | pattern, gridOrigin, columns, previewOffsetY, ghostBlocks, tolerance. |
| Coop Pressure | Several players stand on different plates and hold for a few seconds. | plates, requiredPlayers, holdDuration (seconds). |
| Item Frame | Put an item in an existing item frame, then rotate it to the target angle. | frameTargets (correctRotation 0 to 7, requiredItem), checkItem. |
| Combination Lock | Click digit blocks in the order of a code. | digits (positions), code (digit indices, counted from 0). |
| Pedestal | Right-click the pedestal block while holding an item, or drop or pick up the item near it. | targetPosition, requiredItem, consumeItem, pickupRadius, consumeDroppedItem. |
Details of each type:
- Laser Grid. A beam starts one block from the emitter and goes straight. A mirror sends it in the direction the mirror faces. Right-clicking a mirror sets its direction to the opposite of the face you clicked, or turns it clockwise if you click its top or bottom. A solid block stops the beam. The puzzle is solved when every receiver is hit by a beam of its
requiredColor. Beams are shown with particles, drawn through the centre of the blocks and turning exactly on each mirror;beamDensityis the number of particles per block. A beam is at mostmaxBeamLengthblocks long (default 64; 0 or less means 64, never unlimited) and it never loads a chunk: it stops at the edge of the loaded world. A board with a longer beam must raisemaxBeamLength. A mirror does not vanish when you right-click it: the board is redrawn after the click. - Block Push. Each push moves a block one cell in the direction the player faces. A block pushed back onto its starting cell is always accepted there, and a target hole that a block was pushed over and away from is drawn again. Leave the starting cells empty (air) in the world. A real block there is hidden on the client but stays solid for the server, so nobody could stand on it. A refused push (a wall, another block) counts one wrong attempt per block and target cell until a push succeeds, so holding the click against a wall does not burn
maxAttempts. - Lever Order. A lever rests in the state it has in the world when the player reaches the board, flips from there, and is restored to that state by a reset or after a wrong order, even when it rests powered. A click during the failure cooldown does not reach a real lever.
- Combination Lock. The numbering starts at 0: the first position listed in
digitsis digit 0, the second is digit 1, and so on.codelists these indices in the order to click. For example, with four buttons indigits, the code[2, 0, 3]means: third button, first button, fourth button. Acodevalue outside the listed digits makes the lock ignore every press; the server log now names the offending value. - Pattern Placement.
patternlists materials row by row,columnswide, and its length must be a multiple ofcolumns. The grid lies on the horizontal plane at the Y ofgridOrigin, with columns along X and rows along Z. A preview floatspreviewOffsetYblocks above. WithghostBlockson, a translucent version is drawn on the grid itself and correct blocks are shown client-side, taking the item from the player. A wrong placement is always cancelled.toleranceis the number of wrong placements allowed before the progress resets. - Coop Pressure. A plate counts when a player's feet are on that block, or the block above it. One player per plate. All players in the audience take part, so add them with
/typewriter puzzle join <entry id>. When the plates have been held forholdDuration, every participant completes the puzzle. - Item Frame. The frame must already exist at the configured position; the puzzle never spawns one. An empty frame accepts only the
requiredItem, taken from the player's hand. Each click on a filled frame rotates it one step (45 degrees), andcorrectRotationis the step count from 0 to 7 where it is correct. The frame is a real entity, so the rotation is visible to everyone. - Pedestal. The
requiredItemcan be given by right-click, or dropped withinpickupRadiusblocks of the pedestal. A dropped matching item is removed whenconsumeDroppedItemis on.
Commands
All commands are sub-commands of /typewriter (alias /tw). <id> is the entry id of a puzzle. Only admin commands need a permission.
| Command | Description |
|---|---|
/typewriter puzzle | Open the chat menu of the first Puzzle Menu entry, or tell that none exists. |
/typewriter puzzle list | List the registered puzzles and their types. |
/typewriter puzzle start <id> | Add yourself to the audience of a puzzle. It refuses if you already solved it. |
/typewriter puzzle join <id> | Join a puzzle that is already running, for example a coop puzzle. |
/typewriter puzzle chain <id> | Start a puzzle chain by its entry id or name. |
/typewriter puzzle status | Show your state on every puzzle: active, completed, stopped or inactive. |
/typewriter puzzle leaderboard <id> | Show your statistics and the top ten players for a puzzle. |
/typewriter puzzle stop [id] | Stop one puzzle or all your active puzzles, and reset their progress. |
/typewriter puzzle close | Close your open inventory. |
/typewriter puzzle admin reset <player> <id> | Reset a player's state for a puzzle and restart it. For a shared puzzle, reopens it for the player's whole group. |
/typewriter puzzle admin reload | Clear all runtime states and chains. |
Permissions
| Permission | Grants |
|---|---|
puzzle.admin | The /typewriter puzzle admin commands. |
Configuration
Persistence. Without a Puzzle Artifact, nothing is saved. With one, the extension saves the state and the statistics every 30 seconds and at shutdown, and loads them at start. Create exactly one artifact and keep its generated artifactId. The artifact is required for shared puzzles (see Shared puzzles).
When a player disconnects, their puzzle state is removed from memory, and the next save does not contain it. The statistics stay. To remember a solved puzzle for good, set the fact of the puzzle and read that fact in your criteria. Puzzle Solved Fact only reflects the memory state. A shared puzzle is the exception: its solve belongs to the group and is saved in the artifact.
Messages. No text shown in game is fixed in the code. Texts that belong to one puzzle are fields of its entry, and the replies of the commands plus the two messages every puzzle shares are snippets. See Editable texts.
Editable texts
Texts are MiniMessage, so you can reword, translate or recolour them. A {name} in a text is replaced by the value named in the tables below; a placeholder that does not exist stays as written. PlaceholderAPI placeholders (%...%) are read for the player who receives the text. Only the server console log stays fixed, in English.
Fields of the entries
These fields sit on the entry in the web panel. Each is a text per player, so it accepts variables, and a blank value sends nothing. Existing pages keep the texts they already store; the defaults below apply to new entries and to pages that never stored the field.
| Entry | Field | Default | Placeholders |
|---|---|---|---|
| Every puzzle | hintMessage | The hint of each puzzle type, for example <yellow>Repeat the sequence! {progress}/{total}. | {progress}, {total} |
| Every puzzle | completionMessage | <green>Puzzle completed! | none |
| Puzzle Menu | titleText | <gold><bold>Puzzles</bold> <gray>- | none |
| Puzzle Menu | puzzleLineText | <green>{puzzle} <gray>- <aqua>click to start | {puzzle} the puzzle entry id |
| Puzzle Menu | footerText | <gray>Use <white>/typewriter puzzle stop<gray> to stop all active puzzles. | none |
| Memory Sequence | memorizeMessage | <gold>Memorize the sequence… | none |
| Memory Sequence | yourTurnMessage | <green>Your turn! <white>{progress}/{total} | {progress}, {total} |
| Memory Sequence | stepCorrectMessage | <green>Well done! <white>{progress}/{total} | {progress}, {total} |
| Lever Order | wrongOrderMessage | <red>Wrong order — the levers have been reset. | none |
| Pattern Placement | progressMessage | <green>Pattern <white>{progress}/{total} | {progress}, {total} |
| Item Frame | missingFrameMessage | <red>No item frame at <white>{x} {y} {z} | {x}, {y}, {z} |
| Item Frame | wrongItemMessage | <red>That is not the right item in the frame. | none |
| Item Frame | insertingMessage | <gray>Inserting the item… | none |
| Item Frame | missingItemMessage | <yellow>First place the requested item in the frame. | none |
| Item Frame | itemPlacedMessage | <green>Item placed — rotate the frame. | none |
| Item Frame | rotationMessage | <gray>Rotation {current}/{total} | {current}, {total} |
hintMessage, completionMessage and the chat menu fields are sent in chat; the other fields are shown on the action bar. Some of these defaults were French in earlier builds; they are now English, and a page that stored the French text keeps it.
Snippets
Everything the /typewriter puzzle commands print, the admin commands included, and the two messages shared by every puzzle, is a TypeWriter snippet. Open plugins/Typewriter/snippets.yml, find the key under content.puzzle, change the value and run /tw reload. The extension registers all its snippets with their default text when it starts, so they appear in the file after the first start.
Snippet (under content.puzzle.) | Default | Placeholders |
|---|---|---|
command.no_menu | <red>No puzzle menu configured. | none |
command.list.none | <red>No puzzles configured. | none |
command.list.header | <gold>=== Available Puzzles === | none |
command.list.line | <green>{puzzle} <gray>- {type} | {puzzle}, {type} |
command.puzzle_not_found | <red>Puzzle not found: <white>{puzzle} | {puzzle} |
command.start.already_completed | <yellow>Puzzle already completed. Use <white>/typewriter puzzle admin reset {player} {puzzle}<yellow> to replay it. | {player}, {puzzle} |
command.start.started | <green>Starting puzzle: <white>{type} | {type} |
command.join.already_completed | <yellow>Puzzle already completed for you: <white>{puzzle} | {puzzle} |
command.join.joined | <green>Joined puzzle: <white>{type} | {type} |
command.stop.not_active | <gray>Puzzle is not active: <white>{puzzle} | {puzzle} |
command.stop.stopped | <yellow>Stopped puzzle: <white>{puzzle} | {puzzle} |
command.stop.all_none | <gray>No active puzzle. | none |
command.stop.all_stopped | <yellow>Stopped <white>{count} <yellow>puzzle(s). | {count} |
command.status.header | <gold>=== Puzzle Status === | none |
command.status.line | <gray>{puzzle}: {status} <dark_gray>progress={progress} | {puzzle}, {status}, {progress} |
command.status.active | <green>ACTIVE | none |
command.status.completed | <aqua>COMPLETED | none |
command.status.stopped | <yellow>STOPPED | none |
command.status.inactive | <gray>INACTIVE | none |
command.chain.not_found | <red>Chain not found: <white>{chain} | {chain} |
command.chain.invalid | <red>Chain is invalid or contains unresolved puzzles: <white>{chain} | {chain} |
command.chain.empty | <red>Chain has no resolvable puzzle entries. | none |
command.chain.started | <green>Starting puzzle chain: <white>{chain} | {chain} |
command.leaderboard.header | <gold>=== Puzzle Stats: <white>{puzzle} <gold>=== | {puzzle} |
command.leaderboard.attempts | <gray>Attempts: <white>{attempts} | {attempts} |
command.leaderboard.solves | <gray>Solves: <white>{solves} | {solves} |
command.leaderboard.best_time | <gray>Best time: <white>{seconds}s | {seconds} |
command.leaderboard.rows_header | <gold>--- Leaderboard --- | none |
command.leaderboard.row | <gray>#{rank} <white>{player} <gray>- <white>{solves} solves | {rank}, {player}, {solves} |
command.leaderboard.row_timed | <gray>#{rank} <white>{player} <gray>- <white>{solves} solves <gray>{seconds}s | {rank}, {player}, {solves}, {seconds} |
command.admin.reset.player_not_found | <red>Player not found: <white>{player} | {player} |
command.admin.reset.done | <green>Reset puzzle <white>{puzzle} <green>for <white>{player} | {puzzle}, {player} |
command.admin.reset.done_group | <green>Reset puzzle <white>{puzzle} <green>for <white>{player} <green>and its group (<white>{count}<green> online) | {puzzle}, {player}, {count} |
command.admin.reload.done | <green>Puzzle runtime states cleared. | none |
play.cooldown_wait | <red>Puzzle on cooldown — <white>{seconds}s | {seconds} |
play.max_attempts | <red>Too many mistakes — restart the puzzle to try again. | none |
{type} is the name of the puzzle type (Memory Sequence, Lever Order, ...) and {status} is one of the four command.status.* snippets, so both keep their colours. Every other placeholder is plain text: what a player types in a command cannot add formatting to the reply.
Entries
14 entries, generated from the extension source.
Actions1
- Puzzle MenuPuzzle Menu
Facts2
- Puzzle Active FactPuzzle Active Fact
- Puzzle Solved FactPuzzle Solved Fact
Audiences9
- Puzzle Block PushPush blocks into target holes -
- Puzzle Combination LockEnter the correct code by pressing buttons/levers in sequence
- Puzzle Coop PressureCooperative pressure plate puzzle -
- Puzzle Item FrameRotate item frames to the correct angles
- Puzzle Laser GridLaser grid puzzle -
- Puzzle Lever OrderPuzzle where levers must be activated in the correct order
- Puzzle MemoryMemory sequence puzzle -
- Puzzle PatternPattern placement puzzle -
- Puzzle PedestalOffer an item on a pedestal to solve the puzzle
Artifacts1
- Puzzle ArtifactPersists puzzle states across server restarts
Other entries1
- Puzzle ChainChain of puzzles -
Troubleshooting
The board does not appear. You must be in the world of the puzzle positions and within activationRadius of one of them. Positions left at 0, 0, 0 are ignored, so a puzzle with no positions set has no anchor.
Nothing happens when I click. Check that you are in the audience (the criteria must match, and an audience filter must include you) and that the puzzle is not already solved for you: use /typewriter puzzle status, and /typewriter puzzle admin reset <player> <id> to replay. Clicks during the failure cooldown are ignored; the action bar shows the remaining time.
/typewriter puzzle start says the puzzle is not found. Puzzles are registered when the extension loads, by entry id. Reload after adding a puzzle, and use the entry id, not its name.
The memory puzzle never starts. buttons needs at least 2 entries, and each needs a position.
The pattern puzzle ignores my blocks. pattern must have a length that is a multiple of columns, and you must place blocks on the grid at the Y of gridOrigin. A wrong material is cancelled.
The item frame puzzle says there is no frame. Place an item frame at the configured position first. The log also prints a warning with the coordinates.
Progress is lost after reconnecting. See the warning in Configuration: use fact to keep the result.
A coop puzzle never completes. Every player must be in the audience (/typewriter puzzle join <id>), stand on a different plate and stay there for holdDuration. Dropped items on plates do not count.