Arcadia Prestige

Adds particle cosmetics and a 24-day daily-reward calendar to the Arcadia hub. Luck-perms gated.

File Details

arcadia-prestige-1.2.14

  • R
  • Jun 9, 2026
  • 142.89 KB
  • 29
  • 1.21.1
  • NeoForge

File Name

arcadia-prestige-1.2.14.jar

Supported Versions

  • 1.21.1

Curse Maven Snippet

NeoForge

implementation "curse.maven:arcadia-prestige-1493497:8221664"
Curse Maven does not yet support mods that have disabled 3rd party sharing

Learn more about Curse Maven

## [1.2.14] - 2026-06-08 (latest)

### Fixed

- **Daily milestone reward duplication (VIP / VIP+ / MVP)** — Milestone gift claims were gated solely by a static in-memory bitmask cache that was never seeded from the database: `DailyRewardHandler.preloadClaims` existed but had **zero callers**, so after a server restart — or a relog onto a freshly-restarted instance — `getMilestoneClaims` returned `0` ("nothing claimed") even though the DB row recorded the gift as taken. The `(claims & bit) == 0` guard then passed and the gift was granted again, unbounded. The same cache-only guard also let a player claim the same gift once on each of the multiple FR servers (each had its own empty cache). Three changes close it, all on 1.2.14: (1) `getMilestoneClaims` now reads the authoritative DB row on a cache miss instead of defaulting to `0` (then memoizes it); (2) `claimMilestoneGift` and `autoCollectMilestones` only grant a gift when a DB-side atomic compare-and-set (`UPDATE … SET claims = claims | bit WHERE (claims & bit) = 0`) confirms the bit transitioned 0→1 — the same race-safe pattern the daily-path claim already uses; (3) the orphaned `preloadClaims` is now called on player login to warm the cache off the main thread. The bitmask persist also switched from a full-value overwrite to `claims | VALUES(claims)` so concurrent writes can no longer clobber each other's bits.
- **Staff cosmetics regression (root cause in arcadia-lib ≥ 1.2.14)** — Staff (and VIP/MVP) players lost access to all cosmetics whenever LuckPerms bound late at server start: the lib permission backend fell back to deny-all and every `arcadia.cosmetic.*` check, wildcard included, returned `false`. Fixed in arcadia-lib by lazily (re)binding the LuckPerms backend; Prestige requires **arcadia-lib ≥ 1.2.14**.
- **`LeaderboardTabHandler.formatMobType` crash on a colon-terminated mob type** — A stored mob type like `"minecraft:"` left an empty name after the namespace split, and `charAt(0)` threw `StringIndexOutOfBoundsException`. Now returns the `—` sentinel for an empty name.
- **`CosmeticPermissionScanner` leaked LuckPerms event subscriptions** — `init()` subscribed to `NodeAddEvent`/`NodeRemoveEvent` but never kept or released the handles, so reloading a world in one JVM stacked duplicate listeners that fired `rescan()` repeatedly. The subscriptions are now stored, released on a re-`init()`, and closed on `ServerStopping`.

### Performance

- **`CosmeticPermissionScanner.rescan()` no longer re-streams group nodes per cosmetic** — Built a fresh `getNodes().stream().anyMatch()` scan per grade check and per cosmetic (≈21 traversals per qualifying group); now collects each group's granted permission keys into a `Set` once and probes it with O(1) lookups.
- **`ParticleRenderer` hoists constant `DustParticleOptions` out of the render path** — Sakura, Helix, Binary and Nova allocated fixed-colour dust options every render call; they are now pre-computed as static fields (Nova as a per-`[arm][j]` table split by density), matching the existing `CROWN_*`/`WINGS_DUST_*` pattern. No visual change.
- **`DailyTabHandler.buildTab` resolves the player's grade once** — was calling `hasMinimumGrade()` up to 12 times per render (4 milestones × 3 ranks), each doing LuckPerms grade resolutions; now resolves the grade once and compares levels.

### Correctifs

