promotional bannermobile promotional banner

Arcadia Pets

Collect pets, level their skills, fuse them up, and duel other players. Summon them to walk beside you or ride on your shoulder !

File Details

arcadia-pets-1.2.15

  • R
  • Jun 9, 2026
  • 629.74 KB
  • 41
  • 1.21.1
  • NeoForge

File Name

arcadia-pets-1.2.15.jar

Supported Versions

  • 1.21.1

Curse Maven Snippet

NeoForge

implementation "curse.maven:arcadia-pets-1493098:8221637"
Curse Maven does not yet support mods that have disabled 3rd party sharing

Learn more about Curse Maven

## [1.2.15] - 2026-06-08

### Changed

- **Sniffer's `ANCIENT_SENSE` signature skill re-enabled, dupe-safe** — The skill was previously a no-op after its original "duplicate any broken block" form became an arbitrary-item dupe (`event.getState().getBlock().asItem()` copied spawners, command blocks and modded item-form blocks). It now duplicates **only** a server-configurable allow-list of cheap, renewable nature blocks (`config/arcadia-pets.toml` → `[sniffer] skill_friendly_blocks`: sniffer archaeology loot, flowers, seeds, dirt/sand/gravel/mud, moss) on a level-scaled chance (≈10% at Lv1 → 70% at Lv10, modulated by effectiveness). `grass_block`/`mycelium` are deliberately excluded — their item form is silk-touch-gated, so duplicating it would mint a gated block for free. The extra item is dropped at the broken block via `Block.popResource` (loot and particles now coincide), skips creative/spectator breakers, and bails when the break is cancelled.

### Security

