Skip to content

Guild Alliance System #720

@sven-n

Description

@sven-n

Summary

Implement the Guild Alliance system, which allows multiple guilds to form an alliance under an alliance master guild. This is a prerequisite for Castle Siege, where entire alliances participate as a unit. The system also includes guild hostility (rival relationships).

Prerequisites

None — this is the foundational phase.

Background

OpenMU already has partial support for alliances:

  • Interfaces.Guild has AllianceGuild and Hostility navigation properties.
  • DataModel.Entities.Guild extends Interfaces.Guild with Members.
  • Alliance chat routes through IEventPublisher.AllianceMessageAsync.
  • IGameServerContext.ForEachAlliancePlayerAsync is declared but needs implementation.
  • Client→Server packet docs exist: C1-E6-GuildRelationshipChangeResponse, C1-E9-RequestAllianceList, C1-EB-01-RemoveAllianceGuildRequest.

What's missing is the actual server-side CRUD logic for alliances and the corresponding message handlers.

Requirements

1. IGuildServer Extensions (Interfaces/IGuildServer.cs)

Add these methods to the IGuildServer interface:

ValueTask<bool> CreateAllianceAsync(uint masterGuildId, uint targetGuildId);
ValueTask<bool> RemoveAllianceGuildAsync(uint masterGuildId, uint targetGuildId);
ValueTask<bool> DisbandAllianceAsync(uint masterGuildId);
ValueTask<IImmutableList<AllianceGuildEntry>> GetAllianceGuildsAsync(uint guildId);
ValueTask<bool> IsAllianceMasterAsync(uint guildId);
ValueTask<uint> GetAllianceMasterIdAsync(uint guildId);
ValueTask<bool> SetHostilityAsync(uint guildId, uint targetGuildId, bool create);
ValueTask<GuildRelationship> GetGuildRelationshipAsync(uint guild1, uint guild2);

Add supporting types:

public class AllianceGuildEntry
{
    public string GuildName { get; set; }
    public uint GuildId { get; set; }
}

public enum GuildRelationship : byte
{
    None = 0,
    Union = 1,
    Rival = 2,
}

2. GuildPosition Extension (Interfaces/GuildPosition.cs)

Add AssistantMaster value (needed for castle siege NPC management):

public enum GuildPosition : byte
{
    Undefined,
    NormalMember,
    GuildMaster,
    BattleMaster,
    AssistantMaster, // NEW
}

3. GuildServer Implementation (GuildServer/GuildServer.cs)

Implement the alliance CRUD methods:

  • CreateAllianceAsync: Set AllianceGuild on both guilds. The requesting guild becomes the alliance master (self-referencing AllianceGuild). The target guild's AllianceGuild is set to the master. Enforce configurable max alliance size (default 5). Both guild masters must agree (request/response flow similar to guild join).
  • RemoveAllianceGuildAsync: Only the alliance master can remove member guilds. Clear the removed guild's AllianceGuild.
  • DisbandAllianceAsync: Clear AllianceGuild on all member guilds.
  • GetAllianceGuildsAsync: Find all guilds in the _guildDictionary whose Guild.AllianceGuild matches the given guild's alliance master. Also query the database for offline alliance members.
  • IsAllianceMasterAsync: A guild is the alliance master when guild.AllianceGuild points to itself.
  • GetAllianceMasterIdAsync: Return the runtime uint ID of the alliance master guild.
  • SetHostilityAsync: Set or clear Guild.Hostility on the requesting guild.
  • GetGuildRelationshipAsync: Return Union if both guilds share the same AllianceGuild, Rival if hostility exists (transitively through alliances), None otherwise.

Hostility transitivity rule: If any guild in Alliance A has Hostility pointing to any guild in Alliance B, all members of both alliances are rivals.

4. IGuildChangePublisher Extensions (GuildServer/IGuildChangePublisher.cs)

Add methods to broadcast alliance changes to game servers:

ValueTask AllianceCreatedAsync(uint masterGuildId, uint memberGuildId);
ValueTask AllianceGuildRemovedAsync(uint masterGuildId, uint memberGuildId);
ValueTask AllianceDisbandedAsync(uint masterGuildId);

Implement in GuildChangeToGameServerPublisher.cs.

5. Player Action (GameLogic/PlayerActions/Guild/GuildRelationshipChangeAction.cs) — New file

Validates the request:

  • Player must be a guild master.
  • For alliance creation: target guild must exist, not already in an alliance, max size not exceeded.
  • For alliance removal: player must be the alliance master.
  • For hostility: target guild must exist.

