Spigot Ignis

Server-wide earned shadow progression for Social Minecraft. Echo of the Abyss, advancement-name scrambling, The Mask, The Dagger, and The Heart of Shadows are implemented in source; live-server/client verification is pending.

Build

Requires Java 17+ to launch Gradle; the wrapper resolves a Java 25 toolchain for compilation/testing. Toolchain downloads require network access when no compatible cache is available.

./gradlew clean check jar
./gradlew clean jar -PreleaseVersion=0.1.0

Targets Purpur 26.2.build.2618-stable and requires ProtocolLib 5.4.0 for Mask helmet projection. Neither server API nor ProtocolLib is bundled. Source builds do not require the private wiki.

Knowledge and delivery

The canonical project and stories live in the shared private SoMC wiki. There is no repository-local knowledge bundle.

CI/release workflows publish versioned source artifacts; verify their exact run/commit before using any artifact. Release publication requires the repository Actions secret RELEASE_TOKEN, provisioned separately. The release workflow uses a repository-scoped Git credential helper and curl stdin for authentication rather than embedding a release credential in URLs or process arguments. Semantic-release overrides GIT_ASKPASS, so askpass is not used for release authentication. Never enable tracing. GitOps and production release need separate approvals.

Foundation interfaces and state

  • StateService.open(repository) loads on a dedicated persistence worker and completes only when ready. Failed/corrupt loads do not substitute empty state. No caller may use gameplay state before successful initialization.
  • snapshot() provides an immutable, last-successfully-saved view. update(pureChange) serializes mutations, saves first, then publishes/acknowledges. Mutation functions run off-thread and must not access Bukkit or perform gameplay side effects. Marshal downstream gameplay work back to the server thread.
  • configure(invisibilityMillis, pearls, darknessMillis) validates positive thresholds and atomically saves limits with resulting unlocks. Defaults are 43,200,000 ms (12 h), 313 pearls, and 21,600,000 ms (6 h). Lower limits unlock satisfied tracks during evaluation; higher limits do not revoke unlocks. VOID is objective-based and is excluded from numeric threshold grants.
  • UUID state includes per-track progress, unlock/activation flags, invalidation generations, pending rewards, and issued-item receipts. Echo validity requires a matching owner, receipt, and generation; reset removes relevant receipts and advances the generation. Mask/Dagger validity additionally requires the holder's personal unlock and the issuer's matching track receipt/generation.
  • close() rejects new mutations and lets already queued writes drain without blocking the caller. Hard process termination can lose unacknowledged updates. The worker does not force Bukkit/world access from its thread.

Bootstrap reads only plugins/SpigotIgnis/state.bin; it does not read or migrate Stealth data. Listeners, commands, and pending-delivery retries register only after successful asynchronous initialization. Runtime limits have a Java service API and admin commands; there is no hand-edited configuration file.

Echo gameplay

Take uncancelled void damage, ascend while gliding with elytra in the same world, then make the first landing on End Stone. Death, disconnect, teleport/world change, or restarting cancels an unfinished attempt. No datapack advancement ID is required.

The earned Echo Shard is named Echo of the Abyss, with cryptic lore and a UUID-bound receipt. Right-click it to activate permanent obfuscated-name advancement announcements. It does not hide general identity or alter advancement titles/descriptions. Only the bound owner may use it, and activation cannot grant twice. Lost, unconsumed rewards can be earned again by repeating the feat. Admins can also grant a bound Echo.

Full inventories retain a pending reward; online delivery retries every five seconds using the same receipt identity. Temporary inventory/drop/swap locks prevent ordinary moves during the short activation commit. Disk failures do not consume the item on activation; failed completion saves produce a player-facing recovery message rather than pretending the reward was saved.

Announcement handling uses Purpur's Adventure PlayerAdvancementDoneEvent.message, replacing only the first name argument of the three vanilla advancement translations. It preserves silent events and unrelated/custom message formats. Advancement transformation itself needs no ProtocolLib interception; the plugin's ProtocolLib dependency serves Mask equipment projection. Existing Stealth outbound masking sees the obfuscated alias rather than the original name. Live coexistence remains unverified.

The Mask

Only actual invisibility effects from directly drunk potions count toward the default 12-hour threshold. Splash, lingering, arrows, commands, other plugins, and the Mask itself do not count. Cancelled or non-overriding effect changes do not change attribution. Time uses a monotonic clock; refresh/overlap does not double-count. Whole milliseconds persist with sub-millisecond credit retained in memory. Failed saves retain reserved credit for retry rather than silently discarding it.

Checkpoints run every five seconds and on effect end, disconnect, and disable. Offline time never counts. A plugin-owned player-data provenance marker permits an existing directly drunk effect to resume online counting after reconnect/restart; marker restoration and interactions with plugins changing effects while Ignis is disabled still require live validation. A reset discards old-generation buffered credit; future qualifying time can accumulate anew. Administrator time edits flush current credit before changing the counter.

