Spigot Ignis

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

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. Future tradable-item provenance remains a later design decision.
  • 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.

Commands

All players may use /ignis umbra to privately see the three item tracks. The Mask counter is active; Dagger/Heart natural counters and items remain unimplemented, though administrator-seeded values can be inspected. No passive progress UI is added.

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>
/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 grants unlock use and queue the authenticated item for delivery after profile resolution/inventory availability. 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.

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%