9 releases, newest first.
com.mysql.cj.jdbc.Driver). The legacy com.mysql.jdbc.Driver shim shipped by Paper printed a [STDERR] deprecation warning every time the plugin enabled with mysql.enabled: true. Behaviour is otherwise unchanged; SQLite installs are unaffected.enchantments key now works. wands.yml has shipped sellwand.item.enchantments: [DURABILITY;1] since the sellwand existed, but nothing read it, so the wand never got its glint. The key is now applied, and every item section in the plugin accepts it: wands, generator drops, shop icons and menu buttons.sellwand:
item:
material: BLAZE_ROD
enchantments:
- DURABILITY;1
flags:
- HIDE_ENCHANTSEntries are NAME;LEVEL. The level is optional and defaults to 1, so a bare MENDING works. Legacy names still resolve, so DURABILITY gives you today's Unbreaking and LOOT_BONUS_BLOCKS gives Fortune. Levels above the vanilla maximum are allowed. Pair the key with the HIDE_ENCHANTS flag for a glint with no extra lore line.
An unknown name or a bad level skips that one entry and leaves the rest of the list alone. Set debug: true in config.yml to see which entry was rejected.
Wands already in a player's inventory are untouched, since a use only rewrites the name and lore. Newly issued wands pick the enchantment up.
item_model. The five wand items in wands.yml (sellwand, adminwand, the free and radius upgrade wands, and buildwand) gained an item_model key that sets the vanilla minecraft:item_model component. Point it at a model your resource pack ships, as namespace:path:sellwand:
item:
material: BLAZE_ROD
item_model: "mypack:sell_wand"A bare sell_wand resolves to the minecraft namespace. Leave it empty to keep the material's default model. Unlike custom-model-data, this needs no numeric predicate in the pack, which is what downloaded model packs expect.
item-model works as well, if you prefer the hyphenated spelling used by the other keys.debug: true in config.yml to see which value was rejected.Requires Minecraft 1.21.4 or newer for the component to render. On older clients the key is simply inert.
Your existing wands.yml gains the new key automatically on the next start, with your values and comments untouched.
Completes the v2.57.1 hotfix of the upgrade API. Implementation and documentation only: API_VERSION stays 1.4.0, no public method, event or constant changed.
v2.57.1 started honouring the withdrawal result, which is correct, but it also made a tier that costs nothing depend on the economy answering a zero withdrawal with SUCCESS. Several Vault providers answer a non-positive amount with FAILURE or NOT_IMPLEMENTED. A free tier, or one whose upgrade-cost key is simply absent and loads as 0.0, was therefore refused with NOT_ENOUGH_MONEY, and only after every per-hop event had already fired. A quoted total of 0 now skips the economy entirely and returns SUCCESS with charged 0.
The charge itself is third-party code: a provider that fires a synchronous balance event hands a listener one more window between the revalidation and the mutation. That reproduced the paid row on an air block that v2.57.1 closed on the other side of the withdrawal. The block is now read once more after the charge and before the generator is registered. If it is no longer the same generator on the same tier, the money is given back and the call refuses with NOT_A_GENERATOR or BUSY, so charged stays 0. A refund the economy rejects leaves a warning in the server log naming the amount and the player.
Location#getWorld() throws IllegalArgumentException("World unloaded") on Paper once the world reference has been cleared, so the previous getWorld() == null guard could never protect anything. upgradeGenerator, upgradeGeneratorTo, simulateUpgrade, simulateUpgradeTo, getGeneratorAt and isGenerator now use Location#isWorldLoaded() and answer normally instead of throwing.
GeneratorUpgradeEvent now says what a listener may assume: a multi-level upgrade fires one event per hop before anything is charged, and an event that was not cancelled is a proposal, not a commit. A later hop may still be cancelled, or the upgrade may still be refused after the events, and no compensating event is fired. simulateUpgrade and simulateUpgradeTo document that a dry run stops before the events, so a simulation never returns CANCELLED. API.md carries the same two points.
No config, message, permission, PDC, database or dependency change. Drop-in over 2.57.1.
Fixes two defects in the upgrade API shipped in v2.57.0. API_VERSION stays
1.4.0: no method, constant or signature changed, so this is a drop-in patch.
The withdrawal result is now honoured. Vault providers refuse a withdrawal by
returning an unsuccessful response rather than by throwing, and that response was
being discarded. A provider that refused the charge left the generator upgraded
anyway, with UpgradeResult.charged() reporting money that never moved. The
result is now checked: a refused withdrawal returns NOT_ENOUGH_MONEY with
nothing charged and nothing changed.
The generator is revalidated after the per-hop events. Those events run
third-party code, and nothing was re-read afterwards. A listener that removed the
generator sent the write down the "new generator" path, which created a paid
database row pointing at an empty block and quietly bypassed the placed-generator
limit; a listener that upgraded the same generator re-entrantly could charge the
actor twice. SnGens now looks the generator up again before any money moves and
refuses with NOT_A_GENERATOR or BUSY if it is gone, changed tier, became
corrupted, or had its scope locked meanwhile.
Also in this release:
player.getLocation() works as well as GeneratorView#location(), for
upgradeGenerator, simulateUpgrade, getGeneratorAt and isGenerator.
Previously only an exact block-corner location matched, because the internal
key carries decimals, yaw and pitch.CORRUPTED, NO_ACCESS and BUSY now report the quoted totalCost(), as the
documentation already promised.failedRequirements() no longer repeats the same message once per tier when
several tiers share one requirement.The NOT_ENOUGH_MONEY and BUSY documentation was widened to cover their new
cases. No configuration, language, permission, PDC or database change.
API_VERSION 1.4.0 (additions only, nothing removed or re-signed).
A consumer building its own upgrade menu can now list the whole upgrade chain, price it, dry-run it and execute it, without reimplementing any of SnGens' upgrade rules.
Reads (synchronous, in memory)
getUpgradePath(String fromGeneratorId) -> List<UpgradeStepView>: every tier
reachable after that type, in chain order, with per-step and cumulative cost.quoteUpgrade(String fromGeneratorId, int levels) -> Optional<UpgradeQuote>:
the price of climbing N tiers, capped at the end of the chain.quoteUpgradeTo(String fromGeneratorId, String targetGeneratorId) -> Optional<UpgradeQuote>:
the price of reaching an exact tier. Empty when the target is not ahead of the origin.Execution (synchronous, world-mutating)
simulateUpgrade(Player, Location, int levels) and
simulateUpgradeTo(Player, Location, String targetGeneratorId): dry runs. Same
checks, same result, nothing charged and nothing changed.upgradeGenerator(Player, Location, int levels) and
upgradeGeneratorTo(Player, Location, String targetGeneratorId): perform the climb.All four return an UpgradeResult carrying a typed UpgradeStatus, the final
tier, the levels climbed, the quoted total, the amount actually charged and the
failed requirement messages.
Semantics
GeneratorUpgradeEvent fires per hop, all of them before any money moves.
Cancelling any hop refuses the whole request with CANCELLED and charges nothing.upgrade-requirements is enforced, not just the
current tier's./gens upgrade does not apply. Access is owner or island mate,
exactly like a shift + right click upgrade.New public types: UpgradeStatus (12 constants), UpgradeStepView,
UpgradeQuote, UpgradeResult.
The existing upgrade paths (hand upgrade, /gens upgrade menu, upgrade wands) are
untouched. No config, language, permission, PDC or database change. Documented in
API.md and on the docs site.
/gens recover. The join notice and the island notice list them on their own line.island-pickup in config.yml now governs generators, Collectors and Hoppers together. Set it to false to destroy them instead of refunding them.island-removal.drop-contents in storages.yml decides what happens to the items held inside those blocks. false (default) voids them with the block, true drops them on the ground as if a player had broken it.sngens_refund table gains hoppers and collectors columns. Existing databases are migrated in place on the first boot, no action needed.API version 1.2.0 to 1.3.0. Additions only, nothing existing changed.
Four new cancellable events:
SellwandPreUseEvent - a sellwand is swung at a block, before SnGens inspects what the block holds.BuildWandUseEvent - a build wand preview is confirmed, before any generator is placed or any money is taken.UpgradeWandUseEvent - an upgrade wand is swung, free or radius.AdminWandSelectEvent - the admin region wand sets a corner.Build and upgrade wands work in one batch, so they never fired the per-generator events. These are now their veto point. Cancelling costs the player nothing: no money withdrawn, no wand use consumed.
One new notification event:
StorageRefundIssuedEvent - Collectors or Hoppers were added to a player's vault. RefundIssuedEvent stays generator only, so existing listeners never see an empty id list.New facade methods on SnGensAPI:
getWand(ItemStack) returns a WandView with the wand type, uses left and its type-specific stats.createSellwand, createBuildWand, createFreeUpgradeWand, createRadiusUpgradeWand and createAdminWand build wand items.Full reference: API.md
The developer API can now list everything a player or island owns, so a companion plugin can build per-player menus without scanning worlds or chunks.
API version 1.1.0 -> 1.2.0. Additions only: nothing existing was removed,
renamed, or changed.
Asynchronous (database-backed, so they include generators in unloaded chunks):
getOwnerGenerators(UUID) -> CompletableFuture<List<GeneratorView>>getIslandGenerators(UUID) -> CompletableFuture<List<GeneratorView>>Synchronous (hoppers and collectors are held in memory in full, so these need no database read at all):
getOwnerHoppers(UUID) / getIslandHoppers(UUID)getOwnerCollectors(UUID) / getIslandCollectors(UUID)getPlacedCollectorCount(UUID) / getTotalPlacedCollectors()The last two close a gap: collectors previously had no query surface at all, while hoppers already had their counters.
Island-scoped methods fold in every SuperiorSkyblock2 member, and fall back to the single owner when the plugin is absent or the player has no island. An island is resolved in one indexed query rather than one per member.
HopperView - id, owner, location, maxTypes, slotsUsed, totalItems,
contents, estimatedValue, createdAt, updatedAtCollectorView - the same, without the hopper-only slot fieldsStoredItemView - one stored stack: key, item, amount, unitValue,
totalValue. It carries the already decoded ItemStack, so a menu can render
it directly.GeneratorView#corrupted()
to tell them apart.CompletableFuture results complete on an async thread. Hop back to the main
thread before touching the Bukkit API.No configuration, message, permission, PDC, or database schema change. Drop-in.
Full reference: API.md in the jar's repository, and the online API docs.
Armor and off-hand bonuses now survive plugins that force-equip their own gear.
Some plugins swap their own items into the armor and off-hand slots when a player enters a given world, and hold the player's real gear until they leave. SnGens read the slot as "no SnGens piece", the 4/4 full-set rule collapsed, and the player silently lost the bonus plus got the broken message.
A slot held by such a plugin is now recognised as borrowed, and the last set / off-hand the player genuinely equipped keeps applying. The memory lives on the player's own persistent data, so it survives a relog and a full server restart. Taking your own gear off with no external plugin involved still breaks the set exactly as before, so this cannot be farmed.
external-equipment in config.yml| Key | Default | Role |
|---|---|---|
enabled | true | Master switch. false restores the previous behaviour exactly. |
markers | two namespaces | Persistent-data namespaces identifying the other plugin's items. |
worlds | [] (all) | World filter. Use alone (empty markers) if the other plugin writes no persistent data. |
settle-delay-ticks | 40 | Grace window after join / world change so the other plugin can equip first. |
With markers and worlds both empty the feature disables itself and warns, since accepting any foreign item in any world would be exploitable.
Finding the right marker: set debug: true and walk into the affected world. SnGens logs the offending item's material and every persistent-data key it carries, so you can confirm or correct the markers list in one step. If it carries no keys at all, use the worlds gate instead.
PlayerChangedWorldEvent handler so the bonus follows both directions of the world change./gens reload now recomputes each player on the thread that owns them instead of the command thread.New keys arrive through the update-configs auto-merge, so upgrading needs no manual config edit.
Full documentation for SnGens is now published in this repo, under docs/gitbook/sngens/: overview, installation, commands, permissions, configuration, placeholders, developer API and FAQ.