Unlocked players can craft The Mask from eight Netherite Blocks surrounding a Nether Star. The player-head skin is IgnisUmbra10, resolved using the server's asynchronous profile API (20-second timeout, retry every five minutes until ready). There is no blocking profile lookup on the tick thread. Missing/incomplete skins withhold crafting and queued item creation, not ingredients. Previously issued Masks can still be identified/used from their embedded profile without a new lookup.

Crafting requires a durable issuer receipt before an authenticated result becomes available. Vanilla crafting handles ingredient consumption, including shift-crafting; the plugin does not manually escrow/delete those expensive ingredients while waiting for disk. Locked/invalid outputs and automated Crafters are rejected. Recipe-book discovery follows personal unlocks; clients/mods may inspect global recipe definitions, but cannot bypass server crafting permission checks.

A crafter's generation can share a recipe receipt across multiple legitimately paid crafts. Masks are tradable, but a holder must have their own unlock. Validity also requires the issuer's matching receipt/generation: resetting the issuer invalidates their issued copies even when traded away. Resetting a recipient removes that recipient's unlock and invalidates items issued by them; another issuer's still-valid Mask may be used again once the recipient re-earns their own unlock.

Wearing a valid Mask applies managed invisibility. Effect ownership is established only when this plugin's application actually takes effect; external replacements revoke ownership, so unequipping/resetting removes only Ignis's contribution. ProtocolLib projects only the helmet slot to air for eligible invisible wearers and restores equipment presentation when eligibility changes. Other armor and server inventory contents are not mutated by projection. Actual self/other-player rendering and inventory-screen behavior remain manual acceptance checks.

The Dagger

The default unlock is 313 consumed ender-pearl throws, configurable through limits set pearls. A launch event alone does not count: the adapter correlates the pending pearl launch with its decreased item-stack mirror and the subsequent vanilla USE_ITEM notification. Cancelled launches, unsuccessful spawns without that notification, unconsumed/free throws, unrelated statistics, and duplicate notifications earn nothing. Cancelling the statistic update itself does not undo a pearl already consumed. This does not read or import players' vanilla statistic totals. Exact Purpur event ordering and servers/plugins suppressing those notifications remain live-verification requirements.

Counts save off-thread, with in-memory retry of failed credit. Checkpoints wait for reserved writes rather than acknowledging early; reset generations discard stale buffers. Administrative counter edits drain old credits before applying the requested value. Shutdown drains the pearl tracker before closing the persistence worker. Unacknowledged credit cannot be guaranteed through a hard crash or unavailable storage.

The authenticated Ominous Key is named The Dagger. Its unchanged recipe is:

Heart of the Sea Heavy Core Heart of the Sea
Nether Star Ominous Key Nether Star
Heart of the Sea Dragon Egg Heart of the Sea

Dagger crafting uses the same durable issuance policy as the Mask: vanilla retains ingredient consumption, locked/reset outputs and automated Crafters are denied, and pending admin gifts are not used as paid-craft receipts. TradableRewards shares the ledger rules without changing existing Mask PDC identities or schema version. Both issuer and holder need their own unlock; issuer reset invalidates traded copies.

Right-click normally attempts three blocks along the aim direction. A visible player within eight blocks and a three-dimensional 15-degree half-angle cone instead selects a destination 1.5 horizontal blocks behind their facing direction. Closest-to-crosshair selection uses UUID order for ties. Invisible/vanished, dead, and spectator targets are excluded. The user's look orientation is retained. Line-of-sight queries first check that their chunk corridor is loaded.

A valid attempt starts a two-second per-player cooldown, shared across copies, even if unsafe or cancelled. Further attempts do not extend it. A blocked selected destination does not fall back to a different teleport. Item-use vetoes are respected, while vanilla's default denied block interaction with air does not suppress custom item use. Authenticated keys are prevented from being consumed as vanilla vault keys, including revoked copies.

Safety uses the player's current bounding box, a continuous sweep against block collision shapes (including neighboring protruding shapes), a supported exit, world/build boundaries, and conservative liquid/hazard rejection. It does not sample past thin walls or load missing chunks. Work is capped at 32,768 candidate block cells; extreme body sizes/routes can be refused. Unsupported mid-air exits and campfire/hazard surfaces are conservatively refused. Native Player.teleport(..., PLUGIN) preserves the normal protection/cancellation event path and leaves the reusable item intact. Actual terrain, scaled/posed player collisions, and protection-plugin behavior still require live tests.

Commands

All players may use /ignis umbra to privately see the three active item tracks. No passive progress UI is added.

The Heart of Shadows

Earn the personal unlock through six uninterrupted online hours at total light zero (configurable). Uncancelled positive final damage, leaving darkness, disconnecting, or restarting interrupts unfinished progress. Completed unlocks persist until administrative reset. Light observation never loads chunks; unavailable lighting cannot qualify.

Craft a Creaking Heart surrounded orthogonally by four Ominous Bottle V items, with four Lingering Potions of Instant Health II in the corners. The registered recipe uses exact vanilla potion/bottle metadata, and preparation/click checks independently validate the matrix. Automated Crafters are denied; issued Hearts, including revoked copies, cannot be placed as blocks.

