File Details
arcadia-admin-panel-1.2.4
- R
- May 18, 2026
- 167.15 KB
- 25
- 1.21.1
- NeoForge
File Name
arcadia-admin-panel-1.2.4.jar
Supported Versions
- 1.21.1
Curse Maven Snippet
## [1.2.4] - 2026-05-18 (latest)
### Added (fourth pass)
- **Quick announcement command** — New `/arcadia_adminpanel announce <title>[| <subtitle>]`. Pushes a vanilla title + optional subtitle to every online player and plays a `BLOCK_NOTE_BLOCK_BELL` chime so people actually notice. Title and subtitle are split on a single `|` so the whole message fits in one greedy argument. Color codes (`&a`, `&c`, `§e`, …) are honoured inline so staff can style the title without typing the section character. Title timings: 10t fade-in / 60t hold / 20t fade-out. New permission node `arcadia.adminpanel.announce` gates it (OP level ≥ 2 still short-circuits).
### Ajouts (fourth pass)
- **Commande d'annonce rapide** — Nouvelle `/arcadia_adminpanel announce <titre>[| <sous-titre>]`. Envoie un title + sous-titre vanilla à chaque joueur en ligne et joue un son `BLOCK_NOTE_BLOCK_BELL` pour que les gens remarquent. Titre et sous-titre sont séparés par un seul `|` pour que tout le message tienne dans un argument greedy. Les codes couleur (`&a`, `&c`, `§e`, …) sont respectés inline donc le staff peut styliser le titre sans taper le caractère section. Durées du titre : 10t fade-in / 60t hold / 20t fade-out. Nouveau node de permission `arcadia.adminpanel.announce` qui gate la commande (OP level ≥ 2 court-circuite toujours).
### Added (third pass)
- **Jail Baton (matraque) item** — Custom 32×32 textured staff tool. Right-click another player to jail them for 30 minutes; right-click an already-jailed player to release them. Wielder must have `arcadia.adminpanel.jail` (same gate as the GUI jail button). Cannot baton yourself, cannot baton other staff members (`canOpenAdminPanel` immunity check). New command `/arcadia_adminpanel givebaton` drops one into the staff member's inventory. Stack size 1, RARE rarity, fire-resistant. Hand model uses the vanilla handheld pose so it looks like a tool when held.
- **Warn offline players** — New `/arcadia_adminpanel warnoffline <name> <reason>` accepts both online AND offline targets (resolves via the offline-player cache). Offline targets get the warn row written immediately; the warn list and the on-join notification cover the rest. The existing `warn @selector <reason>` is preserved unchanged for multi-target online use cases. The GUI's existing warn flow (right-click + chat-input session) already worked for offline players.
### Fixed (third pass)
- **Join warn message: the command was hard to spot** — The "/arcadia_adminpanel checkwarn" hint was sent immediately on join (drowned by other mods' welcome spam) and rendered as plain text in a small grey font. Two improvements: (1) delay the warn-summary delivery by 40 ticks (~2 s) so it lands AFTER other mods' join messages; (2) make the command name a clickable + hoverable component using `SUGGEST_COMMAND` action — clicking it fills the chat box so the player sees what they're about to run before pressing Enter. Hover tooltip explains "click to fill the chat box".
### Performance (third pass)
- **Jail anti-glitch sweep now iterates jailed set, not online player list** — Previous version did `O(online_players)` lookups per tick interval. On a 100-player server with 0 jailed entries that was 100 wasted HashMap lookups per second. New version iterates `JailManager.getAllJailed()` directly — typically 0-3 entries — and looks up the corresponding `ServerPlayer` once per jailed UUID.
- **`WarnPolicy.filterActive` always returns an immutable snapshot** — Defensive change so callers stashing the list across thread boundaries (the new scheduler-delayed join notification) cannot accidentally hold a reference to mutable internal state. Costs one extra `List.copyOf` when expiry is disabled; negligible.
### Ajouts (third pass)
- **Matraque de Prison (Jail Baton)** — Outil staff custom avec texture 32×32. Clic droit sur un joueur pour l'emprisonner 30 minutes ; clic droit sur un joueur déjà en prison pour le libérer. Le porteur doit avoir `arcadia.adminpanel.jail` (même gate que le bouton jail du GUI). Impossible de se matraquer soi-même, impossible de matraquer un autre staff (immunité `canOpenAdminPanel`). Nouvelle commande `/arcadia_adminpanel givebaton` ajoute une matraque dans l'inventaire du staff. Stack 1, rareté RARE, résistante au feu. Le modèle d'inventaire utilise le pose handheld vanilla pour ressembler à un outil quand tenu en main.
- **Warn des joueurs hors ligne** — Nouvelle `/arcadia_adminpanel warnoffline <nom> <raison>` accepte les cibles online ET offline (résolu via le cache offline-player). Les cibles offline reçoivent l'écriture du warn immédiatement ; la liste des warns et la notification à la connexion gèrent le reste. L'existante `warn @selector <raison>` est préservée inchangée pour les usages multi-cible en ligne. Le flow warn du GUI (clic droit + session chat-input) fonctionnait déjà pour les joueurs offline.
### Correctifs (third pass)
- **Message warn à la connexion : la commande était dure à voir** — Le hint "/arcadia_adminpanel checkwarn" était envoyé immédiatement à la connexion (noyé par le spam de bienvenue des autres mods) et rendu en texte gris pâle. Deux améliorations : (1) délai du résumé warn de 40 ticks (~2 s) pour qu'il atterrisse APRÈS les messages de connexion des autres mods ; (2) le nom de la commande devient un component cliquable + hoverable avec l'action `SUGGEST_COMMAND` — cliquer remplit la barre de chat pour que le joueur voie ce qu'il va exécuter avant d'appuyer sur Entrée. Tooltip hover explique "cliquez pour remplir la barre de chat".
### Performance (third pass)
- **Le balayage anti-glitch de la prison itère le set des jailed, pas la liste des joueurs en ligne** — La version précédente faisait `O(joueurs_en_ligne)` lookups par intervalle de tick. Sur un serveur de 100 joueurs avec 0 entrée jailed c'était 100 HashMap lookups gaspillés par seconde. La nouvelle version itère `JailManager.getAllJailed()` directement — typiquement 0-3 entrées — et lookup le `ServerPlayer` correspondant une fois par UUID jailed.
- **`WarnPolicy.filterActive` retourne toujours un snapshot immuable** — Changement défensif pour que les callers stashant la liste à travers les frontières de threads (la nouvelle notification de connexion délayée par scheduler) ne puissent pas accidentellement garder une référence à l'état mutable interne. Coûte un `List.copyOf` supplémentaire quand l'expiration est désactivée ; négligeable.
### Added (second pass)
- **Granular per-action permissions** — Reworked the permission model. Each button has its own LuckPerms node (`arcadia.adminpanel.open`, `.warn.view`, `.warn.edit`, `.teleport`, `.invsee`, `.clearinv`, `.resetprogress`, `.kick`, `.ban`, `.mute`, `.jail`, `.setjail`, `.reload`, `.teams`, `.loginqueue`). Buttons the viewer doesn't have are hidden from the GUI entirely. Slash commands check the same nodes via a shared `require()` predicate. OP level >= 2 short-circuits everything (vanilla admins keep full access). Legacy `arcadia.staff.mod` is still honoured as a fallback so existing groups don't lose access on upgrade. Results cached for 2 s per-player so a single menu rebuild only hits the perm backend once.
- **FTB Chunks claim & force-load count** — When FTB Chunks is installed, the team detail menu header, the team list, and the per-player "View Team" button surface the team's total claim count and force-loaded chunk count (with the team's max if set). Parser reads `<world>/ftbchunks/<team-uuid>.snbt` directly — no runtime dependency on the FTB Chunks mod. 30 s cache.
- **Configurable warn TTL with on-join notification** — New `config/arcadia/arcadiaadminpanel/config.json` with `warnExpiryDays` (default 180 = ~6 months, `0` disables). On startup and on `/reload`, warns older than the TTL are physically deleted from the active backend. On player join, the player receives a chat summary of their currently-active warns (top 5) with the time-until-expiry for each. Configurable: `warnNotifyOnJoin = true/false`.
- **Login queue (connection throttle)** — Off by default. When enabled, throttles concurrent logins via `PlayerNegotiationEvent` so a post-restart connection burst doesn't melt heavy modpacks. Token-bucket rolling window: `loginQueueMaxPerWindow` logins per `loginQueueWindowSeconds`. Queued players stay in the "Connecting…" state without holding a player slot or triggering chunk loads. Cap on queue wait (`loginQueueMaxWaitMs`, default 60 s) — beyond that we admit anyway rather than time them out.
- **Jail anti-glitch** — Three independent enforcement layers protect jailed players from escape. (1) `EntityTeleportEvent` cancel for ender pearls, chorus fruit, /tp, and any mod that goes through the standard teleport pipeline — destinations outside the jail proximity radius are denied. (2) Right-click intercept for items/blocks whose registry ID contains "waystone"/"warp"/"teleport"/"portal"/"return_stone" — catches mod teleport gear that bypasses (1). (3) Periodic proximity sweep (default every 1 s) that re-teleports any jailed player whose position drifted outside the configurable radius (default 32 blocks) or whose dimension changed. Catches everything (1) and (2) miss. All three are tunable in `config.json`.
### Fixed (second pass)
- **Cross-server jail sync on reconnect** — Player jailed on server A, disconnects, reconnects on server B: B was loading its jail cache only at startup and didn't know about the new row. `PlayerLoggedInEvent` now does an async per-player DB lookup before teleporting, so jails created on other servers propagate immediately. Conversely, if the row was deleted on another server (unjail) between disconnect and reconnect, B clears its stale cache entry. JSON-mode (single-server) is unaffected.
- **Admin couldn't unjail themselves** — When a staff member jailed themselves (or got jailed by another admin as a prank), the command-block filter rejected `/arcadia_adminpanel unjail` and there was no way out short of a DB edit or restart. Staff (anyone passing the `canOpenAdminPanel` check) now bypass the jail command filter.
- **Search bar was unreadable when filtering** — The previous filter painted a 0xCC overlay on top of every non-matching player head, leaving the names + textures partially visible underneath and the tooltip floating over the dimmed slot. The whole row of darkened heads was illegible. The new filter completely skips rendering for non-matching slots (`renderSlot` returns early, painting a flat dark fill), suppresses the tooltip for those slots, and ignores clicks. Only matching heads remain visible.
### Ajouts (second pass)
- **Permissions granulaires par action** — Refonte du modèle de permissions. Chaque bouton a son propre node LuckPerms (`arcadia.adminpanel.open`, `.warn.view`, `.warn.edit`, `.teleport`, `.invsee`, `.clearinv`, `.resetprogress`, `.kick`, `.ban`, `.mute`, `.jail`, `.setjail`, `.reload`, `.teams`, `.loginqueue`). Les boutons que le viewer ne possède pas sont entièrement masqués du GUI. Les commandes slash vérifient les mêmes nodes via un prédicat `require()` partagé. OP level >= 2 court-circuite tout (les admins vanilla gardent l'accès complet). Le legacy `arcadia.staff.mod` reste honoré en fallback pour ne pas casser les groupes existants. Résultats cachés 2 s par joueur pour qu'une seule construction de menu ne tape qu'une fois sur le backend de perms.
- **Compteur de claims FTB Chunks et chunks force-loaded** — Quand FTB Chunks est installé, l'en-tête du menu de détail team, la liste des teams, et le bouton "Voir la Team" par joueur affichent le total de claims de la team et le nombre de chunks force-loaded (avec le max de la team si défini). Le parser lit `<world>/ftbchunks/<team-uuid>.snbt` directement — pas de dépendance d'exécution sur le mod FTB Chunks. Cache 30 s.
- **TTL des warns configurable avec notification à la connexion** — Nouveau `config/arcadia/arcadiaadminpanel/config.json` avec `warnExpiryDays` (défaut 180 ≈ 6 mois, `0` désactive). Au démarrage et sur `/reload`, les warns plus vieux que le TTL sont supprimés physiquement du backend actif. À la connexion d'un joueur, il reçoit un résumé chat de ses warns actuellement actifs (top 5) avec le temps avant expiration. Configurable : `warnNotifyOnJoin = true/false`.
- **File d'attente de connexion (throttle)** — Désactivée par défaut. Quand activée, throttle les connexions concurrentes via `PlayerNegotiationEvent` pour qu'un burst de reconnexions post-reboot ne fasse pas fondre les modpacks lourds. Token-bucket fenêtre glissante : `loginQueueMaxPerWindow` connexions par `loginQueueWindowSeconds`. Les joueurs en file restent en état "Connexion en cours…" sans occuper de slot joueur ni déclencher de chargement de chunks. Cap sur l'attente (`loginQueueMaxWaitMs`, défaut 60 s) — au-delà on les admet quand même plutôt que de les timeout.
- **Anti-glitch prison** — Trois couches d'application indépendantes protègent les joueurs en prison contre l'évasion. (1) Annulation d'`EntityTeleportEvent` pour les perles de l'ender, les chorus, /tp, et tout mod passant par le pipeline standard de téléportation — les destinations hors du rayon de proximité de la prison sont refusées. (2) Intercept du clic droit pour les items/blocs dont l'ID registry contient "waystone"/"warp"/"teleport"/"portal"/"return_stone" — attrape les items de téléport des mods qui contournent (1). (3) Balayage de proximité périodique (défaut toutes les 1 s) qui re-téléporte tout joueur emprisonné dont la position a dérivé hors du rayon configurable (défaut 32 blocs) ou dont la dimension a changé. Attrape tout ce que (1) et (2) loupent. Les trois sont configurables dans `config.json`.
### Correctifs (second pass)
- **Synchronisation cross-serveur du jail à la reconnexion** — Joueur emprisonné sur serveur A, se déconnecte, se reconnecte sur serveur B : B ne chargeait son cache jail qu'au démarrage et ne connaissait pas la nouvelle ligne. `PlayerLoggedInEvent` fait maintenant un lookup async DB par joueur avant le téléport, donc les jails créés sur d'autres serveurs se propagent immédiatement. Inversement, si la ligne a été supprimée sur un autre serveur (unjail) entre la déconnexion et la reconnexion, B nettoie son entrée de cache obsolète. Le mode JSON (mono-serveur) n'est pas affecté.
- **Un admin ne pouvait pas se unjail lui-même** — Quand un membre du staff se jailait (ou se faisait jail par un autre admin pour rigoler), le filtre de commande rejetait `/arcadia_adminpanel unjail` et il n'y avait aucune issue à part une édition DB ou un restart. Le staff (toute personne passant le check `canOpenAdminPanel`) bypass maintenant le filtre de commande du jail.
- **La barre de recherche était illisible quand on filtrait** — Le filtre précédent peignait une overlay 0xCC sur chaque tête de joueur non-correspondante, laissant les noms + textures partiellement visibles dessous et le tooltip flottant au-dessus du slot assombri. Toute la rangée de têtes assombries était illisible. Le nouveau filtre saute complètement le rendu des slots non-correspondants (`renderSlot` retourne tôt en peignant un fill sombre plat), supprime le tooltip pour ces slots, et ignore les clics. Seules les têtes correspondantes restent visibles.
### Added
- **Last login / last logout / first seen tracking** — New `LoginTracker` records the epoch timestamp of every connect and disconnect to `config/arcadia/arcadiaadminpanel/logins.json`. Surfaced in the player detail GUI (skull lore) and the chat info dump. First-seen timestamps are backfilled from the `.snbt` file creation time during the offline-player scan so existing playerbases get reasonable values without waiting for everyone to reconnect. FTB Essentials' `last_seen.time` is kept as a separate "last position" indicator — it tracks teleport events, not login events, so it cannot answer "when did this player last connect?".
- **FTB Teams browser** — New `Browse Teams` button on the main admin panel (visible only if `<world>/ftbteams` exists). Opens a paginated list of all parties + server teams; click a team to see its members (sorted owner → officer → member → ally), their rank, and their last-seen position. Left-click a member opens the standard player detail panel; right-click teleports the admin to that member's last-seen position. Parses FTB Teams' SNBT files directly so the feature works without a runtime dependency on the FTB Teams mod. Also surfaces a per-player "View Team" button on the player detail panel for players that belong to a team.
- **Programmatic kick/ban path** — Kick and ban now use the vanilla `PlayerList` / `UserBanList` APIs directly instead of building a `kick <name> <reason>` / `ban <name> <reason>` command string. Same outcome, but no command-string concatenation involving a player-controlled name.
### Fixed
- **FTB Essentials data directory not found on certain server layouts** — `OfflinePlayerManager.findFTBDataDirectory` used `Path.endsWith("ftbessentials/playerdata")` which on Windows compares against the OS path separator and silently never matches, so the recursive-walk fallback was a no-op. Players reporting `[ArcadiaAdmin] Could not find FTB Essentials data directory!` on otherwise-valid FTB installs hit this. The new lookup (1) asks the running server for its world path via `server.getWorldPath(LevelResource.ROOT)` and resolves `ftbessentials/playerdata` underneath it (authoritative), (2) falls back to a candidate list including `./`, `./world`, `./Arcadia_World`, and whatever `level-name` is set to in `server.properties`, then (3) does a bounded depth-4 walk that compares path *segments* (correct on every OS).
- **Permission re-check on every menu click** — Defense in depth (1.2.2/1.2.3 hardened the open paths). Every `clicked()` handler in `AdminPanelMenu`, `PlayerDetailMenu`, `TeamListMenu`, `TeamDetailMenu`, and `WarnListMenu` now re-validates `arcadia.staff.mod` (strict) or OP level >= 2 before processing the slot. Closes the theoretical packet-injection path where a crafted `Container Click` packet could trigger sensitive actions (ban/kick/clear/tp/jail) on an already-open container after the permission state had changed.
- **Command-injection hardening in player detail menu** — All `performPrefixedCommand` calls that previously concatenated `targetName` (invsee, clear, advancement revoke) now gate on a strict identifier check (`[a-zA-Z0-9_]{1,16}`). TP-self/TP-here and kick/ban no longer route through the command dispatcher at all — they call `ServerPlayer.teleportTo`, `Connection.disconnect`, and `UserBanList.add` directly. Mitigates argument injection if an offline-mode server has a player named e.g. `"alice everyone"`.
- **`SimpleDateFormat` race condition in warn list** — `WarnListMenu` shared a single `SimpleDateFormat` static field across threads. `SimpleDateFormat` is not thread-safe; concurrent admins opening warn lists could see garbled dates or trip an `ArrayIndexOutOfBoundsException` inside the formatter. Replaced with a `DateTimeFormatter` (thread-safe by contract) using `Instant.ofEpochMilli`.
- **Jail expiry race (double-release / double-teleport-back)** — `JailManager.isJailed` did a non-atomic get-then-remove on the `ConcurrentHashMap`, and the scheduled expiry lambda re-read the same key. Concurrent invocations could both observe an expired entry, both fire the DB delete, the "released" chat message, and the teleport-back. `isJailed` now uses `computeIfPresent` for atomic expiry eviction; the scheduled lambda uses `remove(key, value)` so side effects only fire for the exact entry it scheduled (also fixes the case where a player is re-jailed before the original timer fires). `getJailEntry` no longer re-enters `isJailed`, removing a separate TOCTOU.
- **`WarnManager.removeWarn` non-atomic remove** — The sort + index-lookup + `warns.remove(toRemove)` sequence was partially synchronized; a concurrent `addWarn` between the snapshot and the remove could shift indices and delete the wrong entry. Whole block is now inside one `synchronized (warns)` region.
- **`WarnListMenu` non-staff bypass** — The delete-warn handler gated only on `!sp.getUUID().equals(targetUUID)`, meaning any non-target player who could reach this menu was implicitly allowed to delete warns. Now requires the strict staff check; the self-view path (`/arcadia_adminpanel checkwarn`) remains read-only as intended.
- **`.snbt` filename filter compared full path on Windows** — Both `OfflinePlayerManager.scanDirectory` and `FTBTeamsReader.getTeams` used `Path.toString().endsWith(".snbt")`, which matches if any parent directory in the absolute path contains the suffix. Switched to `getFileName().toString().endsWith(".snbt")`.
### Performance
- **Daemon thread for offline scan** — `Arcadia-OfflineScan` is now a daemon thread; in pathological scan cases it can no longer hold up server shutdown.
- **30-second cached FTB Teams reads** — `FTBTeamsReader` mirrors `FTBDataReader`'s caching strategy so the new Teams GUI doesn't hit disk on every menu redraw or pagination click.
### Ajouts
- **Suivi dernière connexion / dernière déconnexion / première fois vu** — Nouveau `LoginTracker` enregistre l'horodatage epoch de chaque connexion et déconnexion dans `config/arcadia/arcadiaadminpanel/logins.json`. Affiché dans le GUI de détail joueur (lore du crâne) et dans le dump info chat. Les horodatages "première fois vu" sont récupérés depuis la date de création du fichier `.snbt` pendant le scan offline, donc les bases joueurs existantes obtiennent des valeurs raisonnables sans attendre que tout le monde se reconnecte. Le `last_seen.time` de FTB Essentials est gardé comme indicateur séparé "dernière position" — il suit les téléportations, pas les connexions, donc il ne peut pas répondre à "quand ce joueur s'est-il connecté la dernière fois ?".
- **Navigateur FTB Teams** — Nouveau bouton `Parcourir les Teams` sur le panneau admin principal (visible uniquement si `<world>/ftbteams` existe). Ouvre une liste paginée de toutes les parties + teams serveur ; cliquez sur une team pour voir ses membres (triés owner → officer → member → ally), leur rang, et leur dernière position connue. Clic gauche sur un membre ouvre le panneau de détail joueur standard ; clic droit téléporte l'admin à la dernière position connue du membre. Parse les fichiers SNBT de FTB Teams directement, donc la fonctionnalité fonctionne sans dépendance d'exécution sur le mod FTB Teams. Affiche également un bouton "Voir la Team" par joueur sur le panneau de détail joueur pour les joueurs qui appartiennent à une team.
- **Chemin kick/ban programmatique** — Kick et ban utilisent maintenant directement les APIs vanilla `PlayerList` / `UserBanList` au lieu de construire une chaîne de commande `kick <nom> <raison>` / `ban <nom> <raison>`. Même résultat, mais pas de concaténation de chaîne de commande impliquant un nom contrôlé par le joueur.
### Correctifs
- **Dossier de données FTB Essentials non trouvé sur certains layouts serveur** — `OfflinePlayerManager.findFTBDataDirectory` utilisait `Path.endsWith("ftbessentials/playerdata")` qui sur Windows compare au séparateur de chemin OS et silencieusement ne matche jamais, donc le fallback en walk récursif était inopérant. Les joueurs signalant `[ArcadiaAdmin] Could not find FTB Essentials data directory!` sur des installs FTB par ailleurs valides touchaient ce bug. La nouvelle recherche (1) demande au serveur en cours son chemin de monde via `server.getWorldPath(LevelResource.ROOT)` et résout `ftbessentials/playerdata` dessous (autoritatif), (2) retombe sur une liste de candidats incluant `./`, `./world`, `./Arcadia_World`, et la valeur `level-name` de `server.properties`, puis (3) fait un walk borné en profondeur 4 qui compare les *segments* du chemin (correct sur tous les OS).
- **Revérification des permissions à chaque clic du menu** — Defense in depth (1.2.2/1.2.3 ont durci les chemins d'ouverture). Chaque handler `clicked()` dans `AdminPanelMenu`, `PlayerDetailMenu`, `TeamListMenu`, `TeamDetailMenu`, et `WarnListMenu` revalide maintenant `arcadia.staff.mod` (strict) ou OP level >= 2 avant de traiter le slot. Ferme le chemin théorique d'injection de paquet où un `Container Click` packet forgé pouvait déclencher des actions sensibles (ban/kick/clear/tp/jail) sur un container déjà ouvert après changement d'état de permission.
- **Durcissement contre l'injection de commande dans le menu détail joueur** — Tous les appels `performPrefixedCommand` qui concaténaient précédemment `targetName` (invsee, clear, advancement revoke) sont maintenant gated par un check strict d'identifiant (`[a-zA-Z0-9_]{1,16}`). TP-self/TP-here et kick/ban ne passent plus par le command dispatcher du tout — ils appellent directement `ServerPlayer.teleportTo`, `Connection.disconnect`, et `UserBanList.add`. Atténue l'injection d'arguments si un serveur offline-mode a un joueur nommé par exemple `"alice everyone"`.
- **Race condition `SimpleDateFormat` dans la liste de warns** — `WarnListMenu` partageait un seul champ statique `SimpleDateFormat` entre les threads. `SimpleDateFormat` n'est pas thread-safe ; des admins concurrents ouvrant des listes de warns pouvaient voir des dates corrompues ou déclencher un `ArrayIndexOutOfBoundsException` dans le formatter. Remplacé par un `DateTimeFormatter` (thread-safe par contrat) avec `Instant.ofEpochMilli`.
- **Race d'expiration de jail (double-release / double-téléport-retour)** — `JailManager.isJailed` faisait un get-then-remove non atomique sur la `ConcurrentHashMap`, et le lambda d'expiration planifié relisait la même clé. Des invocations concurrentes pouvaient toutes deux observer une entrée expirée, déclencher chacune le delete DB, le message chat "released", et le téléport retour. `isJailed` utilise maintenant `computeIfPresent` pour l'éviction atomique d'expiration ; le lambda planifié utilise `remove(key, value)` pour que les effets de bord ne se déclenchent que pour l'entrée exacte qu'il a planifiée (corrige aussi le cas où un joueur est ré-jail avant le déclenchement du timer original). `getJailEntry` ne réentre plus dans `isJailed`, supprimant un TOCTOU séparé.
- **`WarnManager.removeWarn` remove non atomique** — La séquence sort + index-lookup + `warns.remove(toRemove)` était partiellement synchronisée ; un `addWarn` concurrent entre le snapshot et le remove pouvait décaler les indices et supprimer la mauvaise entrée. Tout le bloc est maintenant dans une seule région `synchronized (warns)`.
- **Bypass non-staff dans `WarnListMenu`** — Le handler de suppression de warn ne gardait que sur `!sp.getUUID().equals(targetUUID)`, signifiant qu'aucun joueur non-cible qui pouvait atteindre ce menu n'était implicitement autorisé à supprimer des warns. Nécessite maintenant le check staff strict ; le chemin self-view (`/arcadia_adminpanel checkwarn`) reste lecture seule comme prévu.
- **Filtre `.snbt` comparé au chemin complet sur Windows** — `OfflinePlayerManager.scanDirectory` et `FTBTeamsReader.getTeams` utilisaient `Path.toString().endsWith(".snbt")`, qui matche si n'importe quel dossier parent du chemin absolu contient le suffixe. Basculé sur `getFileName().toString().endsWith(".snbt")`.
### Performance
- **Thread daemon pour le scan offline** — `Arcadia-OfflineScan` est maintenant un thread daemon ; dans les cas pathologiques de scan il ne peut plus retenir l'arrêt du serveur.
- **Lectures FTB Teams cachées 30 secondes** — `FTBTeamsReader` reflète la stratégie de cache de `FTBDataReader` pour que le nouveau GUI Teams ne touche pas le disque à chaque redraw de menu ou clic de pagination.

