- resolveHabbo() falls back to a hotel-wide online lookup so a direct @nick
mention reaches the target even when they are in a different room (was
resolved only within the sender's room).
- HabboMention now carries the sender figure (live from the sender Habbo,
history from a users.look JOIN); MentionReceived/MentionsList composers
append it so the client can render the sender avatar in the notification.
- 009: add users_settings.mentions_enabled / mass_mentions_enabled columns
so :disablementions / :disablemassmentions actually persist.
Thanks to @Bop:
There's a group bug where you can accept anyone into a group within MS. There's no packet validation for accepting members if the group is invite only.
This is crucial because if you allow users to have rights who are group members, your rooms can be trashed. AKA YOUR EVENT ROOMS
The prize editor could only update existing rows; savePrize was UPDATE-only,
so the admin panel had no way to add a new slice or remove an old one.
- WheelManager.savePrize now takes a sortOrder and inserts when id <= 0
(returning the generated id) or updates + re-enables when id > 0, so a
previously removed prize can be brought back. sort_order is persisted to
match the editor's display order.
- New WheelManager.disablePrizesNotIn(keptIds) soft-deletes (enabled = 0)
any prize absent from the saved authoritative list. Non-destructive: rows
stay in the table and loadPrizes already filters enabled = 1.
- WheelAdminSavePrizesEvent collects the saved ids and disables the rest
before reloading.
No schema change (wheel_prizes already has enabled + sort_order) and no
packet change (id = 0 / omission express insert / delete on the existing
wire). Pairs with the Nitro-V3 client editor add/remove buttons.
getBubble() fell back to NORMAL (bubble 0) for any id not in the registered
BUBBLES map, so custom client-side chat bubbles (e.g. ids 253+) rendered as
the default bubble for everyone. Now unknown positive ids (<=1000) pass
through as a transient bubble carrying that id, so the server relays it and
clients render their own .bubble-<id> style. No need to enumerate each one.
The Nitro client already sends a strong machine fingerprint (Thumbmark,
"IID-<hash>") via the UniqueID packet (header 2490 -> MachineIDEvent), but
the emulator only stored it on the GameClient and never copied it onto the
Habbo's HabboInfo, so it was never written to users.machine_id. As a result
machine/super bans (which read users.machine_id) matched nobody.
- MachineIDEvent: when the fingerprint arrives and the Habbo is already
loaded, copy it onto HabboInfo and persist (run the Habbo save).
- SecureLoginEvent: if the fingerprint arrived before login, copy it onto
HabboInfo right before the login save.
This makes machine/super bans effective without changing the client.
- RCON: add updatewheel/updatesoundboard (reload WheelManager/SoundboardManager live) so the CMS admin pages apply changes without an emulator restart.
- SSO ticket is no longer single-use: loadHabbo, session-resume and performFullDisconnect no longer clear auth_ticket. Behind Cloudflare the WS is dropped and the client retries with the same ticket; clearing it caused 'non-existing SSO token' and the 'refresh twice' / kicked-on-reconnect symptoms. The ticket now lives until its TTL (auth_ticket_expires_at), is overwritten by the CMS on the next /client load, or cleared on logout.
- SessionResume: restoreSsoTicket only restores when auth_ticket is empty (don't clobber a fresh CMS ticket); GameClient.dispose only parks/disconnects when the habbo is still attached to this client (a fast reconnect may have re-attached it to the new connection).
CatalogManager.loadFurnitureValues() (rare-values feature) iterates every catalog page during GameEnvironment.load(); for a RoomBundleLayout this calls getRoomManager().loadRoom(), but RoomManager is constructed after CatalogManager so getRoomManager() returns null -> NullPointerException -> boot aborts. Null-guard the room load so the bundle resolves lazily at runtime instead.
InteractionRoomAds now carries a `scale` default value (100) alongside
imageUrl/clickUrl/offsetX/Y/Z, so the image zoom set in the client's
position editor is stored and broadcast like the other branding fields.
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.