Carrying an authenticated Heart requires both the holder's unlock and a valid issuer receipt/generation. It grants Regeneration II at total light zero, or at night in natural skylit worlds. Multiple copies do not stack. Existing stronger regeneration is left alone; weaker regeneration overlaid by Ignis is restored with its remaining server-tick duration on withdrawal. External clears/replacements and cancellation take precedence. Cleanup and native potion-event ordering still require live verification.

Heart counters checkpoint periodically off-thread. Startup durably clears unfinished streaks before enabling gameplay; shutdown drains interruption writes before closing persistence. Administrator progress edits flush and rebase the active interval rather than replaying older elapsed progress.

Administration requires ignis.admin (default: operators), checked independently during execution and autocomplete:

/ignisadmin inspect <online-name|known-UUID>
/ignisadmin limits
/ignisadmin limits set <mask-ms|pearls|darkness-ms> <positive-value>
/ignisadmin progress <set|add> <online-name|known-UUID> <mask|dagger|heart> <nonnegative-value>
/ignisadmin complete <online-name|known-UUID> void
/ignisadmin give <online-name|known-UUID> <echo|mask|dagger|heart>
/ignisadmin reset <online-name|known-UUID> <void|mask|dagger|heart|all>
/ignisadmin reset <online-name|known-UUID> <void|mask|dagger|heart|all> confirm

Reset confirmation must match the same sender, target, and tracks within 30 seconds; confirm alone cannot bypass the initial request. Reset clears the selected progress/unlocks/activation, pending rewards and issuance receipts, and advances generation, making old Echo copies invalid even after re-earning. Other tracks are unchanged by a single-track reset. Echo grants unlock eligibility but still require consumption to activate scrambling; already activated players do not gain another activation. Admin Mask/Dagger grants unlock use and queue authenticated items for inventory-ready delivery; new Masks also require profile resolution. Mask/Dagger delivery retries remember an already offered gift while its acknowledgement save is failing, so trading it away does not cause another offer in the same plugin session. This marker is not durable across process restart; failed-acknowledgement/restart recovery still requires operator review. Admin mutations are audited, not routine private progress reads.

Offline targets use UUIDs already present in Ignis state; online exact names are resolved without network profile lookups. Commands and contextual completion do not claim that unimplemented reward items are available.

Recovery and format

state.bin is a versioned binary data format (not Java object serialization): magic/version, positive thresholds, UUID player maps, named track states, pending rewards, and issuance receipts. Version 2 reads version 1 and carries forward all existing pending rewards as matching issuance receipts; the next successful save writes version 2. Invalid versions, counts, duplicate keys, malformed flags, negative counters, truncated input, and trailing bytes are rejected. Record counts are bounded at 100,000 players and 100,000 pending rewards/issuance receipts per player.

Writes use a same-directory temporary file, flush it to disk, and require atomic replacement. Unsupported atomic moves or write failures retain the prior active snapshot and fail the mutation; there is no non-atomic fallback. Abandoned temporary files are ignored, never auto-promoted. This does not guarantee survival of arbitrary storage hardware or power failures.

If loading fails, preserve the original file for investigation. Under an approved maintenance plan, stop the server, back up current data, restore a verified matching-version backup, and restart. Do not delete state to recover: that loses earned progress and invalidation records. Do not hand-edit the binary file or import another plugin's state. Restoring an old backup can also restore older revocation/progress state; review that consequence before doing it.

Verification limits

JUnit tests exercise real temporary-file persistence, serialized writes, fault injection, threshold policies, receipt/owner/reset rules, challenge transitions, announcement components, command execution/completion, mocked Bukkit boundaries, metadata, and static workflow guardrails. YAML/shell parsing is not remote CI verification.

Live Purpur startup/shutdown, event sequencing, item appearance/consumption, inventory durability across hard crashes, protections, and Stealth/client compatibility still require a manual smoke test. Minecraft inventory persistence and this plugin's state file are not a single atomic transaction: a hard crash between item delivery and Minecraft saving inventory needs operator recovery review. If disconnect/death happens during activation, the durable benefit can commit before the physical item is removed; any retained copy is inert, not another usable unlock. These limitations prevent claiming complete live/crash acceptance or deploying automatically.

Before a later approved production release, test: void damage/elytra/End Stone success and all interruption paths; other players and lookalikes failing activation; full inventory and failed saves; logout/death during activation; reset/re-earn invalidation; all advancement types and hover/click content with Stealth; admin permission/confirmation/offline UUID cases; direct-drink attribution after reconnect/restart and external effect replacement; skin outages; manual/shift/Crafter ingredient protection; tradable Mask issuer/holder resets; own/other-player helmet rendering, inventory interactions, and preservation of external invisibility; real consumed-pearl launch/statistic ordering; Dagger regular/shift/Crafter recipes, stale receipts, vault-key protection, target visibility, thin obstacles, scaled/posed bodies, hazardous/unsupported exits, world/chunk boundaries, cooldowns across copies/disconnect, and cancelled or redirected protection-plugin teleports.

S
Description
Earnable server-wide shadow progression for Social Minecraft
Readme
322 KiB
0 Stars 1 Watchers 0 Forks
v2.1.0
Latest
2026-10-17 03:28:40 +00:00
Languages
Java 99.8%
Shell 0.2%