Skip to content

Guild role assignment for assistants and battle masters - #955

Open
eduardosmaniotto wants to merge 4 commits into
MUnique:masterfrom
eduardosmaniotto:feature/guild-role-assignment
Open

eduardosmaniotto wants to merge 4 commits into
MUnique:masterfrom
eduardosmaniotto:feature/guild-role-assignment

Conversation

@eduardosmaniotto

Copy link
Copy Markdown
Contributor

Summary

  • Guild role assignment (new): handles C1 E1 GuildRoleAssignRequest.
    GuildRoleAssignHandlerPlugIn maps the wire role to GuildPosition
    (leadership transfer and unknown roles rejected, undocumented Type
    byte ignored) and GuildRoleAssignAction enforces master-only access
    with same-guild online target resolution. GuildServer.ChangeGuildMemberPositionAsync
    now publishes via the existing AssignGuildToPlayerAsync, so no new packets
    or publisher members.
  • Alliance request crash (fix): GuildRelationshipChangeAction
    deconstructed a null GuildData on every failed validation, throwing
    NullReferenceException instead of sending the result. Both call
    sites now check success/null before deconstructing.
  • Guild list order (fix): GuildListRequestAction sorts members by
    rank (master, assistant, battle master, normal) then name, since the
    client renders them in received order.

Implements the previously unhandled C1 E1 GuildRoleAssignRequest: only
the guild master may promote/demote members to AssistantMaster,
BattleMaster or NormalMember (no leadership transfer). The guild server
now publishes position changes so members update without relog.

Also fixes a NullReferenceException in GuildRelationshipChangeAction
where failed validations crashed instead of returning the result, and
sorts the guild list by rank (master, assistant, battle master, normal)
then name.

@sven-n sven-n left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review of the guild role assignment changes: 2 findings, both in GuildServer.ChangeGuildMemberPositionAsync. The alliance null-deconstruction fix and the guild list ordering look correct.


Generated by Claude Code

Comment thread src/GuildServer/GuildServer.cs Outdated
await guild.DatabaseContext.SaveChangesAsync().ConfigureAwait(false);
var listEntry = guild.Members[characterId];
listEntry.PlayerPosition = role;
await this._changePublisher.AssignGuildToPlayerAsync(listEntry.ServerId, listEntry.PlayerName, new GuildMemberStatus(guildId, role)).ConfigureAwait(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reusing AssignGuildToPlayerAsync for a position change registers the member a second time in the game server's guild list.

GameServer.AssignGuildToPlayerAsync ends with Context.RegisterGuildMemberAsync(player), and GameServerContext.RegisterGuildMemberAsync does a plain guildList.Add(guildMember) on a LockableList<Player> : List<Player> — no duplicate check. The member was already registered when they entered the world (PlayerEnteredGameAsyncAssignGuildToPlayerAsync).

Scenario: guild master promotes an online member to battle master. From then on ForEachGuildPlayerAsync iterates that player twice, so every guild chat message (GameServer.cs:227) and every guild-wide action (/guildmove, /guilddisconnect, guild war messages) is delivered to them twice. On logout UnregisterGuildMemberAsyncList.Remove removes only the first occurrence, so a stale Player reference stays in the list and keeps receiving guild broadcasts (and leaks). Each further promotion/demotion adds another duplicate.

A dedicated publisher member (or an idempotent registration / a status-only update path that doesn't re-register) is needed here.


Generated by Claude Code

Comment thread src/GuildServer/GuildServer.cs Outdated
if (guildMember != null)
listEntry.PlayerPosition = role;
if (listEntry.PlayerName is not null)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard only checks PlayerName is not null, but PlayerName is kept after logout while SetServerId(..., OfflineServerId) sets ServerId to 0xFF (GuildMemberLeftGameAsync). So for an offline member whose name was cached the publish still happens with serverId == 0xFF: GuildChangeToGameServerPublisher silently drops it, and the Dapr GuildChangePublisher invokes gameServer256 and logs an error on every call. Reachable when the target logs out between the action's online check and the guild server processing the change (and for any other future caller of this public API).

Suggested change
{
if (listEntry.PlayerName is not null && listEntry.ServerId != OfflineServerId)

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants