feat(session): rank metadata in UserPermissions snapshot

Extend the `UserPermissionsEvent` parser and `IUserDataSnapshot` with
rank metadata mirrored from the Arcturus `permission_ranks` table:
rankId, rankName, rankBadge, rankPrefix, rankPrefixColor.

The new fields are appended to the wire payload AFTER the existing
[clubLevel, securityLevel, isAmbassador] triple. The parser guards
the trailing block with `if(!wrapper.bytesAvailable) return true;`
so older emulators (that don't write the extension) keep working —
the snapshot just exposes the defaults (rankId=0, empty strings) in
that case.

`SessionDataManager.onUserPermissionsEvent` stores the values; the
snapshot builder includes them; existing
`invalidateUserDataSnapshot()` semantics flow through unchanged, so
a runtime promote/demote (via `HabboManager.setRank` →
`UserPermissionsComposer`) auto-flips the React-side
`useUserRank()` / `useHasRankLevel()` / `useIsRank()` consumers in
the Nitro-V3 client.

Companion changes:
- Arcturus-Morningstar-Extended:
  `UserPermissionsComposer.composeInternal()` now appends the 5
  extra fields (pending operator commit; see
  ../Arcturus-Morningstar-Extended/Emulator/src/main/java/
  com/eu/habbo/messages/outgoing/users/UserPermissionsComposer.java).
- Nitro-V3:
  `useSessionSnapshots.ts` exposes the new family
  (useUserRank / useHasRankLevel / useIsRank), replacing the
  SecurityLevel-based wrappers (useIsModerator etc.) that hardcoded
  the renderer enum names — those don't match the operator's
  `permission_ranks.rank_name` column.

Verification: tsgo clean, vitest 138/138.
This commit is contained in:
simoleo89
2026-05-19 18:37:57 +02:00
parent ce561bd5b3
commit 87e67d58df
3 changed files with 77 additions and 4 deletions
@@ -5,12 +5,22 @@ export class UserPermissionsParser implements IMessageParser
private _clubLevel: number;
private _securityLevel: number;
private _isAmbassador: boolean;
private _rankId: number;
private _rankName: string;
private _rankBadge: string;
private _rankPrefix: string;
private _rankPrefixColor: string;
public flush(): boolean
{
this._clubLevel = 0;
this._securityLevel = 0;
this._isAmbassador = false;
this._rankId = 0;
this._rankName = '';
this._rankBadge = '';
this._rankPrefix = '';
this._rankPrefixColor = '';
return true;
}
@@ -23,6 +33,18 @@ export class UserPermissionsParser implements IMessageParser
this._securityLevel = wrapper.readInt();
this._isAmbassador = wrapper.readBoolean();
// Optional trailing block (Arcturus-Morningstar-Extended ≥ 4.2.10):
// rank metadata appended in a backward-compatible way. Older
// emulators don't write these bytes so we keep the defaults
// from flush().
if(!wrapper.bytesAvailable) return true;
this._rankId = wrapper.readInt();
this._rankName = wrapper.readString();
this._rankBadge = wrapper.readString();
this._rankPrefix = wrapper.readString();
this._rankPrefixColor = wrapper.readString();
return true;
}
@@ -40,4 +62,29 @@ export class UserPermissionsParser implements IMessageParser
{
return this._isAmbassador;
}
public get rankId(): number
{
return this._rankId;
}
public get rankName(): string
{
return this._rankName;
}
public get rankBadge(): string
{
return this._rankBadge;
}
public get rankPrefix(): string
{
return this._rankPrefix;
}
public get rankPrefixColor(): string
{
return this._rankPrefixColor;
}
}