Skip to main content

Puzzle

Nine puzzle types with client-side boards, timers, attempts, chains and facts.

Freev0.6Quests & narrative
RequiresTypeWriter0.9Basic (TypeWriter extension)

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 amount when 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​

  1. Install the official TypeWriter Basic extension, which provides the shared types the puzzles use (sounds, items, variables).
  2. Put the Puzzle JAR in plugins/Typewriter/extensions/ and restart the server.
  3. 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:

  1. In the TypeWriter web panel, open a manifest page and add a Lever Order entry.
  2. In levers, add three levers. For each, capture the position of a lever block in game and set orderIndex to 0, 1 and 2, the order in which the player must flip them.
  3. Set amount to 3 so the hint reads 1/3, 2/3, 3/3. Set resetOnWrong on, and cooldownOnFail to 5 if you want a pause after a mistake.
  4. Link onComplete to a sequence that opens the door, and fact to a cachable fact that stores the reward. This fact is also the reliable way to remember that the puzzle was solved.
  5. Optionally, put the puzzle under an audience filter such as a quest audience, so it only runs for the right players.
  6. 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.
New puzzles

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.

FieldMeaning
factA cachable fact increased by amount when the puzzle is solved.
amountMaximum progress, and the reward added to the fact.
timeLimitSeconds before the puzzle times out. 0 disables it.
cooldownOnFailSeconds the player must wait after a failure.
maxAttemptsWrong attempts allowed per session. 0 disables the limit.
resetOnWrongWhether a wrong step clears the progress.
showHints, hintMessageA chat hint after each step, with {progress} and {total}. Blank sends nothing.
completionMessageSent once when solved. Blank turns it off. The default is <green>Puzzle completed!.
onStart, onComplete, onFail, onTimeout, onLifespanExpireEntries triggered for the player.
lifespanSeconds before the puzzle expires for the player, whatever they do. 0 disables it.
activationRadiusBlocks around the puzzle positions. 0 removes the distance check, but the world check always applies. The default is 16.
criteriaConditions 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.
A Puzzle Artifact is required

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​

EntryWhat the player doesMain fields
Block PushLeft- 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 SequenceWatch 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 GridRight-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 OrderRight-click levers in the order of their orderIndex. A wrong lever resets all of them.levers (position, orderIndex).
Pattern PlacementPlace blocks on a grid to reproduce a pattern.pattern, gridOrigin, columns, previewOffsetY, ghostBlocks, tolerance.
Coop PressureSeveral players stand on different plates and hold for a few seconds.plates, requiredPlayers, holdDuration (seconds).
Item FramePut an item in an existing item frame, then rotate it to the target angle.frameTargets (correctRotation 0 to 7, requiredItem), checkItem.
Combination LockClick digit blocks in the order of a code.digits (positions), code (digit indices, counted from 0).
PedestalRight-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; beamDensity is the number of particles per block. A beam is at most maxBeamLength blocks 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 raise maxBeamLength. 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 digits is digit 0, the second is digit 1, and so on. code lists these indices in the order to click. For example, with four buttons in digits, the code [2, 0, 3] means: third button, first button, fourth button. A code value outside the listed digits makes the lock ignore every press; the server log now names the offending value.
  • Pattern Placement. pattern lists materials row by row, columns wide, and its length must be a multiple of columns. The grid lies on the horizontal plane at the Y of gridOrigin, with columns along X and rows along Z. A preview floats previewOffsetY blocks above. With ghostBlocks on, 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. tolerance is 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 for holdDuration, 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), and correctRotation is 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 requiredItem can be given by right-click, or dropped within pickupRadius blocks of the pedestal. A dropped matching item is removed when consumeDroppedItem is 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.

CommandDescription
/typewriter puzzleOpen the chat menu of the first Puzzle Menu entry, or tell that none exists.
/typewriter puzzle listList 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 statusShow 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 closeClose 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 reloadClear all runtime states and chains.

Permissions​

PermissionGrants
puzzle.adminThe /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).

What survives a disconnect

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.

EntryFieldDefaultPlaceholders
Every puzzlehintMessageThe hint of each puzzle type, for example <yellow>Repeat the sequence! {progress}/{total}.{progress}, {total}
Every puzzlecompletionMessage<green>Puzzle completed!none
Puzzle MenutitleText<gold><bold>Puzzles</bold> <gray>-none
Puzzle MenupuzzleLineText<green>{puzzle} <gray>- <aqua>click to start{puzzle} the puzzle entry id
Puzzle MenufooterText<gray>Use <white>/typewriter puzzle stop<gray> to stop all active puzzles.none
Memory SequencememorizeMessage<gold>Memorize the sequence…none
Memory SequenceyourTurnMessage<green>Your turn! <white>{progress}/{total}{progress}, {total}
Memory SequencestepCorrectMessage<green>Well done! <white>{progress}/{total}{progress}, {total}
Lever OrderwrongOrderMessage<red>Wrong order — the levers have been reset.none
Pattern PlacementprogressMessage<green>Pattern <white>{progress}/{total}{progress}, {total}
Item FramemissingFrameMessage<red>No item frame at <white>{x} {y} {z}{x}, {y}, {z}
Item FramewrongItemMessage<red>That is not the right item in the frame.none
Item FrameinsertingMessage<gray>Inserting the item…none
Item FramemissingItemMessage<yellow>First place the requested item in the frame.none
Item FrameitemPlacedMessage<green>Item placed — rotate the frame.none
Item FramerotationMessage<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.)DefaultPlaceholders
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>ACTIVEnone
command.status.completed<aqua>COMPLETEDnone
command.status.stopped<yellow>STOPPEDnone
command.status.inactive<gray>INACTIVEnone
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
Facts2
Audiences9
Artifacts1
Other entries1

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.