You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
feat(messages): extend UserPermissionsComposer with rank metadata + resolved permission map
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`).
This commit is contained in:
+106
-1
@@ -1,11 +1,57 @@
|
|||||||
package com.eu.habbo.messages.outgoing.users;
|
package com.eu.habbo.messages.outgoing.users;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.PermissionSetting;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Rank;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
import com.eu.habbo.messages.ServerMessage;
|
import com.eu.habbo.messages.ServerMessage;
|
||||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||||
|
import com.eu.habbo.plugin.HabboPlugin;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the full per-user permission state to the connected client.
|
||||||
|
*
|
||||||
|
* Wire layout (each trailing block is guarded by `bytesAvailable` on
|
||||||
|
* the client so older Nitro builds keep parsing the prefix and stop):
|
||||||
|
*
|
||||||
|
* int clubLevel
|
||||||
|
* int rank.level // mapped to securityLevel on the client
|
||||||
|
* bool isAmbassador // legacy ACC_AMBASSADOR flag
|
||||||
|
* --- rank metadata (Arcturus ≥ 4.2.10) ---
|
||||||
|
* int rank.id
|
||||||
|
* string rank.name // permission_ranks.rank_name
|
||||||
|
* string rank.badge
|
||||||
|
* string rank.prefix
|
||||||
|
* string rank.prefixColor
|
||||||
|
* --- resolved permission map (Arcturus ≥ 4.2.10) ---
|
||||||
|
* int count
|
||||||
|
* loop: string permission_key + int value // 1 = ALLOWED, 2 = ROOM_OWNER
|
||||||
|
*
|
||||||
|
* The map is the union of:
|
||||||
|
* • rank entries with `PermissionSetting != DISALLOWED` — same data
|
||||||
|
* `Rank.hasPermission(key, isRoomOwner)` reads server-side.
|
||||||
|
* • plugin grants — for each key the rank doesn't allow, every
|
||||||
|
* installed `HabboPlugin.hasPermission(habbo, key)` is consulted;
|
||||||
|
* if any plugin grants it, the key lands on the wire with value 1
|
||||||
|
* (plugins don't have a ROOM_OWNER concept).
|
||||||
|
*
|
||||||
|
* The React-side `useHasPermission(key)` / `useUserPermissions()`
|
||||||
|
* consumers read the map directly so UI gates follow the same
|
||||||
|
* semantics as `PermissionsManager.hasPermission(habbo, key)`
|
||||||
|
* server-side — including plugin-granted permissions, which were
|
||||||
|
* invisible to the client before this commit.
|
||||||
|
*
|
||||||
|
* Two send points:
|
||||||
|
* 1. End of `SecureLoginEvent` — client receives the full state once.
|
||||||
|
* 2. Inside `HabboManager.setRank` — runtime promote/demote refresh.
|
||||||
|
* 3. Inside `UpdatePermissionsCommand` — broadcast after
|
||||||
|
* `:update_permissions` reloads the tables at runtime.
|
||||||
|
*/
|
||||||
public class UserPermissionsComposer extends MessageComposer {
|
public class UserPermissionsComposer extends MessageComposer {
|
||||||
private final int clubLevel;
|
private final int clubLevel;
|
||||||
|
|
||||||
@@ -20,11 +66,70 @@ public class UserPermissionsComposer extends MessageComposer {
|
|||||||
protected ServerMessage composeInternal() {
|
protected ServerMessage composeInternal() {
|
||||||
this.response.init(Outgoing.UserPermissionsComposer);
|
this.response.init(Outgoing.UserPermissionsComposer);
|
||||||
this.response.appendInt(this.clubLevel);
|
this.response.appendInt(this.clubLevel);
|
||||||
this.response.appendInt(this.habbo.getHabboInfo().getRank().getLevel());
|
|
||||||
|
Rank rank = this.habbo.getHabboInfo().getRank();
|
||||||
|
|
||||||
|
this.response.appendInt(rank.getLevel());
|
||||||
this.response.appendBoolean(this.habbo.hasPermission(Permission.ACC_AMBASSADOR));
|
this.response.appendBoolean(this.habbo.hasPermission(Permission.ACC_AMBASSADOR));
|
||||||
|
|
||||||
|
// Rank metadata
|
||||||
|
this.response.appendInt(rank.getId());
|
||||||
|
this.response.appendString(rank.getName());
|
||||||
|
this.response.appendString(rank.getBadge());
|
||||||
|
this.response.appendString(rank.getPrefix());
|
||||||
|
this.response.appendString(rank.getPrefixColor());
|
||||||
|
|
||||||
|
// Build the resolved permission map. Walk rank.getPermissions()
|
||||||
|
// (Rank.permissions has every row from permission_definitions
|
||||||
|
// because PermissionsManager.loadPermissionsNormalized() calls
|
||||||
|
// rank.setPermission(key, …) for every key, including DISALLOWED
|
||||||
|
// ones) and emit the final value per key:
|
||||||
|
// ALLOWED → 1
|
||||||
|
// ROOM_OWNER → 2
|
||||||
|
// DISALLOWED + plugin yes → 1
|
||||||
|
// DISALLOWED + plugin no → omit
|
||||||
|
//
|
||||||
|
// LinkedHashMap preserves the alphabetical order that the rank
|
||||||
|
// table was populated with, which is helpful for snapshotting
|
||||||
|
// and grep'ing wire dumps.
|
||||||
|
Map<String, Permission> rankPermissions = rank.getPermissions();
|
||||||
|
Map<String, Integer> resolved = new LinkedHashMap<>(rankPermissions.size());
|
||||||
|
|
||||||
|
for (Map.Entry<String, Permission> entry : rankPermissions.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Permission rankPerm = entry.getValue();
|
||||||
|
|
||||||
|
if (rankPerm.setting == PermissionSetting.ALLOWED) {
|
||||||
|
resolved.put(key, 1);
|
||||||
|
} else if (rankPerm.setting == PermissionSetting.ROOM_OWNER) {
|
||||||
|
resolved.put(key, 2);
|
||||||
|
} else if (this.anyPluginGrants(key)) {
|
||||||
|
resolved.put(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugins may also grant CUSTOM keys that aren't in
|
||||||
|
// permission_definitions — rare but legal. There's no enumeration
|
||||||
|
// API on HabboPlugin to discover them, so they stay invisible
|
||||||
|
// here. Document the limitation rather than over-engineer.
|
||||||
|
|
||||||
|
this.response.appendInt(resolved.size());
|
||||||
|
|
||||||
|
for (Map.Entry<String, Integer> entry : resolved.entrySet()) {
|
||||||
|
this.response.appendString(entry.getKey());
|
||||||
|
this.response.appendInt(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
return this.response;
|
return this.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean anyPluginGrants(String key) {
|
||||||
|
for (HabboPlugin plugin : Emulator.getPluginManager().getPlugins()) {
|
||||||
|
if (plugin.hasPermission(this.habbo, key)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public int getClubLevel() {
|
public int getClubLevel() {
|
||||||
return clubLevel;
|
return clubLevel;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user