From 4e47dbee1676ee5e9741326f5e09486524e79794 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 20 May 2026 21:54:07 +0200 Subject: [PATCH] fix(modtool): bump users_settings counters on every sanction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The User Info panel reads its CFH / Cautions / Bans / Trade locks counters from `users_settings.cfh_send` / `cfh_warnings` / `cfh_bans` (via totalBans) / `tradelock_amount`. Historically only `cfh_send` was ever incremented (by `InsertModToolIssue` on CFH submit), so a user could accumulate any number of Alert / Mute / Ban / TradeLock sanctions without the stats reflecting it — every panel showed all zeros even on accounts with a long sanction history visible in the modern `sanctions` table. The two systems aren't going away — `ModToolSanctions` (the modern one) tracks individual sanction events with probation timestamps, while the legacy `users_settings.cfh_*` columns are flat counters the ModTool UI displays. Both need to stay in sync. Wire them up: `ModToolManager.bumpUserSettingCounter(userId, column)` Static helper, column-whitelisted (`cfh_warnings` / `cfh_bans` / `cfh_abusive` / `tradelock_amount`) to keep the dynamic SQL safe. Single UPDATE per call; SQL exceptions logged, never thrown. `ModToolSanctionAlertEvent`, `ModToolSanctionMuteEvent` → bump `cfh_warnings`. Mute is a punitive but non-banning action; both it and Alert are recorded as a warning on the legacy counter, matching what the Cautions stat card represents in the new UI. `ModToolSanctionBanEvent` → bump `cfh_bans`. The `totalBans` field the composer sends ALREADY counts entries in the `bans` table, so the wire field reflects reality immediately — this column bump is a defensive duplicate so any code that reads `users_settings.cfh_bans` directly (e.g. plugin scripts, CMS dashboards) stays in sync. `ModToolSanctionTradeLockEvent` → bump `tradelock_amount`. Mirrors what `AllowTradingCommand` already does for the command-line path. `ModToolManager.closeTicketAsAbusive` → bump `cfh_abusive` for the REPORTER (issue.senderId), not the reported user. The Abusive counter measures false reports filed by the user, so it belongs on whoever opened the CFH that got closed as abusive. No client-side changes — counter columns are unchanged, only the write paths are. --- .../habbohotel/modtool/ModToolManager.java | 38 +++++++++++++++++++ .../modtool/ModToolSanctionAlertEvent.java | 3 ++ .../modtool/ModToolSanctionBanEvent.java | 2 + .../modtool/ModToolSanctionMuteEvent.java | 3 ++ .../ModToolSanctionTradeLockEvent.java | 3 ++ 5 files changed, 49 insertions(+) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java index 1984ad28..03546504 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java @@ -651,6 +651,10 @@ public class ModToolManager { sender.getClient().sendResponse(new ModToolIssueHandledComposer(ModToolIssueHandledComposer.ABUSIVE)); } + // Reporter (the user who opened the CFH) gets their abusive + // counter bumped — the legacy stat shown in the User Info table. + bumpUserSettingCounter(issue.senderId, "cfh_abusive"); + this.updateTicketToMods(issue); this.removeTicket(issue); @@ -737,4 +741,38 @@ public class ModToolManager { return issues; } + + /** + * Increments a single integer counter on `users_settings` for the + * given user. Used by the moderation sanction handlers to bump the + * legacy counters that `ModToolUserInfoComposer` surfaces (cfh_warnings, + * cfh_bans, cfh_abusive, tradelock_amount) — historically these were + * only ever incremented by the CFH submission path, so a user could + * accumulate any number of bans/mutes without the User Info table + * reflecting it. + * + * Restricted to a whitelisted column name to keep the dynamic SQL + * safe; the caller passes a Permission-style constant. + */ + public static void bumpUserSettingCounter(int userId, String column) { + switch (column) { + case "cfh_warnings": + case "cfh_bans": + case "cfh_abusive": + case "tradelock_amount": + break; + default: + LOGGER.warn("Refusing to bump unrecognized user_settings column: {}", column); + return; + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement( + "UPDATE users_settings SET " + column + " = " + column + " + 1 WHERE user_id = ?")) { + statement.setInt(1, userId); + statement.executeUpdate(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception bumping {} for user {}", column, userId, e); + } + } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionAlertEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionAlertEvent.java index 4c901c46..21bd1393 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionAlertEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionAlertEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.modtool; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.modtool.ModToolManager; import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctions; import com.eu.habbo.habbohotel.permissions.Permission; @@ -47,6 +48,8 @@ public class ModToolSanctionAlertEvent extends MessageHandler { } else { habbo.alert(message); } + + ModToolManager.bumpUserSettingCounter(userId, "cfh_warnings"); } else { this.client.sendResponse(new ModToolIssueHandledComposer(Emulator.getTexts().getValue("generic.user.not_found").replace("%user%", Emulator.getConfig().getValue("hotel.player.name")))); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionBanEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionBanEvent.java index 1814041b..5a2c8afa 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionBanEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionBanEvent.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.modtool; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.modtool.ModToolBanType; +import com.eu.habbo.habbohotel.modtool.ModToolManager; import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctions; import com.eu.habbo.habbohotel.modtool.ScripterManager; @@ -73,6 +74,7 @@ public class ModToolSanctionBanEvent extends MessageHandler { Emulator.getGameEnvironment().getModToolManager().ban(userId, this.client.getHabbo(), message, duration, ModToolBanType.ACCOUNT, cfhTopic); } + ModToolManager.bumpUserSettingCounter(userId, "cfh_bans"); } else { ScripterManager.scripterDetected(this.client, Emulator.getTexts().getValue("scripter.warning.modtools.ban").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername())); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionMuteEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionMuteEvent.java index e9356fdf..3be70cf6 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionMuteEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionMuteEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.modtool; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.modtool.ModToolManager; import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctionLevelItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctions; @@ -59,6 +60,8 @@ public class ModToolSanctionMuteEvent extends MessageHandler { habbo.alert(message); this.client.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_mute.muted").replace("%user%", habbo.getHabboInfo().getUsername())); } + + ModToolManager.bumpUserSettingCounter(userId, "cfh_warnings"); } else { this.client.sendResponse(new ModToolIssueHandledComposer(Emulator.getTexts().getValue("generic.user.not_found").replace("%user%", Emulator.getConfig().getValue("hotel.player.name")))); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionTradeLockEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionTradeLockEvent.java index 0cece3ce..d501eba5 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionTradeLockEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolSanctionTradeLockEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.modtool; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.modtool.ModToolManager; import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctions; import com.eu.habbo.habbohotel.permissions.Permission; @@ -49,6 +50,8 @@ public class ModToolSanctionTradeLockEvent extends MessageHandler { habbo.getHabboStats().setAllowTrade(false); habbo.alert(message); } + + ModToolManager.bumpUserSettingCounter(userId, "tradelock_amount"); } else { this.client.sendResponse(new ModToolIssueHandledComposer(Emulator.getTexts().getValue("generic.user.not_found").replace("%user%", Emulator.getConfig().getValue("hotel.player.name")))); }