`PermissionsManager.reload()` rebuilds the rank table from
`permission_ranks` + `permission_definitions`, but every Habbo
currently online still holds a reference to the OLD `Rank` object
on `HabboInfo.rank`. Server-side `hasPermission()` therefore keeps
returning stale results, and any Nitro client that reads permission
state from the wire keeps gating UI on the map shipped at login
— until a relogin or `:give_rank` forces a per-user refresh.
Extend the existing `UpdatePermissionsCommand` so after `reload()`
it:
1. Iterates the online Habbos via `HabboManager.getOnlineHabbos()`.
2. Re-binds each one's `HabboInfo.rank` to the FRESH `Rank` object
returned by `PermissionsManager.getRank(currentRankId)`. Falls
back to rank 1 if the admin deleted the rank from
`permission_ranks` between sessions, so the user is never left
with a null `Rank` reference.
3. Sends a fresh `UserPermissionsComposer` to each client.
With the companion composer extension PR also merged, this
broadcasts the rank metadata + resolved permission map runtime —
the Nitro React-side `useHasPermission(key)` / `useUserRank()`
consumers re-render against the freshly-loaded tables without
requiring an F5.
The whisper feedback now reports how many connected users were
refreshed, useful for ops feedback after a large `permission_ranks`
edit.
Defensive null guards on habbo / habboInfo / client survive
transient state during the broadcast (e.g. a user disconnecting
mid-iteration).
Backward-compatible wire extension of `UserPermissionsComposer`
(header 411) that lets Nitro clients display per-deployment rank
info and drive UI gates against the actual `permission_definitions`
table instead of hardcoded SecurityLevel constants.
Wire layout after this change (each trailing block is guarded by
`bytesAvailable` on the client side so older Nitro builds keep
parsing the prefix and stop):
int clubLevel
int rank.level // mapped to securityLevel on the client
bool isAmbassador // existing ACC_AMBASSADOR flag
--- new: rank metadata ---
int rank.id
string rank.name // permission_ranks.rank_name
string rank.badge
string rank.prefix
string rank.prefixColor
--- new: resolved permission map ---
int count
loop: string permission_key + int value // 1 = ALLOWED, 2 = ROOM_OWNER
The permission map is the union of:
* Rank entries whose `PermissionSetting != DISALLOWED` (value 1
for ALLOWED, 2 for ROOM_OWNER).
* For every rank-DISALLOWED key, each installed
`HabboPlugin.hasPermission(habbo, key)` is consulted; if any
plugin grants the permission, the key lands on the wire with
value 1 (plugins do not have a ROOM_OWNER concept).
Iterating `rank.getPermissions().keySet()` covers every key in
`permission_definitions` because `PermissionsManager.loadPermissionsNormalized()`
calls `rank.setPermission(key, ...)` for every row of the table —
including DISALLOWED ones. Custom keys a plugin invents that are
not in `permission_definitions` stay invisible (there is no
enumeration API on `HabboPlugin` to discover them); this is a rare
case documented in the class-level Javadoc.
The result is a client-side permission map whose semantics match
exactly what `PermissionsManager.hasPermission(habbo, key)` would
return server-side — including plugin-granted permissions, which
were invisible to the client before.
Performance: at login the loop is O(N keys × P plugins), with
N ≈ 200 (size of permission_definitions) and P typically 1-5.
`HabboPlugin.hasPermission` is O(1) hashset lookups in
real-world implementations. Sub-millisecond at login, and the
composer is only sent at login + `HabboManager.setRank` +
`:update_permissions` broadcast.
Backward compatibility: all new fields are appended in tail
position with `bytesAvailable` guards on the parser side, so:
* existing Nitro clients keep parsing only the prefix and ignore
the trailing bytes (no error, no behavior change);
* new Nitro clients with the matching parser extension expose the
extra data via `IUserDataSnapshot` snapshot getters and the
React-side `useUserRank()` / `useHasPermission(key)` /
`useUserPermissions()` hooks (see companion PRs on
`duckietm/Nitro_Render_V3` and `duckietm/Nitro-V3`).
Pairs with the CMS-side change introducing auth_ticket_expires_at (60s
expiry written on every ticket issuance). Without an emulator-side
verification the column was advisory only — this commit gates every
SELECT that resolves a user by auth_ticket on
auth_ticket = ?
AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW())
The NULL branch preserves backward-compatibility: CMS deployments that
do not yet populate the column keep working exactly like before
(every ticket passes the WHERE clause as soon as auth_ticket matches),
and the TTL takes effect automatically the moment a CMS starts writing
the expiry value.
Five SELECTs touched:
- SessionEndpoints.java (cms-issued SSO + remember-token flow)
- HabboManager.loadHabbo (game client login by ticket)
- SecureLoginEvent (legacy handshake path)
DB schema delivered both ways:
- Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql:
idempotent ALTER, skips if column already present (information_schema
guard so re-running the bundle is safe).
- Default Database/FullDatabase.sql: column added to the `users` table
definition for fresh installs.
Bumps the emulator version to 4.2.7.
Aligns the :furnidata in-game admin command with the split-aware gamedata
layout shipped by the Nitro V3 client. FurniDataManager now resolves the
furnidata source through three accepted shapes:
- legacy single-file path (filesystem or http URL ending in .json/.json5)
- split-mode directory (URL ending with '/') — walks core/custom/seasonal
tiers via manifest.json5 files and merges by item id, with later tiers
overriding earlier ones (same semantics as the client-side loader)
- fallback to furni.editor.asset.base.path when the renderer config is
missing or contains an unresolved placeholder
Adds a small JSON5 sanitiser (stripJson5) that removes line and block
comments and trailing commas before handing the content to Gson, so both
the renderer config and the split-mode files can be JSON or JSON5
without pulling in a JSON5 dependency. String contents are preserved
verbatim — comment-looking substrings inside strings (e.g. URLs) are
not touched.
Bumps the emulator version to 4.2.6.