Calls the appropriate IGuildServer method via IGameServerContext.GuildServer.

6. Message Handlers — New files in GameServer/MessageHandler/Guild/

Follow the pattern of existing handlers (e.g., GuildRequestHandlerPlugIn.cs):

  • GuildRelationshipChangeHandlerPlugIn: Handles C1-E5 (or the correct headcode for guild relationship requests). Parses the packet, calls GuildRelationshipChangeAction.
  • AllianceListRequestHandlerPlugIn: Handles C1-E9. Calls IGuildServer.GetAllianceGuildsAsync, sends the list via the view plug-in.
  • RemoveAllianceGuildHandlerPlugIn: Handles C1-EB-01. Calls the remove action.

Each handler needs:

  • [PlugIn] attribute with [Guid("...")] (generate new GUIDs).
  • [Display] attribute with resource strings.
  • Implement IPacketHandlerPlugIn or the appropriate sub-code handler interface.

7. Remote View Plug-ins — New files in GameServer/RemoteView/Guild/

  • ShowGuildRelationshipChangeResultPlugIn: Sends the alliance/hostility creation result.
  • ShowAllianceListPlugIn: Sends the list of alliance guilds.

Follow existing patterns (e.g., ShowGuildWarResultPlugIn.cs).

8. View Interfaces — New files in GameLogic/Views/Guild/

  • IGuildRelationshipChangeResultPlugIn
  • IShowAllianceListPlugIn

9. Network Packets

Verify and complete XML definitions in:

  • Network/Packets/ClientToServer/ClientToServerPackets.xml — Ensure GuildRelationshipChangeRequest, RequestAllianceList, RemoveAllianceGuildRequest are fully defined.
  • Network/Packets/ServerToClient/ServerToClientPackets.xml — Add GuildRelationshipChangeResponse, AllianceList response.

Refer to existing packet docs in docs/Packets/C1-E6-*.md, C1-E9-*.md, C1-EB-*.md for field layouts.

10. IGameServerContext.ForEachAlliancePlayerAsync Implementation

The method is already declared in GameLogic/IGameServerContext.cs. Implement it in GameServer/GameServer.cs (or wherever IGameServerContext is implemented): iterate all online players whose guild is in the same alliance as the given guild.

Files to Create

File Description
GameLogic/PlayerActions/Guild/GuildRelationshipChangeAction.cs Alliance/hostility request validation and execution
GameServer/MessageHandler/Guild/GuildRelationshipChangeHandlerPlugIn.cs Packet handler for alliance/hostility requests
GameServer/MessageHandler/Guild/AllianceListRequestHandlerPlugIn.cs Packet handler for alliance list request
GameServer/MessageHandler/Guild/RemoveAllianceGuildHandlerPlugIn.cs Packet handler for removing a guild from an alliance
GameServer/RemoteView/Guild/ShowGuildRelationshipChangeResultPlugIn.cs View plug-in for relationship change result
GameServer/RemoteView/Guild/ShowAllianceListPlugIn.cs View plug-in for alliance list
GameLogic/Views/Guild/IGuildRelationshipChangeResultPlugIn.cs View interface
GameLogic/Views/Guild/IShowAllianceListPlugIn.cs View interface

Files to Modify

File Changes
Interfaces/IGuildServer.cs Add alliance methods and supporting types
Interfaces/GuildPosition.cs Add AssistantMaster
GuildServer/GuildServer.cs Implement alliance CRUD logic
GuildServer/IGuildChangePublisher.cs Add alliance broadcast methods
GuildServer/GuildChangeToGameServerPublisher.cs Implement new publisher methods
GameServer/GameServer.cs Implement ForEachAlliancePlayerAsync
Network/Packets/ClientToServer/ClientToServerPackets.xml Verify/complete alliance packet definitions
Network/Packets/ServerToClient/ServerToClientPackets.xml Add alliance response packets

Acceptance Criteria

  • A guild master can request to form an alliance with another guild master.
  • The target guild master can accept or reject the alliance request.
  • An alliance master can remove a member guild from the alliance.
  • An alliance master can disband the entire alliance.
  • Alliance list can be requested and is correctly returned.
  • Hostility can be set and cleared between guilds.
  • Hostility is transitive through alliances (if guild A→hostile→guild B, and B is in an alliance with C, then A sees C as rival too).
  • ForEachAlliancePlayerAsync correctly iterates all online players in the alliance.
  • Alliance state persists across server restarts (saved to database).
  • Maximum alliance size is enforced.
  • All new code follows existing OpenMU patterns (plug-in attributes, async, etc.).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions