Repository navigation
AntiSpeedrun v0.1.0
Pre-release
Pre-release
Added
- Resummoned dragon fights keep the exit portal sealed in its bedrock basin until
victory, with a configurable inactivity escape. Repeat victories award a
configurable Dragon Egg trophy, and each dragon can drop a Dragon Head. boss-scaling.multi-dragon.balanced-xpnow awards 1,000 XP per extra dragon when enabled.
Turning it off retains the previous 500 XP cap. The original dragon keeps vanilla's
first-kill or resummon reward, anddoMobLootstill prevents XP drops.boss-scaling.scale-resummoned-dragons, which until now was parsed and read by nothing, scales
fights resummoned with four End crystals. The resummoned dragon opens its own
battle-prep-secondswindow when it appears, and the party on the main island is counted and
reinforced exactly as for the first fight./summon, spawn eggs and other plugins' dragons do not
open one.boss-scaling.multi-dragon.rounding-mode, which until now was parsed and read by nothing, sets
how the party'splayers * multiplierrounds to a dragon count:HALF_UP(the default),CEIL
orFLOOR. The product is exact, so10 * 0.7is seven underCEIL. A party of one still
fights one dragon under every mode, and an unknown mode warns and falls back toHALF_UP.- One victory for a multi-dragon fight. The original Ender Dragon cannot die while an extra dragon
lives, so the exit portal, the egg, the 12,000 XP award and the "dragon killed" flag wait for the
last dragon. Extra dragons do not heal from End crystals. A resolved
reinforcement window is recorded indragon-fights.yml, so a restart mid-fight no longer spawns
a second set of extra dragons. - The multi-dragon reinforcement window. The first player to enter an End whose dragon has never
been killed starts aboss-scaling.battle-prep-secondscountdown, shown to everyone in the End;
the dragon fights from the start, so a solo player is never kept waiting. When it ends, survival
and adventure players within 300 blocks of the centre are counted and the extra dragons the
party earns undermulti-dragon.multiplierandmax-dragonsspawn above the main island.
Resummoned fights can now lock their existing exit portal. - Handing a tier-gated item to an Allay, or taking one back from it, is refused for a player who
has not earned the tier. Allays collecting and delivering items on their own are unaffected, so
sorters keep working. Bottling Dragon's Breath is refused until the player meets the End gate's
requirement; it followsdimension-gates.the-end.enabledand is waived by the End waivers and
the item bypass. Mobs picking up items are not gated. - Taking a tier-gated item off an armor stand or out of an item frame is refused for a player who
has not earned the tier. Swapping an item onto an armor stand slot that holds a gated piece counts
as taking it, and a framed item knocked out by the player's punch or projectile stays in the frame.
Placing items and rotating a framed item are unaffected. Followsitem-progression.enabled, the
tier table and the item bypass. item-progression.gate-nested-bundles, which until now was parsed and read by nothing, gates
bundle contents. Taking an item out of a bundle is refused while the bundle holds a stack the
player has not earned, wherever the bundle sits: in a container, or in the player's own hotbar,
main inventory or off hand. Using such a bundle in either hand to spray its contents onto the
ground is refused too, without blocking the block that was clicked. Every stack in the bundle is
checked, because the item the client selects is not visible to the server, so a bundle holding
only ungated or earned items is untouched. Shulker box contents were already covered: a placed
shulker box is a container like any other.- A dispenser firing tier-gated armour onto a player who has not earned that tier is now cancelled, so
the armour stays in the dispenser and the player gets the usual item-gate rejection line. It follows
item-progression.gate-dispensers(default on),item-progression.enabled, the tier table and the item bypass, and only players are gated. anti-cheese.block-early-eye-throwing, which until now was parsed and read by nothing, refuses an
Eye of Ender throw until the player has earnedminecraft:nether/find_fortress. The item use is
denied at the interaction, so the eye stays in the hand and no Eye of Ender entity spawns; setting
an eye into an empty End Portal frame is not a throw and is left alone. Waived by
antispeedrun.bypass.anticheese, an/asr bypassgrant, or/asr unlock the_end. The refusal
line is the newanti-cheese.early-eye-rejection-message, validated as MiniMessage at load like
every other rejection message.- The Journey Guide Book.
/journeybook(alias/rulesbook) and/asr bookhand the player a
written book generated from the live configuration: the dimension gates and what each requires,
the item tiers with their hints, and the Eye of Ender and Mending rules while they are on.
Vanilla advancements are named by their in-game titles in the reader's own language. With
journey-book.give-on-first-joinon, a player who has never received the book is given one a
tick after joining — including players who joined before the plugin was installed, since
delivery is decided by the plugin's own persisted flag rather thanhasPlayedBefore(). Both
spellings of the command are gated onantispeedrun.bookand refuse while the player already
carries a copy or has no room for one. - Brand vector master (
docs/assets/icon-master.svg) and automated multi-resolution icon suite
(scripts/export-icons.mjs) featuring the Progression Crown and Tri-Realm Apex, with CI drift
gate enforcement (.github/workflows/icons.yml). /progress, and/asr progresswhich delegates to it, both gated onantispeedrun.progress
(defaulttrue). It draws the player their own progression card: every dimension gate in
configured order with a completed, in-progress or locked mark, what each one is still waiting on,
and a highlightedNEXT STEPline. That next step is the same string the idle reminder puts in
{NEXT_STEP}— one implementation, so the two cannot drift apart and tell a player different
things. The newprogress-card.simple-cardsetting chooses the shape:AUTO(the default) draws
a plain-ASCII card for a Bedrock client and the full one for a Java client, andALWAYSand
NEVERforce one or the other. Bedrock's font carries none of the marks the full card uses, so
without this a Geyser player saw a column of replacement boxes; the plain card is asserted
character by character rather than described as "renders cleanly".AUTOrecognises a Bedrock
client only on a server actually running Floodgate, which stays asoftdepend— nothing here
compiles against it. Milestone display names and advancement keys read fromconfig.ymlreach
the player as text, never as markup.- Idle reminders, implementing the
idle-remindersection, which until now was parsed and read by
nothing. A player who stands still forstand-still-secondsis shown their next progression goal
on the action bar, as a title, or in chat, at most once percooldown-minutes. Standing still is
detected by a poll on the player's own region scheduler, one per player and only while the feature
is enabled — there is noPlayerMoveEventhandler anywhere in the plugin and no global tick loop,
and a build that introduces either fails the test suite. An action bar reminder is re-sent while it
is up so thatdisplay-duration-secondsis honoured past the client's own three-second fade. A
player who has cleared every gate is told nothing. - Item tier gates are now enforced. The compiled
item-progression.gated-itemstable was
previously built on every reload and read by nothing; three channels now consult it. A player who
has not met a tier's requirements cannot pick a gated item up off the ground, take one out of a
container by an ordinary, shift, hotbar-swap, off-hand-swap, double-click-gather or drop click,
or buy one from a villager or wandering trader. The gate is waived by
antispeedrun.bypass.items, by an unexpired/asr bypassgrant, or by setting
item-progression.enabled: false. Putting gated items into a container is never blocked,
including onto a slot that already holds a matching stack, and neither is moving them around
inside your own inventory — dragging included, since a drag only moves items out of the cursor
and the clicks that would load the cursor from a container are refused. Views that hand a
player's own item straight back — the crafting grid, a crafting table, and the anvil, smithing
table, grindstone, enchanting table, cartography table, loom and stonecutter — are untouched;
furnaces, brewing stands, Crafters and storage blocks are containers and are gated. villager-progression.gate-mending-tradeis now enforced. With it switched on, a player who has
not earnedvillager-progression.required-advancement(Zombie Doctor by default) cannot select a
villager or wandering trader offer whose result carries Mending, and cannot take that result out
of the merchant window if an offer was already selected when they filled the ingredient slots.
The gate is keyed on the enchantment, so a Mending book is told apart from any other enchanted
book — something the material-keyed item tier table cannot do — and it reads both an item's
ordinary and its stored enchantments, so an already-enchanted tool offered by a datapack or a
plugin merchant is covered as well as a librarian's book. It is off by default, costs nothing on
the trade path while it is off, and is waived byantispeedrun.bypass.itemsor an unexpired
/asr bypassgrant. A result that is both above its item tier and enchanted with Mending is
refused once, not twice. The refusal is worded byvillager-progression.hint, shipped as "Cure a
Zombie Villager (Zombie Doctor)"; left blank, it names the raw advancement key. While the gate is
switched on, arequired-advancementchanged from the shipped key beside that shipped hint is a
warning at load, since the refusal would describe a requirement the gate no longer asks for; a
switched-off gate refuses nothing, so it is not warned about. The gate is
independent ofitem-progression.enabledbut borrows that section'srejection-messageand
feedback-cooldown-seconds. An item tier id containing:is warned about at load: it still gates
every item it names, but a tier named exactlyvillager:mendingshares the Mending gate's feedback
throttle and suppresses one of the two refusal lines.- Drop recall, implementing
item-progression.drop-recall-enabled, which until now was parsed and
read by nothing. A player may always re-collect an item entity they dropped or died with,
whatever its tier, so that gear held by administrative grant, gear predating installation, and
gear held under a since-revoked bypass is gated rather than confiscated. The stamp is written to
the item entity, never to anItemStack, so it cannot be transferred, stockpiled or merged
into a stack — seedocs/provenance-model.md§4. - Dimension gates are now enforced. A player who has not met the configured
dimension-gates.<dimension>.require-*requirements cannot reach the Nether or the End on foot,
as the passenger of a boat, minecart or camel, or by a cross-dimensional Ender pearl teleport.
Intra-dimensional teleports —/spawn, random-teleport plugins, chorus fruit — are untouched, as
are teleports another plugin or an operator asked for. The gate is waived by
antispeedrun.bypass.gates, by an unexpired/asr bypassgrant, or by an/asr unlockoverride. - Selective ejection at a portal: an unqualified rider is dismounted and set down on solid ground
two blocks behind the vehicle, while qualified riders in the same boat carry on. The vehicle is
only stopped when nobody aboard qualifies, so no empty vehicle is sent through. /antispeedrun(/asr) administration command with tab completion:reload,profile apply <CASUAL|SMP_STANDARD|HARDCORE>,unlock <nether|end> [lock],bypass <player> [duration|off]
andinspect <player>. Each subcommand is gated on its ownantispeedrun.admin.*node, and
completion offers nothing — including player names — under a subcommand the sender cannot run.- Durable state, so an unlock or a bypass no longer evaporates at the next restart. Server-wide
dimension unlocks are written tostate.yml; per-player bypass grants, which carry an expiry,
and the journey-book delivered flag live in the player's persistent data container. - Journey-book delivery is recorded by the plugin rather than inferred from
hasPlayedBefore(), which is false for every player who joined before the plugin was installed
and would have skipped an established server's entire population. - Configuration profile presets shipped as
profiles/casual.yml,profiles/smp_standard.ymland
profiles/hardcore.yml./asr profile applycopies the previousconfig.ymlto
backups/config-<timestamp>.ymlbefore overwriting it. - Wildcard match modes for
match-patterns— prefix (IRON_*), suffix (*_IRON_ORE), contains (*_DIAMOND_*) and exact — compiled once per configuration snapshot into anEnumMap/EnumSetmaterial lookup that allocates nothing per item pickup. - Most-restrictive-wins precedence when two tiers claim one material, resolved by requirement dominance and then by document order; an incomparable pair fails startup with an error naming the material, both tiers and the
exclude-materialsline that resolves it. - Folia platform support declaration (
folia-supported: true), without which Folia refuses to enable the plugin. - CI guards rejecting
BukkitScheduler/BukkitRunnableusage, which throws on Folia, and verifying the Folia manifest key is present. - CI
server-smokematrix booting the built jar on real Paper and Folia servers across the supported range. - Full
item-progressionandtrim-progressionconfiguration trees, previously documented but absent from the shipped config. - Granular bypass permissions (
antispeedrun.bypass.gates,.items,.anticheese) and thejourneybookcommand registration. - Configurable outer-End boundary (
outer-end-radius,outer-end-poll-seconds) and exit portal combat lock with an escape valve (exit-portal-lock-during-battle,exit-portal-lock-release-minutes). - Initial project scaffolding and baseline architecture for PaperMC 1.21+ (Java 21).
- Dynamic progression gating engine for Nether and The End portals.
- In-game
/progress(/asr progress) survival milestone tracker card. - Idle / standing-still progression reminder engine with configurable cooldowns.
- Journey Guide Book system (
/asr book). - Multi-dragon boss party scaling with configurable multiplier and rounding modes (
HALF_UP,CEIL,FLOOR). - Anti-cheese protections (blocking Bed/Anchor explosion damage on Ender Dragon and Wither, exit portal crystal placement blocking).
- Standards-compliant CI workflows for DCO, OpenSSF Scorecard, and Standards validation.
Changed
- Item gating is a pure function of
(player advancements, material). The natural-structure provenance system is removed: noItemStackcarries a plugin tag, which makes gear laundering structurally impossible. Decision record indocs/provenance-model.md. gate-natural-structure-chests,gate-player-placed-chestsandgate-armor-standsremoved — each configured a distinction that no longer exists.dropper-can-retrieveanddeath-drop-retrievalcollapse intodrop-recall-enabled.- Mob item pickup is no longer intercepted; piglin bartering and Allay sorters are unaffected.
- Soft-dependency matrix trimmed to Floodgate, the only optional integration the plugin consumes.
- Supported servers are stated as Paper or Folia, Minecraft 1.21.4 through 26.2, and CI now boots the built jar on each end of that range and on 1.21.11 for both platforms, plus a weekly run on the newest Paper and Folia release. Earlier 1.21 releases are no longer implied: the plugin compiles against the 1.21.4 API.
- A
config.ymlthat names an advancement key the server cannot resolve — or a
require-advancementslist written with entries that all name nothing — now stops the plugin at
startup instead of starting it on the shipped defaults. Those defaults declare no gated item
tiers, so the old fallback turned item gating off server-wide over a single typo while every gate
still reported itself armed. A file that cannot be parsed at all is unchanged: it still falls back
to the defaults and the server still starts, because a file that says nothing describes no gating
to enforce. So does a bad key inside a gate that is switched off —enabled: false,
item-progression.enabled: false, orgate-mending-trade: false— which is a warning, because a
gate that is off admits everyone and says so, and a stale key in a section an operator has already
turned off must not stop a server that booted yesterday. A file that parses but whose
gated-itemscannot be turned into a gate table at all now stops the plugin too, for the same
reason a tier collision does./asr reloadis unchanged in every case — the configuration already
running stays live and the plugin is never disabled — and a reload that would not survive a
restart now says so in the log rather than waiting for the restart to say it. - Two remaining ways to switch a gate on and gate on nothing now take that same startup arm rather than passing in silence. An item tier's
require-advancementswritten as a plain value instead of a list used to fall back to the built-in default — which for a tier is no requirement at all — on a warning, so the tier shipped armed and open over a YAML shape mistake that an upper-case letter in the same key was already refused for. Andgate-mending-trade: truebeside a blankrequired-advancementgated the mending trade on nothing with no warning about either half, because each half is legitimate alone. Both now rejectconfig.yml. Two limits, both because refusing a boot over a configuration that is still enforceable would be a false refusal: they are errors only while the gate reading them is switched on — underenabled: false,item-progression.enabled: falseorgate-mending-trade: falsethey stay warnings — and a plain value under a dimension gate stays a warning too, since those ship a non-empty default, so the gate goes on requiring the shipped advancements rather than nothing.
Fixed
- A progression check reached through a stale reference to a player who has already left no longer reads their statistics, first-join time or advancements, none of which any Folia region owns once the player is removed. The check now answers without touching the player, with every requirement unmet, and caches nothing.
- The dimension gate is now enforced on Folia. Its arrival backstop listened for
PlayerChangedWorldEvent, which Folia never fires, so it acted on Paper only; and on Folia 1.21.4 neither portal event fires either, on foot or in a vehicle, so a player riding a boat through a Nether portal, or walking through one, crossed any gate unchecked. The backstop now listens for the player being added to the destination world, which both platforms report, and an ineligible, unwaived arrival is returned to the source world's spawn and told why. Paper still stops the transit at the portal as before, with the backstop as the second line behind it. On Folia a cross-dimension teleport by a command or another plugin is judged by the backstop too, because Folia reports no teleport cause from which to exempt it; the entry below narrows that. - On Folia an operator's cross-dimension
/tpno longer sends an ineligible player straight back. Folia'steleportAsyncfires noPlayerTeleportEvent, so the backstop could not tell a deliberate teleport from a boat through a portal. A vanillatporteleportis now read off the command line when the sender holdsminecraft.command.teleport(andminecraft.command.executewhen it is wrapped inexecute), and the players it names are let into the world it names. The forms read are the four vanilla shapes, bare or underexecutewithin,as,atand the position-only subcommands; a line it cannot read with certainty, including one with a conditional, is judged as before. Another plugin can announce its own teleport withAntiSpeedrunPlugin#expectTeleport(Player, World); a plugin that does not is still judged on Folia. Paper is unchanged. - A refusal no longer depends on its message being sent. The item gate, the Mending trade gate, the vehicle portal check and the arrival backstop (Paper only until the entry above) each told the player why before cancelling the event or scheduling the return, so a message that threw left the event uncancelled and the player through. Each now refuses first and messages after.
- A configuration warning that quotes an operator's value now strips Unicode line and paragraph separators and bidirectional controls as well as control characters, so a value carrying U+2028 cannot split one warning into several console lines and one carrying U+202E cannot reverse the rest of it. The warnings for an unresolvable advancement key, an unknown enum value and a value of the wrong type now quote through the same sanitiser and length bound as the rest.
config.ymlno longer says an item tierhinttakes MiniMessage colour tags. It is checked as MiniMessage at load, but shown as plain text, so a tag in it appears as written.- A player returned to a world's spawn by a dimension gate is no longer set down inside a sealed pocket. The landing search accepted any two blocks of harmless air over solid ground, which is also the shape of an ore pocket in the middle of netherrack, and the spawn column of the Nether is searched over the world's whole height looking for a gap. A spot now counts only when a player standing on it could walk off it: the search steps out over the ground around the candidate, as far as three blocks and two dozen places to stand, and refuses a space that closes in on itself inside those bounds — a single block, a pair of them, or a small room alike. Anything larger is kept, because room to move, dig and light is not the trap this refuses, and neither is a cave a long way from anywhere. Ground the server cannot be asked about from the thread doing the asking counts as walled in rather than being read anyway. When the column offers nothing better the spawn is handed back unchanged, as before. The same rule applies to the retreat behind a portal, which falls back to where the rider already was.
- A
rejection-message, an item tierhintandjourney-book.titleare validated as MiniMessage when the configuration is read, instead of being deserialised on a region thread at the moment they are sent. A template MiniMessage refuses — a legacy section-sign colour code is the one an operator is likely to write — is now reported by a warning at startup and on every/asr reload, naming the key, and the shipped default for that same key is used in its place; for a tierhint, whose default is blank, that means no hint, so the refusal names what is still outstanding. Previously the template threw on every refusal, and becauseItemProgressionListenerand the arrival backstop inProgressionGateListenersent the message before they cancelled or ejected, the throw could let the refused player through. Those paths now refuse first, as the entry above describes. villager-progression.hintis validated as MiniMessage when the configuration is read, like a tierhint. A legacy section-sign colour code in it threw when the Mending refusal was deserialised, before the trade was cancelled, so an ineligible player could buy Mending once per feedback cooldown. It now warns at load and the shipped hint is used; that fallback is not also reported as a stale hint.- A dimension gate whose last outstanding requirement is
require-account-age-daysis now announced. Tenure advances while the player is offline, so the gate was already open by the time they logged back in, was recorded silently by the join prime, and had no advancement and no online watch left to announce it — the player was never told. The set of gates a player has been congratulated on is now persisted in theirPersistentDataContainer, and a join announces the difference. A player with no persisted record is primed silently, so installing this on an established server does not congratulate its whole population at once. DIAMONDis gated.DIAMOND_*has no trailing underscore to match the gem itself, so every tool made from a diamond was gated while the diamond was not.NETHERITE_UPGRADE_SMITHING_TEMPLATEis excluded fromnetherite-tier. It matchesNETHERITE_*but belongs totrim-progression, and without the exclusion two systems claimed one material.antispeedrun.bypassis no longer a child ofantispeedrun.admin. Becauseantispeedrun.admindefaults toop, every operator was silently exempt from every dimension gate, item lock, and anti-cheese rule.- Dimension gate defaults are advancement-driven (
0hours /0days), matching the specification. The shipped config previously required 2h playtime for the Nether and 20h plus a 7-day account age for The End. - The
end-tieritem gate no longer contradicts the End dimension gate; a player could previously pass the gate and still be unable to pick up an Ender Eye. max-single-hit-boss-damagelowered from50.0to12.0. Against the Ender Dragon's 200 HP the previous cap still permitted a four-hit kill.- The root
/antispeedruncommand no longer requiresantispeedrun.admin, which had made/asr progressand/asr bookunreachable for regular players despite both permissions defaulting totrue.