Add /le stats resetProgress commands to reset player/area stats & progression (recalc-based)

Goal

Add admin/edit commands to reset a player's and/or an area's stats and progression (not just leaderboard stats). Today the only way to clear anything is the GUI AREA_STATS_CLEAR button, which is area-only and stats-only.

Commands — /lasersenigma stats resetProgress <variant>

Variant Targets Permission Confirm
allPlayersForArea <areaId> [confirm] one area, all players ADMIN yes
allAreasForPlayer <playerName> [confirm] one player, all areas EDIT if playerName = self, else ADMIN yes
playerForArea [<playerName>] [<areaId>] one player × one area EDIT if self/omitted, else ADMIN no (low impact)
allPlayerForAllArea [confirm] all players, all areas ADMIN yes
  • playerName resolved via OfflinePlayerOrSelectorParser (offline-capable); omitted ⇒ self. areaId omitted ⇒ resolved from sender location. Console + self-variant without a name ⇒ errors.command.unavailable_in_console.
  • confirm: literal last argument, never tab-completed (empty suggestion provider). If absent on a confirm-required variant, send a caution message inviting the user to re-type the command with confirm. Mirrors the RaceLifecycleCommandRegistrar reset precedent.

Data scope

Erased per variant. Kept in all cases: playersinventories, active race membership.

Table allPlayersForArea allAreasForPlayer playerForArea allPlayerForAllArea
area_player_stats per area per player player×area all
player_global_stats recalc/player reset to 0 (keep name) recalc/player reset to 0 (keep name)
area_global_stats / server_global_stats recalc recalc recalc reset
playerskeys per area per player player×area all
bonus_obtained per area per player player×area all
checkpoints (×2) player all

Checkpoints are stored per world, not per area → only erased when all of a player's areas are targeted (variants 2 & 4).

Aggregate strategy: recalculate, never decrement

Decrementing player_global_stats is unsafe: the existing onAreaStatsCleared only fixes nb_unique_areas_won (6 of 7 cumulative fields stay inflated), iterates winners only (misses non-winners' entries/time), and any pre-existing drift makes a decrement permanently wrong. Instead:

  • New service helper recalculatePlayerGlobalStats(uuid) recomputes a player row from the source of truth (area_player_stats SUM/COUNT + bonus_obtained COUNT), preserving last_known_name. Shared by the 4 variants and the GUI button.
  • Safe order: delete targeted area_player_stats + bonus_obtained → recalc affected player rows → recalc area_global_stats (or delete) → recalc server_global_stats.
  • player_global_stats: never DELETE the row on full-wipe variants — reset counters to 0 and keep last_known_name (we are resetting progression, not anonymizing).

Two server-aggregate bugs to fix (required for the recalc to be correct)

  1. server_global_stats.nb_unique_areas_won definition mismatch. The incremental path (onPlayerWon) counts (player, area) winning pairs; the recalc (computeServerTotals) uses COUNT(DISTINCT playeruuid) = distinct winners. The same field yields different values depending on the code path. Fix the recalc formula to SUM(CASE WHEN nb_victories > 0 THEN 1 ELSE 0 END) to match the incremental semantics (and the player-level field of the same name).
  2. server_global_stats.total_time_spent forced to 0 on recalc. The "cannot be summed in SQL" comment is stale — the column is a BIGINT of milliseconds and is summable. Add COALESCE(SUM(total_time_spent), 0) to computeServerTotals instead of resetting to 0L.

Layering (must respect the existing architecture)

  • Command (StatsCommandRegistrar): parse args, resolve areaId/playerName, read the confirm flag, delegate. No business logic, no SQL.
  • Controller (new StatsResetController, static like AreaController): dynamic permission check (Permission.EDIT/ADMIN.checkPermissionAndSendMsg) before the confirm gate; success messages.
  • Service (GlobalStatsUpdateService extended): delete + recalc + events.
  • Repository: new delete/aggregate queries (deleteByAreaAndPlayer, deleteByPlayer, deleteAll, findPlayerUUIDsByAreaId, computePlayerTotals; bonus deleteByPlayer / deleteByArea / deleteByAreaAndPlayer / countByPlayer).

GUI alignment

AreaStats.clear() (the GUI AREA_STATS_CLEAR button) is repointed to the new full resetAllPlayersForArea service logic, so the button and the allPlayersForArea command share a single code path (now also clearing keys/bonus).

Side deliverables

  • New translation codes (caution/confirm prompts, success messages) — 25 language files.
  • CHANGELOG entry.
  • Wiki update (commands / stats page).