33 releases, newest first.
If your network runs Geyser and Floodgate, your Bedrock players saw nothing where a pet should be, while Java players saw the model perfectly. This release fixes that.
Two independent causes, one answer.
Every pet BODY this plugin draws is a Display entity: the head backend is an ITEM_DISPLAY, and a
model pet's bones ride an invisible ITEM_DISPLAY carrier. Geyser has no Bedrock definition for
ITEM_DISPLAY, so it discards the spawn in silence. The pet's name plate is a TEXT_DISPLAY,
which Geyser does translate, so what a Bedrock player actually received was the pet's name frozen
in mid-air at the spot it first appeared, with no pet under it and nothing moving it.
Separately, models are drawn through BetterModel's DummyTracker, and the Bedrock content bridge
only knows EntityTracker, so it never learns the pet exists at all.
A Bedrock viewer is sent a substitute their client can actually render: an armor stand wearing the pet's own head texture, following the pet by exactly the same movement arithmetic, with the pet's name plate riding it.
Java players are completely unaffected. They receive the same packets they received before, byte for byte. And on a server without Floodgate, nothing changes at all.
bedrock: config bandIt is added to your config.yml automatically on the first boot; your existing values are kept.
| Key | Default | What it does |
|---|---|---|
enabled | true | Off restores the previous behaviour exactly |
scale | 1.0 | Multiplied by the pet's own model.scale, so pets/*.yml needs no changes |
height-offset | -0.7 | Normally negative: an armor stand's head sits above its feet |
small | true | A small stand; off roughly doubles the height, so double the offset too |
invisible | true | Hides the stand's body, leaving only the head it wears |
mount-label | true | Lets the pet's name plate ride the substitute |
If a Bedrock player reports seeing nothing, flip invisible to false first: some Bedrock versions
hide an invisible entity's equipment along with its body, which would hide the very head the
substitute exists to show. Every key takes effect with /pets reload, no restart needed.
minecraft:scale attribute, which only exists on clients 1.20.5
and newer. Below that the substitute renders at its natural size.pets/, lang/ or guis/. The developer API
is untouched and API_VERSION stays 1.0.0.lang/messages_es.yml on the first boot, because SnLib only
auto-seeds the English file and the Spanish one has to be on disk before the
lang module loads. That call was made unconditionally, and Bukkit's
saveResource answers a destination that already exists with its own warning
rather than an exception, so from the second startup on the console showed
Could not save messages_es.yml to plugins/SnPets/lang/messages_es.yml because messages_es.yml already exists.replace = false).No configuration, command, permission or API change. Upgrading is a jar swap.
Pets no longer go invisible when you teleport a long way inside ONE world.
1.24.1 fixed this for a world change. It turned out to be the wrong trigger: the cause is a
DISTANCE, not a world. Teleporting inside a single world - /tp 100 0 100 to
1000000 0 1000000, a /home, an island warp - broke exactly the same way, and nothing was
watching for it.
A client throws an entity away when it unloads the chunk that entity lives in. An owner who teleports further than the server sends them chunks leaves every pet behind as something their client has already forgotten, and from then on the plugin is moving pets that no longer exist: the move packets are dropped in silence, so the server believes the pets are placed and visible while the player sees nothing. Since the packet that attaches a model's parts to the invisible carrier they ride is only ever sent when a pet is CREATED, they could never come back on their own.
The animation pass now measures how far each owner moved, in chunks, against the distance their own client is still being sent chunks for, and rebuilds the whole formation where they landed whenever they outran it. It reacts to the movement itself rather than to a teleport event, so it does not care how the player got there: a command, a plugin, a portal, an island warp, a vehicle or a respawn all look the same.
Also fixed: someone already standing where you land now sees your pets in the right place. Before, they were sent your pets at the coordinates you had just left.
Nothing to configure and nothing to migrate: the distance is read from your own view-distance
and from each player's render distance, so it can never drift out of step with your server.
Ordinary walking, sprinting and elytra flight never trigger a rebuild.
/pets reload path already did for every formation on the server.Drop-in. No config, lang, command, permission or database change, and nothing to migrate.
A pet is now created at level 0 instead of level 1. Every path that mints one starts it unlevelled: a box, a fusion result, and /pets admin give with no [level] token.
Every pet already on your server keeps its level, and a level is worth exactly what it was worth in 1.23.0. This release moves where a pet starts, never what a level is worth. The buff, EdTools and buff-display ramps are untouched, so a pet written with initial equal to per-level is still worth exactly level x per-level at every level - now including level 0, where it is worth nothing.
experience.base is now the cost of leaving the first level, and level 0 and level 1 share that one requirement. A pet's lifetime therefore gains one extra rung.
This is deliberate and it is not cosmetic. A plain downward extension of the curve would ask for base - per-level, and a requirement of 0 is the sentinel that means "this pet does not progress". The shipped ember_fox.yml declares base: 500 with per-level: 500, so the extension lands on exactly 0 and every fresh copy of that pet would sit at level 0 forever with nothing in its file to blame. Any curve whose per-level reaches its base has that shape.
/pets admin setlevel <player> <instance> 0 is now accepted, so a pet can be put back to fresh./pets admin givescroll's [levels] keeps its floor of 1. It is a count of levels a scroll is worth, not a position on the ladder, and a scroll worth 0 levels would be an item that silently does nothing.No key was added, removed or renamed. Only comment text changed, so an existing server sees no config.yml or pets/ edit at all.
20,266 assertions against the compiled classes, with 1.23.0's own progression math loaded in an isolated classloader as a negative control. 172,380 sweep comparisons confirm nothing above the starting level moved, and the control fires on 20,100 curve shapes that 1.23.0 would have stranded at level 0.
buff-display: a pet whose effect lives in another plugin can say what to show. A pet that declares no buff: and no edtools-boosts: because ANOTHER plugin reads it through the SnPets API and pays a boost of its own for it (SnBattlePass and its Pase_ family) used to render as Damage 0.0% on every menu, item and hologram. The new optional buff-display: block in pets/<id>.yml - a name plus the edtools-boosts entry shape (initial, per-level, optional max) - is what {buff} and {buff-value} show for such a pet. SnPets never applies it.buff: keeps showing that, a pet whose effect is its edtools-boosts: block keeps showing that (1.20.0), and only a pet with neither reads this block - so no pet that showed something before shows anything different now.pets/ is seed only: the commented example ships in ember_fox.yml for a new install; add the block to existing pet files by hand.slots.base-count / storage.base-capacity), the rank permission (snpets.slots.<n> / snpets.storage.<n>) and the purchased value - so with base-count: 1, selling a player their first slot changed nothing: /pets admin slots give <player> 1 reported "1 in total". Since 1.22.0 the effective value is max(base, permission) + purchased: the permission still names the absolute count a rank grants (it replaces the base when higher), and every slot or unit of storage sold stacks on top of that floor. With base-count: 1, buying one slot now means two equipped pets.slots.max-count / storage.max-capacity cap the effective TOTAL a purchase may reach, not the purchased half alone - so the shipped max-count: 7 still means "no more slots than the 7 cells the menu draws". A command past the ceiling is still trimmed and reported, never cancelled, and rank permissions still ignore the ceilings./pets admin info.config.yml and lang comments were reworded (fresh installs only).stat-spinning template in guis/boosts.yml ({stat}, {candidate}, {group-color}); the old spinning template on the pet cell is retired.roll-stat template splits into roll-stat-experience, roll-stat-level and roll-stat-buff, so each roll button is styled and worded on its own. roll-stat-locked stays shared, like roll-all-locked.templates.spinning and templates.roll-stat stay in the file but are no longer read - delete them whenever you like. The traits menu is untouched.{buff} and {buff-value} now resolve a pet's edtools-boosts. In every place a pet is
described - the menus, an extracted pet item, a hologram line - the two placeholders name the
pet's EFFECT. A pet that declares a vanilla buff: shows exactly what it showed before. A pet
whose buff grants nothing and whose effect is its edtools-boosts block resolves them from that
block instead: {buff} becomes the boosted currencies joined in file order, and {buff-value}
the live value at the pet's current level, with the boost grade and trait widening and the
per-entry max: already applied. A pet boosting several currencies at different values shows
the highest one. The trait's flat effects.edtools points are deliberately NOT included - they
are the trait's effect and the trait's own lines already show them.ProgressionMath.edToolsEntry and the booster engine now consumes the same helper the
placeholders do (a 144,000-comparison sweep proves the extraction is byte-identical to the
1.15.0-proved inline code).messages.edtools-currency-<id>
entry per currency you want renamed (e.g. edtools-currency-money: "&6Money"); the separator
between two names is menus.edtools-buff-separator. Only enchant ships named - every other id
is invented on your server - and an id without an entry shows as the raw id. The trait index's
currency lines resolve through the same entries, so one currency can never carry two names.The level and rarity scrolls are applied now. 1.18.0 shipped all three scrolls but only the ownership one did anything. Both of the others are used the same way: hold one and click a pet cell of the main pets menu - an equipped slot marker or a stored pet - and it is applied to that pet and consumed, one per use.
pets/<id>.yml names under upgrades-to, keeping its level, experience, trait, boosts and obtained date.guis/main.yml gained player-inventory: open: your own inventory stays usable while the pets menu is up, which is what lets you pick a scroll up at all. Every click on the menu's own cells is still cancelled, so nothing in the menu can be taken out of it. Two side effects, both good: the ownership scroll now works with the menu open, and splitting a stack in your own inventory works again while a menu is up.
Set player-inventory: locked in guis/main.yml to get the old behaviour back; the two menu scrolls stop working with it.
player-inventory, the storage-drop-target item and input: true on slot-filled in guis/main.yml, plus seven messages.scroll-* lines in both locales. Your own values and comments are kept.storage-drop-target item: it never renders, and it is the only thing that makes the storage grid accept a scroll.config.yml gained no keys. No command, permission or schema change.api/ classes are byte-identical to 1.18.0 and API_VERSION stays 1.0.0.Three pet scrolls, and the ownership scroll works now.
A new managed scrolls: band in config.yml declares three consumable items, handed out with
/pets admin givescroll <player> <scroll> [amount] [levels] (permission
snpets.admin.givescroll). A scroll is a stamped ITEM, not a balance: it sits in an inventory,
drops, and can be traded or sold like any other item.
ownership - works in 1.18.0. Pick the scroll up on your cursor and left or right click
a pet ITEM sitting in your own inventory: the head becomes yours, its owner tags are rewritten,
its {owner} lore line is repainted with your name, and one scroll is consumed. This is the way
out of the 1.17.0 owner lock, which made a freshly extracted head unredeemable by anyone but the
player who took it out.level and rarity - defined, given and stamped, but not yet applicable. Their
items exist, their lore renders, and a level scroll carries its [levels] value in its PDC, so
scrolls of different sizes circulate side by side and lowering default-levels later never
devalues one already handed out. Nothing consumes them yet; applying them to a pet lands in a
later version.Each scroll declares which pets it accepts: whitelist first (an EMPTY list means every pet), then
blacklist. Neither is validated on load, so an id naming no pet simply never matches.
The ownership scroll refuses, and consumes nothing, when the head already names you, when the slot
holds more than one head, when the pet is outside that scroll's lists, when the pet's
pets/<id>.yml is gone, when pet-items.enabled is off, or when you are in creative and
pet-items.allow-creative is off. Dropping a scroll onto anything that is not a pet item does
nothing at all - the click is the ordinary inventory swap it looks like.
upgrades-to in pets/<id>.yml names the pet a rarity scroll will turn that pet into. It is
NOT the same ladder as fusion.into - a fusion eats two pets and can fail, an upgrade eats one
scroll and cannot - so a pet may declare either, both, or neither. It parses in 1.18.0 and nothing
reads it yet.
config.yml is managed: the whole scrolls: band merges in on the first boot, with its
comments, and your existing values are untouched. Verified against real 1.17.0 files.lang/messages_en.yml and lang/messages_es.yml receive 13 new keys the same way.pets/ is seedOnly: upgrades-to does NOT arrive on an existing install. Add the key to
your own pet files by hand. Only a fresh install gets the commented example now shipped in
pets/ember_fox.yml.traits banner in
config.yml now says a trait grants up to four things rather than three (it has been four since
1.16.0); existing installs keep the old wording.ScrollManager registers the three items and mints them; it registers no redeemable on purpose,
because a right-click dispatch would consume a scroll in mid-air.ScrollListener is the plugin's first InventoryClickEvent. It cancels the event before
touching either stack, in the same event and on the main thread, so the click cannot resolve
underneath the mutation.api/ class files are byte-identical to 1.17.0 and
API_VERSION stays 1.0.0.A pet item now remembers who took it out.
Full backward compatibility. Every pet item already in circulation carries no owner tag, and an item with no owner tag stays redeemable by whoever holds it - exactly the behaviour it was traded under. The lock can only ever affect a pet extracted from 1.17.0 onwards.
{owner} is now a pet placeholder everywhere. It used to work only above a pet's head; it now resolves in the pet item's lore and in the pet cells of all seven menus, from the same resolver the refusal message uses - so an item's lore and the message that turns somebody away from it can never name two different people.
messages.pet-item-not-yours (en + es) - auto-merged into every existing install on the next boot.config.yml is managed, so the new &7Owner&8: &f{owner} lore line and the new comments reach fresh installs only. To show the owner on an existing server, add that line to pet-items.item.lore by hand, or delete the lore list and let the merge write the shipped one back.API_VERSION stays 1.0.0.traits.yml can now carry an optional effects.edtools block: FLAT percentage points per EdTools currency, or per enchant alias (enchants / enchant / global-enchants / encantamientos) for the GLOBAL enchant multiplier.edtools-boosts block of its own. That is the whole reason the effect exists: a pet needs no EdTools block to pay its trait.max: (that ceiling bounds the entry that declared it and nothing else).menus.trait-effect-edtools and menus.trait-effect-edtools-enchant, merge into existing language files on boot.api/ class files are byte-identical to 1.15.0 and API_VERSION stays 1.0.0.studious:
weight: 30.0
effects:
exp-percent: 15.0
edtools:
essence: 15.0
enchants: 10.0edtools-boosts entry in pets/<id>.yml can now declare an optional max: a ceiling in percentage points for THAT pet.0 or negative all mean "no ceiling", so every pet file written before this grants exactly what it granted. A negative value warns once, naming the pet and the entry.per-level: 0.4 is +300% at level 750.pets/ is seedOnly, so the new key reaches fresh installs through the commented example in pets/ember_fox.yml; an existing server adds it by hand.API_VERSION stays 1.0.0.New experience source: EDTOOLS_BLOCK_BREAK. EdTools consumes the blocks its omnitools break without ever firing a vanilla BlockBreakEvent, so BLOCK_BREAK never saw them and a farming server had no source for its main activity. A pet type can now name EDTOOLS_BLOCK_BREAK in its experience.source and level from EdTools mining and harvesting.
experience.sources.edtools-block-break (default true) joins the experience.sources band of config.yml. It auto-merges into an existing install; your edits and comments are kept.experience.tools list of EdTools tool ids (crop-tool, mining-tool, whatever your EdTools names them), matched case-insensitively, on top of the experience.materials whitelist it already had. Left out, every omnitool counts.es.edwardbelt class is ever loaded, and a pet on this source simply earns nothing. Install or reload EdTools later and the source picks it up on its own.Performance. This is the busiest event a farming server produces - thousands a second with bulk enchants - and EdTools fires it off the main thread. The handler does one cancel check and one counter increment; a single shared task pays the totals out coalesced per (player, block, tool) every 5 ticks. Never a task per block, never a one-tick timer.
pets/<id>.yml is seed only, so source: EDTOOLS_BLOCK_BREAK and tools are added to existing pet files by hand; the commented example ships in pets/ember_fox.yml for new installs. No command, permission or language change. The public API is untouched and API_VERSION stays 1.0.0.
Equipped pets can now grant EdTools boosters. A pet declares an edtools-boosts block in its
pets/<id>.yml and, while it is equipped, its owner gets an EdTools booster for each currency the
block names, plus the global enchant multiplier. EdTools is a soft dependency: without it
installed no es.edwardbelt class is ever loaded and the plugin behaves exactly as it did before.
edtools-boosts:
money:
initial: 10.0 # percent granted at level 1
per-level: 1.0 # percent added by each level above 1
enchants:
initial: 10.0enchants, enchant, global-enchants,
encantamientos for the global enchant multiplier.ProgressionMath.petBuff
the buff engine uses, so the two can never drift apart.duration = 0) and never persisted by EdTools: they last
exactly as long as the pet stays equipped. They are written on join, rewritten whenever the
equipped set changes or a pet levels up, rolls a trait or grade, or is edited by an admin, and
removed on unequip, on quit and when the plugin disables. Because nothing is persisted, a crash
cannot leave one behind.snpets-
booster that is no longer wanted, so a half-applied write self-corrects.edtools band in config.yml| Key | Default | Meaning |
|---|---|---|
edtools.enabled | true | Master switch. Turning it off and reloading removes the boosters already granted; it does not wait for a restart |
edtools.booster-display-name | "&dPets" | Name EdTools shows for this plugin's boosters |
edtools.join-delay-ticks | 20 | Ticks after a join before the boosters are written, so EdTools restores its own state first |
config.yml is managed, so the band arrives on the next boot with your values and comments intact.
pets/<id>.yml is seed only. The commented example ships in pets/ember_fox.yml for a new
install, but the pet files on a server you already run are never merged again, so you add
edtools-boosts to them by hand.Nothing else changed: no command, no permission, no database schema and no public API change
(API_VERSION stays 1.0.0).
(57/54), (62/54), (67/54). The storage itself was never overfilled
(that was fixed in 1.8.2), but a used count quoted above the capacity reads as the capacity being
bypassed rather than enforced.insertPet
queues its cache write before completing its future, while the reservation is released one task
later, so a click processed in between saw the same pet in both. The ordering is deliberate and
stays as it is; only the number the message quotes was wrong.max(stored, capacity). A booking can raise the quoted number
up to the capacity and never past it, while a storage that is genuinely over its limit (107/54
from a historic overfill, an admin grant or a lowered capacity) still shows the real number.No configuration, command, permission or database change. Public API unchanged (API_VERSION 1.0.0).
The roulette animation is now each player's own setting, and it persists.
The traits and boosts menus gained a button at slot 8: Roll Animation: On plays the spin,
Roll Animation: Off shows the result at once, with the same reveal sound, the same chat line and
the same redraw. The choice is saved on the player's own row, so it survives a relog and a restart.
animation block in boxes.yml, and a
server that switched a roulette off entirely (traits.roll.enabled / boosts.roll.enabled)
still reveals silently for everyone, exactly as before.guis/traits.yml and guis/boosts.yml are managed, so the two new templates (anim-on and
anim-off) merge into an existing install on their own, comments included. They carry
slots: [8] rather than a letter of the layout:, so they land in the right cell even on an
install whose layout you edited before this button existed.snpets_players gains one column, roll_animation. This is the plugin's first schema
migration: the ALTER TABLE runs once, right after the tables are created and before any
player row is read, on SQLite and MySQL alike. Every player who already has a row keeps the
animation ON. A boot that finds the column already there does nothing at all.One leftover worth knowing about: the shipped spinning template lost its click-actions and its
&eClick to skip the animation lore line, but those are VALUE changes and the auto-merge never
overwrites a value you already have. An existing install keeps showing that line, and clicking the
spinning cell now does nothing - deliberately, since [pets-skip-spin] stays registered as a no-op
for this one release rather than warning on every click. Delete the line and the click-actions:
block from your templates.spinning whenever you like; the tag itself goes in the next minor.
Two new language keys, messages.roll-animation-on and messages.roll-animation-off, arrive in
lang/messages_en.yml and lang/messages_es.yml the same way.
No API change: API_VERSION stays 1.0.0. No new command or permission.
YamlUpdater.merge and Bukkit's own parser: both templates and
both lang keys reach an untouched AND an owner-edited install, with their comment blocks, in the
right section, readable back; the merge is idempotent; and the removals above really do stay on
disk, which is why they are documented rather than assumed.pet-items.enabled: false still switches the whole feature off.strict-clicks stays true, so every other key over the menu is still inert: declaring the new list is precisely what lets Q through the gate, and only on a stored pet.This release consumes the drop key that SnLib 1.31.0 added to the GUI click matrix. SnApi.LEVEL did not change, so the plugin's API handshake cannot enforce it: on an older SnLib the plugin still enables, the new key is simply ignored and Q does nothing. Update SnLib to get the feature.
guis/main.yml is managed, so the new templates.pet-entry.drop-click-actions key merges in on the next boot and Q works immediately. Verified out of game against the real merge, on an untouched install and on one with owner edits.
The lore line that ADVERTISES the key is a value inside an existing list, and a merge never edits a value you already have, so it does not arrive. Add it by hand under templates.pet-entry.lore if you want it visible:
- "&e&lSHIFT + RIGHT CLICK"
- "&e&lQ (DROP)"
- "&6Take this pet out as an item you can trade"Proving the merge turned up something that was already wrong before this release: the README has advertised, since 1.7.0, that deleting templates.pet-entry.shift-right-click-actions opts you out of pets leaving storage. It never held. guis/main.yml is managed and carries no extensible marker, so the next boot merges the deleted key straight back. The switches that DO hold are pet-items.enabled: false, update-configs: false, or a # sn:extensible line you write above pet-entry: in your own file. The README and the shipped comments now say this.
Note that a corrected comment on a key you already have does not reach your disk either - only new installs get the new wording.
holograms.billboard (vertical | center | horizontal | fixed, default
vertical). config.yml is managed, so the key and the new look arrive on the next boot with no
file editing. Writing center restores the previous behaviour exactly.vertical and says so once in the console.No command, permission or API change. API_VERSION stays 1.0.0.
-s / -sf flags now tab-completeEvery /pets admin command declares the two silent flags as its last two optional parameters, so
they complete on tab and appear in the usage line as [-s] [-sf]. They were always accepted, but
tab completion stopped at a leaf's last declared argument, so on the sixteen commands whose
arguments are all required they were suggested nowhere and had to be typed from memory. Press tab
once a command's own arguments are typed and you get both; press it again after typing one and you
still get the other.
Nothing about how the flags behave changed: they are still trailing and order-independent, -sf
still silences success confirmations only, and a trailing token that is neither an argument nor a
flag is still accepted in silence.
Two new config.yml keys cap what the capacity commands may leave on a player:
| Key | Default | Caps |
|---|---|---|
slots.max-count | 7 | the purchased equip slots /pets admin slots may leave |
storage.max-capacity | 0 (off) | the purchased storage /pets admin storage may leave |
A command that would go past the ceiling is not cancelled: it writes the ceiling and tells you
it did, so /pets admin slots give Snopeyy 100 leaves 7 purchased slots on a stock install. 0
removes a ceiling. The default of 7 matches the seven s cells of the shipped guis/main.yml
layout, which is the number the menu can actually draw.
The ceiling does not limit the snpets.slots.<n> / snpets.storage.<n> permission grants, and it
never lowers a row on its own: lowering max-count on a live server leaves players above it alone
until the next slots give/set on them, which then clamps.
Both new config keys and both new language keys are merged into existing installs on boot; your values and comments are preserved.
storage-full and box-partial
quoting the corrected numbers.No configuration, language or permission changes: existing setups keep working untouched.
Two text bugs, no behaviour outside text.
boxes.yml or a pet in
pets/<id>.yml whose display-name starts with [rgb] or [small] drew its gradient on the
ITEM and printed the literal tag in every chat message that named it
(The [rgb]Basic Pet Box failed to open...). The tags are read at index 0 of a finished line
and a placeholder value is spliced mid-line, so they never reached it. Both registries now
consume them once, when the file is read, and the item path is unchanged: it still re-reads the
raw yml, so nothing is expanded twice. [center] is dropped from that value, because centering
a fragment that lives inside another line means nothing.{group-color} works in menus.boost-line. The boost lines travel nested inside the
{boosts} placeholder value, and a placeholder value is never re-scanned for placeholders, so
the pet cell's own {group-color} could not reach into them and the token stayed literal. The
colour is now resolved while the line itself is formatted.{group-color} is bound on the boosts menu's stat cells and roll buttons, which is what the
guis/boosts.yml header already promised. It is empty when nothing is selected or the group
declares no colour, so it never shows a literal token.{group-color} works and where it cannot: not on menus.grade-row,
menus.group-separator or menus.trait-effect-* (no pet in scope) and not inside a pet file's
lore: (that lore is itself a placeholder value).Only comments changed in lang/messages_en.yml, lang/messages_es.yml and guis/boosts.yml -
they merge in on boot and your values are kept. Nothing in pets/ or boxes.yml is touched, and
the public API is unchanged (API_VERSION stays 1.0.0).
Per-pet holograms. Every pet can now show a few lines of text above it. The lines are packet entities MOUNTED on the pet - the head itself, or the invisible carrier a BetterModel pet's bones already ride - so the client positions them from the pet on each of its own ticks and the plugin sends no position packet for a label at any point. That is what keeps the text and the pet from ever moving at different times.
holograms band in config.yml: enabled, height-offset, line-spacing, scale,
background (ARGB hex), shadow, see-through, line-width and default-lines.hologram: block in pets/<id>.yml: enabled, lines, height-offset.
pets/ is seed only, so the block is never merged into the pet files you already have - that is
what holograms.default-lines is for. Only an explicit enabled: false silences a pet.{pet}, {level}, {level-cap},
{exp}, {exp-next}, {percent}, {group}, {group-color}, {trait}, {buff},
{buff-value}) plus {owner}. PlaceholderAPI tokens resolve against the pet's owner.No hologram provider, by design. SnLib's HologramUtil cannot move a hologram and does not
expose its entity id; DecentHolograms can move, but it is server-side with its own tick and its own
teleports per line and per viewer, which is a second position stream on a second clock. Neither can
ride the pet, and riding the pet is the whole feature.
Upgrading. config.yml is managed, so an existing server receives the whole band on the next
boot with enabled: true and the pets you already have start showing holograms.default-lines.
Set holograms.enabled: false if you would rather they did not. Careful with height-offset on a
server that pushed models to the ground with models.height-offset: a model pet's label is
measured from its feet, so give those pets a larger offset of their own (the shipped
stone_golem.yml is the example).
No new command, no new permission, no new dependency, and no change to the public developer API
(API_VERSION stays 1.0.0).
A pet sitting in the storage grid can now leave it as a real item.
Nothing is destroyed by a refusal. A redeem into a full storage, into a profile that is still
loading, in creative mode while allow-creative is off, or with the feature switched off hands
the item straight back with its state intact. Taking a pet out is refused - with the pet left
exactly where it was - when it is EQUIPPED, when its pets/<id>.yml is gone, or when you have no
free inventory slot. A pet is never dropped on the floor to make the click work.
The head is not placeable, so clicking a block with it can never place it and destroy the pet on
it, and boxes.blocked-blocks applies to the redeem click too - clicking a chest opens the chest.
config.yml gains a managed pet-items band: enabled, allow-creative, and the
item.display-name / item.lore the extracted head is rendered with. Every pet placeholder the
menus use works there. The item's MATERIAL is always the pet's own head and cannot be configured.
guis/main.yml gains templates.pet-entry.shift-right-click-actions. It is a managed file, so an
existing install receives the new key and the feature works immediately. The two lore lines that
ADVERTISE it are part of an existing list and are NOT merged in - add them by hand, or delete the
pet-entry lore and let the next boot write the shipped one back. See the FAQ.
Ten new messages.* keys in lang/messages_en.yml and lang/messages_es.yml.
No new command, no new permission. The public API is untouched: API_VERSION stays 1.0.0 and
extraction and redemption fire no events in this version.
1.6.1 was bumped in the pom but never released; its changes ship here. They are prose and comments
only, plus one behaviour-identical edit (RenderConfig.reload() derives its three formation fields
through locals before publishing them).
The one an operator actually benefits from: the README told an upgrading admin to set
models.height-offset to about -1.9 for a ground-walking BetterModel pet. That is only correct
on a fresh install. A server that kept formation.height-offset: 0.35 needs about -0.35, and
copying -1.9 sinks the model 1.55 blocks into the floor.
Also corrected: two "bit-identical" claims about the 1.6.0 circle geometry that are literally
false (the measured worst disagreement is 2.3e-13 blocks, nine orders of magnitude below the
1/4096 of a block a movement packet can encode, so nothing renders differently), a facing error
understated threefold, and the "identical gaps" promise, which now holds only on a CIRCLE -
equal angle steps are not equal arc length on an ellipse (~23% gap spread at 4 pets, ~28% at 6;
1 to 3 pets unaffected).
The pet-item PDC codec was exercised against the shipped obfuscated jar, every class and method found by signature rather than by name: 33 checks, 0 failures. Full round trip is field-for-field identical, a pet with no trait or grades writes only the 4 required tags, a redeemed pet is stamped with the REDEEMER as owner and comes back unsaved and unequipped, re-encoding a decoded pet reproduces the same 8 tags, and all 11 corruption cases (absent, empty, blank, non-numeric, decimal, long-overflow) decode to null so the item is handed back rather than redeemed at an invented value. Hostile numbers clamp instead of wrapping.
formation.shape picks the curve the arc is cut from:
CIRCLE - every pet sits formation.radius blocks out, in every direction. This is the
pre-1.6.0 behaviour.OVAL - formation.radius is the distance at the owner's sides and the new
formation.back-radius the distance straight behind them (and straight in front), so the
arc hugs the owner's back and opens out at the flanks.The arc is now evaluated as an ellipse aligned to the owner and rotated into the world, and the
CIRCLE case is collapsed at load time, so the animation tick still runs one formula with no
per-pet branch and the same two trig calls and zero allocations per pet as before. A CIRCLE
server's pets stand exactly where they did in 1.5.0 - verified out of game over 31500 combinations
of yaw, arc centre, arc width, position and radius, with a worst disagreement of 2.3e-13 blocks
against a movement packet's own 1/4096-block quantum.
An unknown shape logs one warning per load and falls back to CIRCLE, exactly like
formation.facing.
formation: radius: 2.0, shape: OVAL, back-radius: 1.4, height-offset: 1.9,
arc-degrees: 190.0, facing-offset: 180.0.
animation: head-size: 1.3, bounce.height: 0.06, bounce.speed: 0.1.
config.yml's fusion.broadcast is now only the default. Any pet may override it in its own
pets/<id>.yml:
fusion:
into: gale_sprite
chance: 35.0
cost: 25000
# Announce a successful fusion of two of THIS pet to the whole server.
# Leave the key out to follow config.yml fusion.broadcast.
broadcast: trueThe flag belongs to the parent - the pet being consumed, the one whose file declares into -
so you announce the rare fusions and stay quiet about the common ones. Fuse All still never
announces, whatever any pet file says.
Existing servers keep their old formation values. config.yml is managed: SnLib inserts the
two NEW keys (formation.shape, formation.back-radius) into your file but never overwrites a
value you already had. So your server becomes an oval with your own old radius at the sides and
1.4 blocks behind, and adopts none of the other new numbers. Set formation.shape: CIRCLE and
run /pets reload to restore the old look exactly, or edit the formation and animation bands
by hand to adopt the new one.
If you adopt height-offset: 1.9 and you use BetterModel models that should stand on the ground,
remember models.height-offset is summed with it: set it to about -1.9.
pets/<id>.yml is seed-only, so no pet file you already have receives the new fusion.broadcast
key. Every existing pet keeps following the global setting until you write the key yourself.
No new command, permission or lang key. The public developer API is untouched (API_VERSION stays
1.0.0).
Every /pets admin <action> now accepts two optional trailing flags, in any order and combinable:
-s - do not message the target player-sf - do not message yourself/pets admin give Bob ember_fox 3 5 -s -sf
/pets admin currency dice-normal give Bob 10 -s
/pets admin clear Bob all -sf-sf silences success confirmations only. An error, a refusal (storage full, no such pet, an
unknown box) or a failed query always reaches the admin who ran the command - an admin command
that fails in silence is the worst possible outcome.
Put the flags last: everything after the first flag is treated as flag tail, so
/pets admin give Bob ember_fox -s 5 grants one pet, not five.
-s previously had a single message to silence, because only /pets admin currency ever told its
receiver anything. The three grants that did not now do:
messages.pet-received - sent by /pets admin givemessages.box-received - sent by /pets admin givebox and /pets admin giveallboxBoth are new keys in lang/messages_en.yml and lang/messages_es.yml, merged into an existing
install automatically on boot. Offline receivers are never messaged.
/pets admin list <player> -sf, clear <player> all -s, givebox <player> <box> -sf and the
other commands with an optional argument now accept a flag where that optional would go. Before,
they answered snlib.invalid-number / snlib.invalid-value and did nothing.- in the position of an optional
argument.API_VERSION stays 1.0.0).README.md gained a "Silent flags" section, and two long-standing table entries were corrected:
/pets admin give and /pets admin list now show their optional arguments, and
/pets admin open is described correctly (it opens the target's own menu on the target's
screen; there is no read-only spectate mode).
Every pet box is a top-level key in a single boxes.yml (seed-only, # sn:extensible-root),
the same shape traits.yml already used. One place to edit them all.
The upgrade is automatic. On the first boot after updating, an existing boxes/ folder is
folded into boxes.yml and renamed to boxes-migrated/. Nothing is deleted, every box id is
kept, and the box items already sitting in players' inventories and shulkers keep opening.
A file name containing a dot cannot be a yml key, so it becomes _ with a loud console warning
(only the stacks of THAT box are affected). Files written with tab indentation or a UTF-8 BOM
migrate correctly. If the migration cannot finish for any reason, the plugin refuses to start
rather than silently continue with the two example boxes - your folder is never touched.
A box can now FAIL to open. Each one declares a band:
rare:
chance:
min: 60
max: 90When a box is handed out, a percentage is drawn in that band, rendered into the item's
{chance} name and lore, and written onto the stack. Opening rolls the number on the stack,
not the box's current band, so:
A failed open consumes the box, grants nothing, starts the box's cooldown and sends
messages.box-chance-failed (or -bulk). Boxes handed out before this version carry no
percentage and count as 100%, so nothing already in play changes.
Per-box failure announcements: feedback.fail-broadcast-key (new), beside the existing
broadcast-key.
/pets admin givebox Snopeyy basic 1000 used to answer "Snopeyy's pet storage is full (0/54).
Nothing was given." A box is an ITEM: it goes in a chest and waits. Both give commands now
always hand the boxes over, and storage capacity gates only OPENING - which returns the box with
its percentage intact.
[chance] argument/pets admin givebox <player> <box> [amount] [chance]
/pets admin giveallbox <box> [amount] [chance]Without it the percentage is drawn from the box's own band; with it (0-100) it is forced.
giveallbox draws once, so everybody receives the same number. [amount] now has an explicit
ceiling of 10000 boxes per command, because a physical give with no upper bound is a typo away
from spawning tens of thousands of item entities.
messages.box-chance-failed, messages.box-chance-failed-bulk, messages.box-fail-broadcast.
admin-givebox-done / admin-giveallbox-done gained {chance}.
Note for existing installs: SnLib inserts missing KEYS into your language file but never
overwrites a value you already have, so the three new keys arrive automatically while the two
admin-*-done lines keep their old wording (no {chance} shown) until you update them by hand.
The same applies to boxes.yml: migrated boxes get no chance block and no {chance} in their
lore, so they always open until you add one.
Requires SnLib (attached to this release's dependencies as usual). Paper 1.20.x and 1.21.x.
The public developer API is untouched: API_VERSION stays 1.0.0. A failed open fires no
event - PetBoxRewardEvent only ever fires when there was a reward.
The trait and boost roulettes can be clicked while they spin to reveal the result at once. The outcome was already decided and saved before the first frame, so skipping never changes what came out, only how long you wait for it. The reveal sound, the chat message and the redraw are the same as when the animation ends on its own.
Taking a pet out of the middle of the row now slides the rest down, so the free equip slots are always the last ones: with four pets equipped, unequipping the second leaves 1, 2, 3 occupied and 4 free instead of a gap at 2. It applies to unequipping, deleting, transferring and retyping a pet, and a collection that already had a gap is compacted on its owner's next login. Nothing is deleted and no extra formation rebuild is paid for it.
An unequip performed on an OFFLINE player is the one exception: the gap stays until that player next logs in, which is deliberate. Closing it right away would mean one database write per pet that has to move.
A group in config.yml may now declare a colour, and every menu template that draws a pet can
use it as {group-color}:
groups:
rare:
display: "&9Rare"
color: "&9"
order: 2It accepts a legacy code (&9), a hex code (&#ff00aa) or SnLib's [rgb] gradient tag; the
[rgb] form only works when {group-color} is the first thing on the line. It is purely
cosmetic and is not a rarity system: nothing in the plugin reads a value out of it.
groups: is marked # sn:extensible, so the new color: key is never inserted into a config
that already exists. Until you write one, {group-color} falls back to the colour codes that
group's display already starts with ("&9Rare" yields &9), so the placeholder is correct
either way.
guis/boosts.yml and guis/traits.yml receive the new click-actions: ["[pets-skip-spin]"]
key on the spinning template automatically, so the skip works after a restart. The
&eClick to skip the animation hint is a new line appended to an existing lore list, and
SnLib never appends to a list you already own: add it by hand if you want it shown.
No API change (API_VERSION stays 1.0.0). No new command, permission or lang key.
/pets, or any [pets-open] button) painted SnLib's declared-items frame first (BARRIERs, an empty grid) and only received the bound one a container sync later, which read as the menu loading. The fully bound window is now resent in the same call stack, so that frame never exists.group-empty template drew a GRAY_STAINED_GLASS_PANE instead of the icon configured under menus.bulk-delete.icons. It now binds the same {icon} the full button does, with the grey name and lore unchanged.[pets-forget-selection] close-action, guarded so navigating between menus (boosts to selector to boosts, traits to the index and back) still keeps the pick.Drop the new jar in and restart. One value has to be changed by hand on an existing install: SnLib's merge inserts missing keys but never overwrites a value you already have, so guis/bulk_delete.yml keeps the old glass pane. Set
templates:
group-empty:
material: "{icon}"or delete guis/bulk_delete.yml to have it reseeded. New installs already ship it. The new close-actions key merges into all seven guis/*.yml on boot with no action needed.
No config, lang, command, permission or API changes. API_VERSION stays 1.0.0.
models.smoothing-ticks. With the bones riding the carrier there is no second position stream left to ease, and easing would only make a model trail the heads beside it. A value left in an existing config.yml is ignored; there is nothing to migrate.No config migration, no database migration, and no API change (API_VERSION stays 1.0.0). Pure jar swap.
Adds a public developer API so other plugins can integrate with SnPets. API_VERSION 1.0.0.
The API lives in com.sn.pets.api inside this same jar. There is no separate artifact: install the jar into your local Maven repo and depend on it with provided scope.
Cancellable, fired before the action:
PetEquipEvent, PetUnequipEvent - a player equips or stores a pet from the pet menuPetBoxOpenEvent - before the box table is rolled; cancelling hands every box backPetFuseEvent - before a fusion charges or destroys anythingPetGroupDeleteEvent - before a bulk delete empties a groupPetRollEvent - before a trait or boost roll spends anythingNotifications, fired after the fact:
PetBoxRewardEvent, PetLevelUpEvent, PetFusedEvent, PetRolledEventCancellable events fire only for player-driven actions. The /pets admin equivalents bypass them on purpose, so an operator override is never vetoable by a third-party listener.
SnPetsProvider.get() returns a read-only facade with 11 methods: a player's equipped and stored pets, their capacities, currency balances and buff totals, plus the pet, trait and boost-grade definitions the server declares. Every method is synchronous and reads in-memory state, so all of them are safe on the main thread.
New key api-events.enabled (default true). Set it to false to skip all API event dispatch at zero cost; the query service stays available either way.
Docs: the Developer API page on the shared GitBook.
No behaviour changes for servers that do not use the API.
First stable release of SnPets.
Packet-based floating pets that follow their owner in a half-circle formation which rotates with the owner's camera. Pets render as player heads or as BetterModel models, level up from a configurable experience source, and grant damage, resistance and speed buffs that scale with level. The whole server shares ONE animation tick, so pet count never adds scheduler tasks.
Features
Requirements: Java 21+, Paper 1.20.4+, SnLib 1.30.0+ and packetevents in plugins/
Audit: shipped after a full /sn-audit run (~230 agents) with the severity gate PASSED -
17 critical findings resolved and blind-verified, 128 non-gating warnings recorded.
Documentation: the shared GitBook, section SnPets.