- **Pet-sniffer dig allowlist is now enforced (was blanket-cancel)** — `PetEventHandler.onSnifferDigDrop` previously cancelled **every** server-spawned item landing within 0.6 blocks of an active pet sniffer, fully closing the vanilla `sniffer_digging` dupe but also voiding legitimate finds. It now checks the existing `SNIFFER_DIG_ALLOWLIST` cache and only cancels items **not** on the list, so the controlled vanilla-ore set drops while the dupe path (and any datapack-extended loot table) stays closed.
- **Re-enabled `ANCIENT_SENSE` cannot dupe on protected regions** — `SkillHandler.onBlockBreak` now runs at `EventPriority.LOWEST` and returns early on `event.isCanceled()`, so a protection/claim mod that vetoes the break at a lower priority no longer leaves the skill spawning a duplicate for a block that is never actually removed.
- **Bet duels accepted via the UI popup minted money (no stake escrow)** — Accepting a *wagered* duel through the in-game invite popup or the pet-book sneak-accept ran `DuelManager.accept`, which copied the bet onto the session but **never deducted the wager** — only the `/arcadia_pets duel` command path escrowed. At duel end the winner was still paid `bet×2 − 10%` out of nothing, so an ordinary client clicking *Accept* on a wagered invite created currency. Bet escrow is now centralized in `DuelManager.escrowBet`, called before `accept()` on **every** accept path (command, UI packet, pet book); on insufficient funds the challenge is cleared and the accept aborts. Payout is now a true redistribution of escrowed stakes.
- **Aura-tick packet had no server-side rate limit** — `C2SAuraTick` (documented as client-sent ~every 40 ticks) was trusted at face value; a modified client spamming it multiplied the Soul Drain self-heal and the per-call entity scan ~40×. `SkillHandler.triggerAuraTick` now gates on a server-side last-tick timestamp (36-tick minimum, stored in the player's persistent NBT so it self-cleans on logout), neutralizing the spam while leaving the legitimate cadence untouched.
- **Duel ATTACK target index was not bounds-checked** — The client-supplied `targetPetIdx` flowed into `session.isAlive()` → `hpFor(player)[idx]` with no validation (the SKILL path already bounds it), so a crafted packet threw `ArrayIndexOutOfBoundsException` on the server thread. `handleAttack` now rejects out-of-range indices up front.
- **`confirmRoster` did not verify the sender was a duel participant** — A leaked/guessed duel id let any player write into a roster and force-confirm the opponent's side. `confirmRoster` now rejects callers that are neither `p1` nor `p2`.

### Fixed

- **Pets in the Fusion Altar were destroyed on close with a full inventory** — `FusionMenu.removed()` returned the un-fused pets with `inventory.add(...)` but ignored the failure flag and cleared the slot regardless, permanently deleting the pet when the inventory was full. It now drops in-world on add-failure, mirroring the consume-then-clear idiom already used by `performFusion`.
- **Duel rewards were skipped when a duel ended by timeout, disconnect or `/duel forfeit`** — ELO, bet payout, the win quest and achievement tracking all hung off a single reward call reachable only from the packet combat path. Timeout, opponent disconnect and the command-forfeit path each ended the duel without granting any of them — a PvP duel decided by a turn timeout updated no rating and paid out no bet. All four end paths now route through one idempotent `DuelManager.finalizeDuel`.
- **Per-pet duel record (`duelsPlayed`/`duelsWon`) was wiped on almost every pet interaction** — Feeding, taking damage, dying, gaining skill XP or applying star essence rebuilt `PetData` through the 9-arg constructor, which hard-codes the duel record to `0/0`; since `updatePetItem` re-reads then overwrites, the tooltip duel record reset to zero constantly. New `withHunger`/`withSkills` helpers (and 11-arg pass-through at the star-essence sites) preserve the record across every mutation.
- **Per-pet duel stats double-counted on bot wins and over-counted on small rosters** — A bot duel ending on the bot's turn called `endDuel` twice (`+2` played); a roster of fewer than 3 pets padded the missing slots with the first pet, crediting it `×2`/`×3`. `endDuel` is now idempotent via a `finalized` flag, and `creditSide` deduplicates by pet id.
- **Confirming a roster against a bot logged a server NPE** — `C2SDuelRosterReady` broadcast the initial state to `getPlayer(p2)` unguarded; in a bot duel `p2` is `BOT_UUID` (no player) so `sendToPlayer(null, …)` NPE'd on every bot-duel start. Both sends are now null-guarded like every sibling broadcast.
- **`MAX_STAR_PET` achievement was unobtainable** — Its tracker was never called. It now fires from `applyStarEssence` when a pet reaches all six stats at 5★ (30 stars).
- **`tickRegen` could NPE on a map desync** — The active-pet HP regen dereferenced `activePetData.get(uuid)` without a null guard; it now skips a desynced entry defensively.
- **Pet-bag shift-open ignored its documented cap** — Shift-open used the full stack count; it now caps at 4 to match the "up to 4" contract and bound the reveal payload.
- **Combat-log sounds matched the raw translation key, not the rendered text** — `DuelScreen.tickSounds` now resolves the localized line before keyword-matching, so sound cues fire correctly regardless of locale.

### Performance

- **Per-second pet-item validation no longer fully decodes every pet item** — `validateActivePetItems` runs each second and scanned every inventory slot, fully parsing each `PetData` (EnumMap + skill-string parse + unique-skill fixup) just to compare a UUID. A new lightweight `PetData.readPetId` probe reads only the id tag on this hot path.
- **Item renderer no longer parses full pet NBT every frame** — `PetItemRenderer` (the custom renderer for every visible pet item) decoded a full `PetData` per slot per frame but only ever used the mob type; it now reads just the `MobType` tag and drops the per-frame dummy-`PetData` allocation in the fallback path.
- **Pedestal HUD card is cached instead of rebuilt every frame** — The crosshair-on-pedestal stats card rebuilt 5 `Component`s and re-measured its width every frame; it now caches them keyed by pet id + custom name.
- **Dead RTT render cache removed** — `PetRenderCache`/`PetRenderCacheHandler` were never wired to any caller yet `flushPending` ran every frame; both files were deleted.
- **Memory-leak evictions** — `lastAftershockMs` and `lastDeadPet` (per-player server maps) are now cleared on logout, and `PocketPetRenderer`'s client light caches are evicted on recall/clear, matching the eviction pattern already used elsewhere.
- **Fewer per-action allocations** — The duel bot reuses a shared `Random` instead of allocating one per action, and ELO data is pre-warmed off-thread at duel start so duel-end never blocks the server tick on a cold JDBC read.

### Changed

- **`PetBagItem.LOCKED_DROPS` is now a `ConcurrentHashMap`** — Matches the rest of the mod's shared server state and hardens the public field against any future off-thread caller.
- **Bundled Arcadia Lib updated `1.2.11` → `1.2.14`** — The mod now ships against the latest core library, picking up the lazy LuckPerms binding fix (staff/VIP cosmetics no longer lock out when LuckPerms registers late), the `EconomyService.add` success-reporting contract, the over-`Integer.MAX_VALUE` currency-truncation fix, and the Hub/Dashboard per-frame allocation cuts. Compiles clean against the new signatures; no `arcadia-pets` source changes required.
- **Compétence signature `ANCIENT_SENSE` du Sniffer réactivée, anti-dupe** — La compétence était un no-op depuis que sa forme d'origine « dupliquer n'importe quel bloc cassé » était devenue un dupe d'objet arbitraire (`event.getState().getBlock().asItem()` copiait spawners, command blocks et blocs moddés ayant une forme item). Elle ne duplique désormais **que** les blocs d'une liste blanche configurable côté serveur de blocs nature bon marché et renouvelables (`config/arcadia-pets.toml` → `[sniffer] skill_friendly_blocks` : loot d'archéologie du sniffer, fleurs, graines, terre/sable/gravier/boue, mousse), avec une chance croissante selon le niveau (≈10 % au niv.1 → 70 % au niv.10, modulée par l'efficacité). `grass_block`/`mycelium` sont volontairement exclus — leur forme item est verrouillée derrière le Toucher de Soie, donc la dupliquer offrirait gratuitement un bloc verrouillé. L'objet supplémentaire est lâché au bloc cassé via `Block.popResource` (loot et particules coïncident), ignore les casseurs en créatif/spectateur, et s'abstient si le cassage est annulé.

### Sécurité

- **La liste blanche de fouille du sniffer est désormais appliquée (était un blocage total)** — `PetEventHandler.onSnifferDigDrop` annulait auparavant **tout** objet généré par le serveur atterrissant à moins de 0,6 bloc d'un sniffer-familier actif, fermant entièrement le dupe `sniffer_digging` vanilla mais annulant aussi les trouvailles légitimes. Il vérifie désormais le cache `SNIFFER_DIG_ALLOWLIST` existant et n'annule que les objets **absents** de la liste, donc l'ensemble contrôlé d'ores vanilla tombe tandis que le dupe (et toute table de loot étendue par datapack) reste fermé.
- **L'`ANCIENT_SENSE` réactivée ne peut pas dupliquer sur les régions protégées** — `SkillHandler.onBlockBreak` s'exécute désormais en `EventPriority.LOWEST` et sort tôt sur `event.isCanceled()`, donc un mod de protection/claim qui annule le cassage à une priorité inférieure ne laisse plus la compétence générer un double pour un bloc qui n'est jamais réellement retiré.
- **Les duels avec mise acceptés via le popup créaient de la monnaie (aucune mise prélevée)** — Accepter un duel *avec mise* via le popup d'invitation ou l'accept au sneak du livre appelait `DuelManager.accept`, qui copiait la mise sur la session mais **ne prélevait jamais l'enjeu** — seul le chemin commande `/arcadia_pets duel` faisait l'escrow. À la fin du duel le gagnant recevait quand même `mise×2 − 10 %` sorti de nulle part : un client ordinaire cliquant *Accepter* sur une invitation misée créait de la monnaie. L'escrow est désormais centralisé dans `DuelManager.escrowBet`, appelé avant `accept()` sur **tous** les chemins d'acceptation (commande, packet UI, livre) ; en cas de fonds insuffisants le défi est annulé et l'acceptation avorte. Le paiement est maintenant une vraie redistribution des enjeux prélevés.
- **Le packet d'aura n'avait aucune limite de fréquence côté serveur** — `C2SAuraTick` (censé être envoyé ~tous les 40 ticks) était cru sur parole ; un client modifié le spammant multipliait le soin de Drain d'Âme et le scan d'entités par appel d'environ 40×. `SkillHandler.triggerAuraTick` se base désormais sur un horodatage serveur du dernier tick (minimum 36 ticks, stocké dans le NBT persistant du joueur pour s'auto-nettoyer à la déconnexion), neutralisant le spam sans toucher à la cadence légitime.
- **L'index de cible d'ATTAQUE en duel n'était pas borné** — Le `targetPetIdx` fourni par le client allait dans `session.isAlive()` → `hpFor(player)[idx]` sans validation (le chemin SKILL le bornait déjà), donc un packet bricolé lançait une `ArrayIndexOutOfBoundsException` sur le thread serveur. `handleAttack` rejette désormais les index hors plage en amont.
- **`confirmRoster` ne vérifiait pas que l'émetteur était un participant** — Un id de duel fuité/deviné permettait à n'importe quel joueur d'écrire dans une équipe et de forcer la confirmation du côté adverse. `confirmRoster` rejette désormais les appelants qui ne sont ni `p1` ni `p2`.

