12 KiB
Spigot Heights
A Purpur 26.2 plugin adding craftable player-scaling potions and dispenser launch tubes for tiny players.
Approved behavior is specified in the SoMC OKF wiki.
Requirements
- Purpur 26.2 build 2618
- Java 25 or newer
Potions
Potion of Shifting Stature
Amethyst Shard | Chorus Fruit | Amethyst Shard
Amethyst Shard | Awkward Potion | Amethyst Shard
Amethyst Shard | Chorus Fruit | Amethyst Shard
Drinking it chooses a random configured scale in 0.1 increments.
Potion of Growth
Gold Ingot | Amethyst Shard | Gold Ingot
Amethyst | Potion of Shifting Stature | Amethyst
Gold Ingot | Rabbit's Foot | Gold Ingot
Potion of Diminution
Use the Growth recipe with a Fermented Spider Eye instead of the Rabbit's Foot. Growth and Diminution adjust scale by one configured step and clamp at the limits.
Potion of Restoration
Gold Ingot | Amethyst Shard | Gold Ingot
Amethyst Shard | Potion of Shifting Stature | Amethyst Shard
Gold Ingot | Sugar | Gold Ingot
Produces one Potion of Restoration. Drinking it restores scale to exactly 1.0 (normal size), even outside configured limits. This size is saved across reconnects, respawns, and server restarts. Other permanent saved scales are still clamped to the configured range.
Temporary splash and lingering variants
Brew any of the four authenticated stature potions with gunpowder to make its splash variant; brew that with dragon's breath to make its lingering variant. Brewing preserves the stature kind, not arbitrary cosmetic renames/lore. Ordinary potions and cancelled brewing/application events are not overridden. Renaming an ordinary item cannot turn it into a stature potion.
Each splash/cloud applies once per player, without recipient opt-in. The first application saves the player's original base scale. Further applications operate on the temporary scale and reset expiry to five minutes after the latest application, including a valid application already at a stature bound. Growth/shrinking respect the current limits; throwable Restoration sets temporary scale to exactly 1.0.
Example: 1.0 → 0.8, then 0.8 → 0.6 four minutes later, returns to 1.0 five minutes after the second application. Expiry restores the exact original baseline, even if administrator limits changed meanwhile. Offline time counts. Baseline, expiry, and application receipts are persisted; a pending restoration survives disconnect during persistence and is acknowledged only after live application. After that sequence is cleared, ordinary later joins/respawns use the usual limit policy.
Drinking a permanent stature potion ends the temporary sequence, using the current temporary scale (or restored baseline if already expired). Administrator height changes also end the sequence. File writes are serialized off the server tick thread; failed saves do not apply an unsaved change. Disable waits for the active writer and drains its result before returning. Do not forcibly kill the server while it is saving.
Heights tracks only its loaded clouds and checks them every five ticks, dispatching AreaEffectCloudApplyEvent and honoring cancellation and recipient filtering. This supports effectless stature clouds without adding a vanilla status effect. Online expiry is checked every second. Durable receipts survive cloud unload and are retired on observed permanent entity removal; abrupt process termination can leave harmless orphan receipts.
Heights corrects Purpur 2618's no-op conversion of custom potions without a vanilla base potion type inside BrewEvent. This covers existing items without changing metadata or crafting recipes. It detaches aliased results at LOWEST priority before normal result modifiers and validates/corrects at HIGHEST. Conflicting/missing results or item-construction failures cancel the brew; valid converted results and native ingredient/fuel accounting are retained. Cancellation remains authoritative. As with other Bukkit listeners, this cannot control plugins that directly mutate inventories or run conflicting handlers outside the normal event-priority contract.
Verification: nativeBrewingTest executes real Purpur 2618 brewing cycles with native inventories, recipes, metadata and Paper event dispatch. It covers all four splash/lingering conversions, ingredient safety and cancellation, mixed/ordinary potions, and output compatibility with the stature adapters. World and entity boundaries are test doubles; these checks do not claim native projectile/cloud spawning, client appearance, or live logout/restart gameplay. Those remain supplementary live checks.
Ride larger players (US-010)
Right-click a larger player with your main hand to mount immediately. Sneaking and consent are not required. Only the smaller rider may initiate: larger players cannot force pickup of smaller players. Either participant can end the ride, without administrator permission or rider approval:
/heights carry stop
There are no consent requests, expiry timers, or accept/decline commands. Settings and player-height
commands still require spigotheights.admin; console cannot carry players. Ride tracking is not
persisted. There is no remount cooldown or opt-out: a smaller player can mount again after ejection.
Both players must be available and within 4 blocks, in the same world. Their effective scale
difference, including attribute modifiers, must meet carry.minimum-scale-gap (default 0.1,
independent of potion adjustment sizes). Zero still requires a strictly larger carrier. Neither
may already have a vehicle or passengers. Heights permits only one rider per carrier and blocks
additional/nested mounts involving an active pair. Ineligible player clicks remain uncancelled for
other plugins; only eligible carry attempts and their same-tick duplicates are consumed.
The rider can sneak-dismount normally; either participant can use the stop command. Heights calls only the rider's native leaveVehicle() when commanded, leaving placement, falling, damage, and environmental hazards to Minecraft. There is no safe-ground requirement or search, rescue teleport, saved-position/spawn fallback, motion/fall-distance reset, invulnerability, or recovery system. Getting off above a drop is a gameplay risk.
Eligibility is rechecked every five ticks, including temporary potion application/expiry, administrator edits and external scale modifiers. Death, disconnect, plugin disable and loss of eligibility attempt native cleanup. For observed teleport events, Heights detaches the pair and lets the original teleport continue, without replaying or redirecting it. Native carrier teleports that refuse passengers before emitting an event may still require /heights carry stop first.
Server/plugin cancellation remains authoritative: a refused dismount leaves accurate tracking and is reported, not bypassed or called successful. A cancelled voluntary stop can be retried by either player; automatic eligibility cleanup continues checking and reports refusal once per ride rather than spamming. Lifecycle/disable refusals are logged. Native dismount events are reconciled after dispatch so a later cancellation is honored. An unrelated vehicle is never ejected during stale-pair cleanup.
Verification: API/domain tests preserve native cleanup and reject rescue/motion-reset calls.
NativeCarryRoutingTest registers the production listener through Purpur 2618's actual Paper
plugin manager and dispatches both PlayerInteractEntityEvent and PlayerInteractAtEntityEvent.
Same-tick duplicate rider/target interactions are coalesced, including refused mounts; a fresh
interaction next tick can retry without a gameplay cooldown. Registered mount/dismount cancellation
and nonadmin stop routing are also covered. Server/player boundaries are doubled; this is not
client-packet generation or live-server evidence. Supplementary Purpur checks remain for empty/occupied
hands, scaled rider positioning and movement, native placement/falling, cancellation interoperability,
both-role death/disconnect, portal/cross-world teleports, disable and potion/admin/expiry eligibility
changes. No throwing, added launch force, player items, storage, opt-out or rescue behavior is included.
Tiny-player launchers
A player below the configured scale threshold can walk onto a hopper whose output points into a dispenser. If the block in front of that dispenser is passable, the player is moved there and launched in the direction the dispenser faces. The launcher does not require redstone.
Configuration
Defaults are in src/main/resources/config.yml:
height:
minimum: 0.4
maximum: 2.0
adjustment-step: 0.1
carry:
minimum-scale-gap: 0.1
launcher:
maximum-player-scale-exclusive: 0.5
speed: 1.5
cooldown-ticks: 20
carry.minimum-scale-gap is independent of potion height.adjustment-step and defaults to 0.1,
including existing files without the key. It must be a finite, nonnegative number; invalid values
prevent plugin enable. Zero permits any strictly positive scale difference, not equal-size riding.
Edit this setting in the file and restart; min/max commands preserve it.
Player scales are stored by UUID in plugins/SpigotHeights/state.yml.
Commands
Operators (or senders granted spigotheights.admin) can use:
/heights settings
/heights set min 0.2
/heights set max 3.0
/heights player Steve
/heights player Steve 0.5
Console is supported. Tab completion suggests subcommands, bound names, and valid example values; other valid numbers may also be entered.
Changes are saved to plugins/SpigotHeights/config.yml and take effect without a restart. Minimum must be at least 0.0625, maximum at most 16.0, minimum cannot exceed maximum, and the launcher threshold must remain within the range. Invalid changes or save failures leave active settings unchanged.
Players are not immediately resized. Subsequent potion use, joins, and respawns use the new limits; Restoration still returns and preserves exact scale 1.0. Processing an expired temporary sequence is another exception: its exact baseline is restored before ordinary later joins/respawns follow the new limits. The settings commands only change min/max; edit other settings in the configuration file and restart the server.
Player height
/heights player <name> reports an online player's scale (1.0 = normal size). If another plugin adds scale modifiers, both the base scale and effective scale are shown.
/heights player <name> <scale> sets that player's base scale by UUID after an asynchronous durable save and ends any temporary sequence. Values must be finite and within current min/max limits, except 1.0 is always allowed. Saved scales follow the usual limits on reconnect and respawn if configuration changes later. A failed save leaves the player's height unchanged.
Player names must match an online player exactly (case-insensitive); partial names and offline players are rejected. Tab completion suggests online names and valid example sizes. Operators, console, and senders with spigotheights.admin may view or set heights, including their own.
Build
./gradlew clean check jar
The plugin JAR is written to build/libs/.
check includes nativeBrewingTest. It downloads the exact Purpur 26.2 build 2618 launcher (SHA-256 verified) and prepares its runtime dependencies with Paperclip's patch-only mode under build/brewing-runtime/. It does not start a server, open ports, create a world or accept the EULA. Initial/clean builds need network access; the native test JVM permits up to 1 GiB heap. No runtime implementation classes or test dependencies are packaged in the plugin. Run ./gradlew test for the fast API/domain suite alone, or ./gradlew nativeBrewingTest for native brewing and registered carry-event routing tests. Full verification remains ./gradlew clean check jar.
Releases
Gitea Actions checks pushes and pull requests and stores a development JAR. Pull requests validate conventional commits. Main-branch conventional commits drive semantic releases when the repository defines a RELEASE_TOKEN with contents-write permission.