- **Duplication des récompenses de paliers journaliers (VIP / VIP+ / MVP)** — Les réclamations de cadeaux de paliers ne reposaient que sur un cache bitmask statique en mémoire, jamais alimenté depuis la base de données : `DailyRewardHandler.preloadClaims` existait mais n'avait **aucun appelant**, donc après un redémarrage serveur — ou une reconnexion sur une instance fraîchement redémarrée — `getMilestoneClaims` renvoyait `0` (« rien réclamé ») alors même que la ligne en base indiquait le cadeau déjà pris. Le garde `(claims & bit) == 0` passait alors et le cadeau était accordé à nouveau, sans limite. Ce même garde « cache uniquement » permettait aussi de réclamer le même cadeau une fois sur chacun des serveurs FR (chacun ayant son propre cache vide). Trois changements le corrigent, tous sur la 1.2.14 : (1) `getMilestoneClaims` lit désormais la ligne DB faisant autorité en cas de cache miss au lieu de retomber sur `0` (puis la mémoïse) ; (2) `claimMilestoneGift` et `autoCollectMilestones` n'accordent un cadeau que si un compare-and-set atomique côté DB (`UPDATE … SET claims = claims | bit WHERE (claims & bit) = 0`) confirme la transition du bit 0→1 — le même motif race-safe que la réclamation du chemin journalier ; (3) le `preloadClaims` orphelin est désormais appelé à la connexion du joueur pour réchauffer le cache hors du thread principal. La persistance du bitmask passe aussi d'un écrasement de la valeur complète à `claims | VALUES(claims)` pour que des écritures concurrentes ne s'effacent plus mutuellement.
- **Régression cosmétiques staff (cause racine dans arcadia-lib ≥ 1.2.14)** — Les joueurs staff (et VIP/MVP) perdaient l'accès à tous les cosmétiques dès que LuckPerms se liait tard au démarrage du serveur : le backend de permissions de la lib retombait en deny-all et toute vérification `arcadia.cosmetic.*`, wildcard inclus, renvoyait `false`. Corrigé dans arcadia-lib par une liaison paresseuse du backend LuckPerms ; Prestige requiert **arcadia-lib ≥ 1.2.14**.
- **Plantage de `LeaderboardTabHandler.formatMobType` sur un type de mob terminé par deux-points** — Un type de mob stocké comme `"minecraft:"` laissait un nom vide après la séparation du namespace, et `charAt(0)` levait `StringIndexOutOfBoundsException`. Renvoie désormais le sentinel `—` pour un nom vide.
- **`CosmeticPermissionScanner` fuyait des abonnements d'événements LuckPerms** — `init()` s'abonnait à `NodeAddEvent`/`NodeRemoveEvent` sans jamais conserver ni libérer les handles, donc recharger un monde dans un même JVM empilait des écouteurs en double qui déclenchaient `rescan()` à répétition. Les abonnements sont désormais stockés, libérés sur une ré-`init()`, et fermés à l'arrêt du serveur (`ServerStopping`).

### Performance

- **`CosmeticPermissionScanner.rescan()` ne re-parcourt plus les nodes du groupe par cosmétique** — Effectuait un nouveau scan `getNodes().stream().anyMatch()` par vérification de grade et par cosmétique (≈21 parcours par groupe éligible) ; collecte désormais une seule fois les clés de permission accordées de chaque groupe dans un `Set` et le sonde avec des lookups O(1).
- **`ParticleRenderer` sort les `DustParticleOptions` constants du chemin de rendu** — Sakura, Helix, Binary et Nova allouaient des options de poussière à couleur fixe à chaque appel de rendu ; elles sont désormais pré-calculées en champs statiques (Nova en table par `[arm][j]` séparée par densité), conformément au motif existant `CROWN_*`/`WINGS_DUST_*`. Aucun changement visuel.
- **`DailyTabHandler.buildTab` résout le grade du joueur une seule fois** — appelait `hasMinimumGrade()` jusqu'à 12 fois par rendu (4 paliers × 3 rangs), chacun effectuant des résolutions de grade LuckPerms ; résout désormais le grade une fois et compare les niveaux.