The :about / :info hotel-info title was hardcoded ("Arcturus Morningstar
4.1.0") and drifted from the real build. Now Emulator.version reads the
jar manifest's Implementation-Version (= ${project.version}, added via the
assembly plugin) and falls back to MAJOR.MINOR.BUILD only outside a jar.
Title becomes "Arcturus Morningstar Extended <version>" (e.g. 4.2.24).
-- VERY IMPORTANT !!!!
-- First check if the items_base ID and catalog_items ID is not in use !
-- After the SQL please go to the catalog_items table and change the page_id to where your BOTS are located
Server side of the soundboard feature:
- rooms.soundboard_enabled flag + soundboard_sounds table (self-bootstraps
at boot via SoundboardManager; migration 021 seeds up-front)
- SoundboardManager loads enabled sounds and persists the per-room flag
- SoundboardPlayEvent broadcasts the pressed pad to everyone in the room
- SoundboardSetEnabledEvent (owner/staff) toggles the room flag and
pushes refreshed settings
- settings (flag + sound list) sent on room enter, alongside YouTube
`HousekeepingResetUserPasswordEvent` was writing a SHA-256 hex digest
into `users.password`, but the Nitro auth path
(`SessionEndpoints` / `AccountChangeEndpoints` → `AuthHttpUtil.checkPassword`)
only does `BCrypt.checkpw`. A SHA-256 hex string doesn't start with
`$2…$`, so jbcrypt throws `IllegalArgumentException`, `checkPassword`
returns false, and operators saw "credenziali invalide" on every
account whose password had been reset from the in-client panel.
Switch to `BCrypt.hashpw(plain, BCrypt.gensalt(10))` — same idiom
already used by `SessionEndpoints.java:351` and
`AccountChangeEndpoints.java:98`. Cost 10 (vs 12 there) is fine for a
server-generated 12-char random password: gensalt(10) keeps the
operator-facing reset snappy and the output is identical-shape
(`$2a$…`) to what jbcrypt 0.4 already accepts.
Side-effects:
- drops the `MessageDigest` / `NoSuchAlgorithmException` /
`StandardCharsets` imports and the local `sha256Hex` helper
- repurposes the existing `housekeeping.error.hash_failed` key for
`BCrypt.gensalt`'s only failure mode (invalid cost / log_rounds out
of range) so the client error surface is unchanged
- updates the file javadoc to stop telling future readers to "swap the
MessageDigest constant" — Arcturus itself only verifies BCrypt
Companion of duckietm/Nitro-V3#157 (`feat/housekeeping-panel`). The
client/UI is untouched — packet 9200, the action-result reveal card,
the copy button, and the plaintext flow through `message` are all
unchanged.
Closes out the HK panel server-side surface.
* Incoming 9127 HousekeepingSendHotelAlertEvent — broadcast a
StaffAlertWithLinkComposer to every online user that hasn't
set blockStaffAlerts. Composed once, fanned out by reference;
empty-message guard returns `housekeeping.error.alert_empty`.
* Outgoing 9206 HousekeepingDashboardComposer + Incoming 9128
HousekeepingGetDashboardEvent — single round trip with the
aggregated counters: online / total users + active / total
rooms + pending support tickets + sanctions in the last 24h +
approximate emulator uptime + a version string. Active-rooms
is derived from RoomManager.getActiveRooms().getUserCount()>0
to avoid counting idle preloaded rooms. Peak online today /
all-time aren't tracked yet, so they currently echo the live
online count as a best-effort placeholder.
* Outgoing 9207 HousekeepingActionLogComposer + Incoming 9129
HousekeepingListActionLogEvent — read the optional
housekeeping_log table. If the table isn't there the SQL
exception is swallowed and an empty list goes back, so the
panel renders a no-entries view rather than crashing. Schema
is documented in the handler's javadoc; operators who want
audit run a single CREATE TABLE then the HK panel populates
from new writes (writes are a follow-up — every HK handler
will eventually append a row).
`mvn package` clean — the final fat jar lands in
Latest_Compiled_Version/ after the build finishes.
* Incoming 9117 HousekeepingGiveCreditsEvent — Habbo.giveCredits for
online (ships UserCreditsComposer) or UPDATE users.credits for offline.
* Incoming 9118 HousekeepingGiveCurrencyEvent — generic across the
non-credits currencies. currencyType 0 => duckets/pixels (givePixels),
5 => diamonds (givePoints(5,n)), anything else routes through
givePoints(type,n). Offline path INSERT ... ON DUPLICATE KEY UPDATE
users_currency.
* Incoming 9119 HousekeepingGrantItemEvent — batch-INSERT N rows into
the items table with item_id = base furni id. Capped at 100 per call
so a typo can't bury the DB. Online inventory refresh deferred — the
user picks the new items up on next hand-inventory open or relog.
* Incoming 9120 HousekeepingSetHcSubscriptionEvent — extends
users_settings.club_expire_timestamp by `days*86400`. Stacks on top
of the existing expiry if it's still in the future, otherwise starts
from now. days==0 clamps to now (effective cancel).
All four reuse HousekeepingActionResultComposer (no new outgoing
composer this slice).
`mvn compile` clean.
Eight new incoming handlers + two new outgoing composers cover the
full rooms-domain HK panel.
* Outgoing 9202 HousekeepingRoomDetailComposer — single room with a
leading `found` boolean. Writes the IHousekeepingRoom shape via a
static `appendRoomFields` that HousekeepingRoomListComposer shares.
* Outgoing 9203 HousekeepingRoomListComposer — `count` then N rooms.
Used for both find-by-name (exact match, up to 50) and the prefix
autocomplete dropdown (up to 8).
* Incoming 9110 HousekeepingFindRoomByIdEvent — loadRoom(id, false)
covers both the in-memory cache and the offline `SELECT * FROM rooms`
path. No `loadData` so HK doesn't pull furni/bots/pets just to
render a summary.
* Incoming 9111 HousekeepingSearchRoomsEvent — (query, exactMatch,
limit). Branches between `name = ?` and `name LIKE ?` so the same
wire packet serves both the autocomplete and the exact-find flows.
Hard-capped to 50.
* Incoming 9112 HousekeepingRoomStateEvent — (roomId, open). Toggles
Room.setState(OPEN | LOCKED) and persists via Room.save(). One
packet covers both the open and close API endpoints.
* Incoming 9113 HousekeepingMuteRoomEvent — (roomId, minutes). Room.
setMuted is a boolean, so minutes==0 unmutes and minutes>0 mutes.
A scheduled auto-unmute is left for a future slice; the wire field
is reserved.
* Incoming 9114 HousekeepingKickAllFromRoomEvent — Room.ejectAll().
* Incoming 9115 HousekeepingTransferRoomOwnershipEvent — UPDATEs both
rooms.owner_id and rooms.owner_name so the navigator cached name
doesn't go stale. Validates the new owner exists via
HabboManager.getHabboInfo before touching the row.
* Incoming 9116 HousekeepingDeleteRoomEvent — ejectAll + dispose +
uncacheRoom + DELETE FROM rooms, mirroring the minimum-viable
subset of RequestDeleteRoomEvent. Pets/guild/custom-layout cleanup
is skipped on this slice (orphans don't crash the emulator).
`mvn compile` clean.
Closes out the users-domain HK actions.
* Incoming 9107 HousekeepingSetUserRankEvent — (userId, rankId).
Validates the rank exists in `permission_ranks`, UPDATEs users.rank,
and if the target is online rebinds their HabboInfo to the fresh
Rank object and ships a UserPermissionsComposer so server-side
hasPermission() and the client's useHasPermission(key) consumers
re-render against the new permissions without a relog.
* Incoming 9108 HousekeepingTradeLockUserEvent — (userId, hours,
reason). Writes `users_settings.trade_locked_until = now + hours*3600`
so the lock survives logout/login. Online targets also get their
in-memory HabboStats.allowTrade cleared and an optional alert.
* Incoming 9109 HousekeepingResetUserPasswordEvent — (userId).
Generates a 12-char alphanumeric (SecureRandom over a curated
ambiguity-free alphabet), writes its SHA-256 hex to users.password
(the column is varchar(64) — already sized for SHA-256 hex) and
blanks auth_ticket so any live SSO ticket can't bypass the reset.
Plaintext is returned to the operator in the action-result
message — they relay it out-of-band. If your CMS uses a hash other
than SHA-256, swap the MessageDigest.getInstance constant.
`mvn compile` clean.
Every HK action handler returned bare error slugs (\"invalid_input\",
\"user_offline\", \"no_active_ban\", \"target_unkickable\", \"ban_failed\",
\"user_not_found\") in HousekeepingActionResultComposer.message. The
client's `localizeOrPassthrough` only treats a value as a translation
key when it contains a dot, so those bare slugs were rendered raw in
the status banner and the toast — ugly and untranslatable.
Re-prefix all error messages with `housekeeping.error.` so the EN +
IT dictionaries can resolve them. Success path is unchanged (server
sends empty string, client falls back to `housekeeping.action.success`).
Companion dictionary entries land on the client side.
Incoming 9106 HousekeepingForceDisconnectUserEvent — (userId, reason).
Sends the optional reason as a Habbo.alert, dispatches the action ack
BEFORE calling target.disconnect() so the result lands on the wire
before the target's socket closes, then drops the session. Online-only;
offline target returns `user_offline`.
`mvn compile` clean.
Incoming 9104 HousekeepingMuteUserEvent — (userId, reason, minutes).
Unlike ModToolSanctionMute which takes a fixed-bucket minutes arg
from a CFH context, this one applies an arbitrary in-session mute via
Habbo.mute(seconds, false). Mute is online-only (the live Habbo object
holds the remaining seconds), so an offline target returns ok=false
with `user_offline`. The reason string, if non-empty, is delivered via
Habbo.alert so the muted user sees why.
Incoming 9105 HousekeepingKickUserEvent — (userId, reason). Replicates
the ModToolManager.kick body (leave room + alert) locally so HK doesn't
piggyback on ACC_SUPPORTTOOL the way ModToolManager.kick does — keeps
the permission model `acc_housekeeping`-only. Respects ACC_UNKICKABLE
the same way the legacy path does.
Both reuse HousekeepingActionResultComposer with their own actionKey
(user.mute / user.kick).
`mvn compile` clean.
Incoming 9103 HousekeepingUnbanUserEvent — reads userId, resolves
the username via HabboManager.getHabboInfo(int) (covers both online
and offline paths in one call), then dispatches to
ModToolManager.unban(username) which clears all active rows from
the `bans` table for that user.
Reuses HousekeepingActionResultComposer with actionKey `user.unban`.
If the user never had an active ban the SQL UPDATE matches zero rows
and the handler responds with `ok: false, message: 'no_active_ban'`
— from a UI standpoint that's a no-op, not an error.
`mvn compile` clean.
Adds two new packets:
* Incoming 9102 HousekeepingBanUserEvent — reads (userId, reason,
hours). Unlike ModToolSanctionBanEvent which only accepts the four
fixed Habbo-protocol banType buckets (18h / 7d / 30d / 100y), this
one converts the hours arg straight to seconds and feeds them into
ModToolManager.ban with ModToolBanType.ACCOUNT and cfhTopic=0.
Duration is clamped to 100 years to keep it inside `int` range.
* Outgoing 9201 HousekeepingActionResultComposer — generic ack
for any HK action (ban / mute / kick / give-credits / room-close /
…). Wire shape is (actionKey, ok, actionId, message). The
actionKey lets the client filter multiple in-flight actions to
the right Promise via `accept`, so concurrent admin operations
don't cross-resolve.
actionId here is the target user id because ModToolBan doesn't
expose the `bans` autoinc id on the object — there's a TODO to swap
this for a dedicated housekeeping_log row id once that table goes in.
Same ACC_HOUSEKEEPING permission gate as the find-user packets, so
operators only need to grant the permission once.
`mvn compile` clean.
Adds Incoming 9101 HousekeepingFindUserByIdEvent, which replies on
the existing HousekeepingUserDetailComposer (Outgoing 9200) — the
composer is shape-agnostic about how the lookup was issued, so the
two find-* handlers share the same response packet.
The by-id handler uses HabboManager.getHabboInfo(int) directly, which
already covers both the online (in-memory hashmap) and offline (SQL
LIMIT 1 on users) branches in one call. The by-name path still has
to do online + offline manually because the equivalent String overload
doesn't exist as an instance method, only as a static.
Also introduces Permission.ACC_HOUSEKEEPING ("acc_housekeeping") so
the in-client housekeeping panel doesn't piggyback on ACC_SUPPORTTOOL.
Both HK handlers now gate on the new permission; the toolbar UI on
the client side was already checking `acc_housekeeping`, so this
closes the loop. Operators must add the permission to
permission_definitions for the desired rank:
INSERT INTO permission_definitions
(permission_key, max_value, comment,
rank_1, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7)
VALUES
('acc_housekeeping', 1,
'Allows access to the in-client Housekeeping admin panel ...',
0, 0, 0, 0, 0, 0, 1)
ON DUPLICATE KEY UPDATE rank_7 = 1, comment = VALUES(comment);
`mvn package` clean (Habbo-4.2.12-jar-with-dependencies.jar).
First wire-level packet for the in-client housekeeping admin panel.
Adds an incoming handler (Incoming 9100) that resolves a username to
a HabboInfo (online via the HabboManager hashmap, offline via the
users-table SQL fallback) and replies with HousekeepingUserDetail
(Outgoing 9200) containing id / username / motto / look / rank / rank
name / online / lastOnline / credits / duckets / diamonds / email /
ipLogin / isBanned. Active-mute and active-trade-lock are written as
trailing booleans (currently false) so the renderer parser can pick
them up later behind a bytesAvailable guard once those manager APIs
are surfaced offline-side.
Permission gate is ACC_SUPPORTTOOL — same one ModTools already uses.
Avoids adding a new column to the permissions table on this slice; a
dedicated ACC_HOUSEKEEPING permission can be introduced later when
the destructive HK operations (give-credits, delete-room, reset-pwd)
go in.
Reserves the 9100..9199 / 9200..9299 ID blocks for the rest of the
HK packet surface (search-prefix, find-by-id, ban/mute/kick with
arbitrary duration, room actions, economy, catalog admin, dashboard
aggregate, audit log read).
`mvn compile` clean on Habbo 4.2.12.