### Correctifs

- **Les familiers dans l'Autel de Fusion étaient détruits à la fermeture avec un inventaire plein** — `FusionMenu.removed()` rendait les familiers non fusionnés avec `inventory.add(...)` mais ignorait l'échec et vidait l'emplacement quand même, supprimant définitivement le familier si l'inventaire était plein. Il dépose désormais au sol en cas d'échec d'ajout, comme le fait déjà `performFusion`.
- **Les récompenses de duel étaient ignorées à la fin par timeout, déconnexion ou `/duel forfeit`** — ELO, paiement de mise, quête de victoire et succès dépendaient d'un seul appel atteignable uniquement depuis le chemin packet de combat. Le timeout, la déconnexion de l'adversaire et l'abandon par commande terminaient le duel sans rien accorder — un duel PvP décidé par timeout ne mettait à jour aucun classement et ne payait aucune mise. Les quatre chemins de fin passent désormais par un unique `DuelManager.finalizeDuel` idempotent.
- **Le palmarès de duel par familier (`duelsPlayed`/`duelsWon`) était effacé à presque chaque interaction** — Nourrir, subir des dégâts, mourir, gagner de l'XP de compétence ou appliquer une essence d'étoile reconstruisait `PetData` via le constructeur à 9 arguments, qui fixe le palmarès à `0/0` ; comme `updatePetItem` relit puis réécrit, le palmarès du tooltip se remettait sans cesse à zéro. De nouveaux helpers `withHunger`/`withSkills` (et le passage à 11 arguments aux sites d'essence) préservent le palmarès à chaque mutation.
- **Stats de duel par familier comptées en double sur victoire bot et sur-comptées sur petites équipes** — Un duel bot finissant au tour du bot appelait `endDuel` deux fois (`+2` joués) ; une équipe de moins de 3 familiers comblait les slots manquants avec le premier familier, le créditant `×2`/`×3`. `endDuel` est désormais idempotent via un flag `finalized`, et `creditSide` déduplique par id de familier.
- **Confirmer une équipe contre un bot loguait une NPE serveur** — `C2SDuelRosterReady` diffusait l'état initial à `getPlayer(p2)` sans garde ; en duel bot `p2` est `BOT_UUID` (aucun joueur), donc `sendToPlayer(null, …)` lançait une NPE à chaque début de duel bot. Les deux envois sont désormais protégés par null comme toutes les diffusions sœurs.
- **Le succès `MAX_STAR_PET` était inobtenable** — Son tracker n'était jamais appelé. Il se déclenche désormais depuis `applyStarEssence` quand un familier atteint ses six stats à 5★ (30 étoiles).
- **`tickRegen` pouvait NPE sur une désync de map** — La régénération de PV du familier actif déréférençait `activePetData.get(uuid)` sans garde null ; elle saute désormais une entrée désynchronisée par sécurité.
- **L'ouverture au shift d'un sac ignorait son plafond documenté** — L'ouverture au shift utilisait le compte complet de la pile ; elle plafonne désormais à 4 pour respecter le contrat « jusqu'à 4 » et borner la charge de l'écran de révélation.
- **Les sons du journal de combat se basaient sur la clé de traduction brute, pas le texte rendu** — `DuelScreen.tickSounds` résout désormais la ligne localisée avant la recherche de mots-clés, donc les indices sonores se déclenchent correctement quelle que soit la langue.

### Performance

- **La validation des familiers par seconde ne décode plus entièrement chaque item** — `validateActivePetItems` tourne chaque seconde et parcourait chaque emplacement d'inventaire en parsant entièrement chaque `PetData` (EnumMap + parse de la chaîne de compétences + correctif de compétence unique) juste pour comparer un UUID. Une nouvelle sonde légère `PetData.readPetId` ne lit que le tag d'id sur ce chemin chaud.
- **Le rendu d'item ne parse plus tout le NBT du familier à chaque frame** — `PetItemRenderer` (le rendu custom de chaque item de familier visible) décodait un `PetData` complet par slot par frame mais n'utilisait que le type de mob ; il ne lit désormais que le tag `MobType` et supprime l'allocation de `PetData` factice par frame dans le chemin de repli.
- **La carte HUD du piédestal est mise en cache au lieu d'être reconstruite à chaque frame** — La carte de stats visée au réticule reconstruisait 5 `Component` et remesurait sa largeur à chaque frame ; elle les met désormais en cache, indexés par id de familier + nom personnalisé.
- **Cache de rendu RTT mort supprimé** — `PetRenderCache`/`PetRenderCacheHandler` n'étaient câblés à aucun appelant alors que `flushPending` tournait à chaque frame ; les deux fichiers ont été supprimés.
- **Évictions de fuites mémoire** — `lastAftershockMs` et `lastDeadPet` (maps serveur par joueur) sont désormais vidés à la déconnexion, et les caches de lumière client de `PocketPetRenderer` sont évincés au rappel/clear, suivant le motif d'éviction déjà utilisé ailleurs.
- **Moins d'allocations par action** — Le bot de duel réutilise un `Random` partagé au lieu d'en allouer un par action, et les données ELO sont préchargées hors-thread au début du duel pour que la fin de duel ne bloque jamais le tick serveur sur une lecture JDBC à froid.

### Modifications

- **`PetBagItem.LOCKED_DROPS` est désormais une `ConcurrentHashMap`** — Aligne ce champ sur le reste de l'état serveur partagé du mod et le durcit contre tout futur appelant hors-thread.
- **Arcadia Lib embarquée mise à jour `1.2.11` → `1.2.14`** — Le mod est désormais livré avec la dernière bibliothèque centrale, récupérant le correctif de liaison paresseuse de LuckPerms (les cosmétiques staff/VIP ne se verrouillent plus quand LuckPerms s'enregistre tard), le contrat de signalement de succès d'`EconomyService.add`, le correctif de troncature de monnaie au-delà d'`Integer.MAX_VALUE` et les réductions d'allocations par frame du Hub/Dashboard. Compile proprement avec les nouvelles signatures ; aucune modification du code source d'`arcadia-pets` requise.