GiveBadge could treat a missing offline user as eligible for a badge and insert through a nullable user subquery. Depending on SQL mode this could fail late or persist an orphaned user_id value. Resolve the offline user first, return HABBO_NOT_FOUND when absent, and insert badges with the resolved user id only.
SetMotto updated the in-memory motto and then unconditionally broadcast RoomUserData through the current room. Online users without a current room could throw a null-pointer exception after the state change, making the RCON call report an error despite mutating the user. Only broadcast room data when a room is present and cover the invariant with a contract test.
GiveRespect inverted the offline SQL parameters for respects_given and respects_received. Online users received the intended counters, but offline users had the two persisted counters swapped. Bind respect_given to respects_given and respect_received to respects_received, with a contract test to keep the RCON offline path aligned.
GiveCredits treated offline UPDATE execution as success without checking whether any user row was changed. Nonexistent user ids could therefore return an offline success response while granting nothing. Use executeUpdate(), return HABBO_NOT_FOUND when no row is affected, and keep SQL errors from falling through to the offline success message.
RCONServerHandler released the inbound ByteBuf only after successfully parsing, writing, flushing, and closing the response. Any exception before the tail release could leak Netty buffers and let malformed RCON traffic consume memory over time. Guard non-ByteBuf messages, release accepted buffers from a finally block, and add a contract test for the release invariant.
RCON GivePixels previously used an UPDATE for offline users, so users without an existing users_currency type 0 row received no pixels while the command still returned success. Match the GivePoints and housekeeping paths with an upsert and add a contract test that keeps offline pixel grants creating missing currency rows.
Rank changes and manual currency grants are among the highest-risk housekeeping actions. They already write audit entries, but the coverage contract did not list them, so a future regression could silently remove those logs. Extend the contract test to require audit logging for credit grants, currency grants, and rank changes.
The first audit coverage pass covered economy/account-impacting HK actions, but room and session mutators still returned success without an audit row. Add audit entries for room deletion, force disconnect, room kicks, user kicks, room mute, room state changes, and successful unbans, and extend the coverage contract to keep these privileged actions tracked.
Several privileged housekeeping handlers returned success without appending an audit entry, so the action log stayed incomplete even after the log table schema was fixed. Add audit writes for ban, mute, password reset, HC changes, trade lock, item grants, room ownership transfer, and hotel alerts, and cover the expected logging surface with a contract test.
Housekeeping audit writes used an obsolete housekeeping_log schema with operator_id, operator_name, target_user_id and ip columns, while the migration and list composer read actor_id, actor_name, target_type, target_id, target_label, action, detail and success. That made log inserts fail against migrated databases and made auto-created tables unreadable by the client. Align the writer and auto-create DDL with the action-log schema, preserve operator IP in detail, and add a contract test for schema drift.
ModToolKickEvent was the only staff-only modtool handler that called the moderation kick path without checking ACC_SUPPORTTOOL first. Gate it with the same support-tool permission and scripter handling used by the neighboring moderation actions, and add a contract test that keeps all staff-only modtool handlers behind ACC_SUPPORTTOOL.
Require poll answer, cancel, and question-data packets to match the poll configured on the caller's current room. Previously a crafted packet could target any loaded poll id and submit the final question directly, including badge-reward polls, without being in a room where that poll was active.
Keep word quiz handling null-safe and add a contract test covering current-room poll scoping for all poll handlers.
Move voucher exhaustion checks and history persistence behind a synchronized per-voucher claim path. Rewards are now applied only after the history row is inserted successfully, preventing duplicate or failed-claim redemption from granting credits, points, or catalog items.
Adds a contract test for claim ordering. Maven verification was attempted but blocked by sandbox network/plugin resolution after escalation usage was exhausted; diff --check passes.
FurniEditorUpdateFurnidataEvent (10046) was edit-only: FurnidataWriter.write()
refuses classnames absent from furnidata, so a furni with no entry showed the
DB-fallback name with locked fields and "Classname not found". Make it an upsert:
- FurnidataWriter.create(): append a complete entry (JSON5-preserving, atomic +
backup) into the matching roomitemtypes/wallitemtypes furnitype array; guards
against duplicate classname (ALREADY_EXISTS) and id collision (ID_COLLISION);
split-tier writes to items.furnidata.create_tier (default "custom", file
created with a shell if absent), single-file writes to the source.
- FurnidataEntryBuilder: build the complete entry from the item's items_base row
(id = sprite id, classname, type-driven section, xdim/ydim, canstandon/
cansiton/canlayon, name/desc, sane defaults matching existing entries).
- Handler: on write()==false, load the Item, build + create the entry, map
CreateResult to a precise message; then the existing reindex + 10047 broadcast
+ public_name mirror run for both paths; audit action is "create" vs "edit".
No renderer change, no new packet. Pairs with the client unlocking name/desc when
the entry is missing (separate Nitro-V3 change).
Resolve furnidata from the renderer config and asset base before falling back to the legacy items.furnidata.path override. This keeps the emulator aligned with the same furnidata URL the UI/renderer already consume.
Keep the legacy path as a compatibility fallback for older installs, but stop exposing absolute furnidata file paths in the startup log. The provider now reports a compact manager-style source label instead.
Add coverage proving renderer-config furnidata.url wins over the legacy path when both are present.
Guard the guild acceptance update with level_id = REQUESTED so a stale or concurrent accept cannot promote a membership row that has already changed state.
Tests: mvn '-Dtest=GuildManagerMembershipContractTest,GuildMembershipManagementContractTest,GuildMembershipRequestContractTest' test
Guard RoomTradeManager.startTrade while holding the activeTrades lock so concurrent trade starts cannot register the same participant in multiple active trades before room status updates settle.
Add a contract test covering the lock-scoped participant guard and keep the existing trade safety tests green.
Room owners can remove bots from their room, but picking up another user's bot must return it to the original owner instead of transferring ownership to the picker.
Tests: mvn -Dtest=BotPickupOwnershipContractTest test; mvn -DskipTests package
Redeeming clothing furniture now inserts the wardrobe grant before removing/deleting the voucher furniture. If the DB insert fails, the item remains in the room and the in-memory wardrobe is not updated.
Tests: mvn -Dtest=RedeemClothingContractTest test; mvn -DskipTests package
Keep the housekeeping rank ceiling for normal staff, but treat the highest configured rank as the core rank so rank 7 can act on other rank 7 users without opening peer actions for lower staff ranks.
Tests: mvn '-Dtest=HousekeepingTargetRankGuardContractTest,HousekeepingMutationGuardTest,HousekeepingSetUserRankEventTest,HousekeepingTargetRankGuardContractTest' test
Deduct the computed rent cost when a user rents an InteractionRentableSpace. The previous flow only checked that the user had enough credits, then marked the space as rented without charging them, allowing free weekly rentals.
Honor ACC_INFINITE_CREDITS for staff accounts and add a contract test that keeps the charge before the rented state is assigned.
Reject monsterplant seed redemption when the caller does not own the placed seed. Without this guard, a user in the same room could trigger ToggleFloorItemEvent against another user's seed and have the server delete that item while creating the monsterplant pet for the attacker.
Add a contract test covering the ownership guard before createMonsterplant is reached.
Reject client-supplied room ids for self-moderation packets unless they match the caller's current room. This prevents users with saved rights or ownership in another room from muting, banning, or unbanning users remotely via crafted packets.
RoomUserBanEvent now also ignores invalid ban type values instead of letting valueOf throw through the message handler.
Add a contract test covering ban, mute, and unban current-room scoping.
Route console log level and logger columns through custom Logback converters so terminals with ANSI support get colored severity badges and compact colored class names.
Keep the same habbo.console.style auto/ansi/plain behavior as the startup splash, including plain fallback for non-interactive output, NO_COLOR, and legacy Windows console paths.
The file appenders keep their existing verbose patterns unchanged, so debug/error log files remain plain and grep-friendly.
Cover the level formatter, logger formatter, override behavior, and Logback pattern wiring with tests.