Social

Guild

Persistent guilds: officer ranks, MOTD, invites/kicks, leader handoff.

Server
services.AddCrossplayGuild();
Unity package
com.crossplay.guild
Depends on
core

Providers you can swap 1

Infrastructure seams. Crossplay ships a working implementation of each — replacing one changes where data lives or how it moves, never a game rule.

IGuildStore provider

Guild persistence SPI — the seam that owns where and how guild data lives. The GAME supplies the real store (its own schema, its own database); the base ships InMemoryGuildStore for dev and tests. Invariant: one guild per account.

  • void AddInvite(long accountId, long guildId)

    Records a pending invite of an account to a guild.

  • void AddMember(long guildId, long accountId)

    Adds an account as a member of a guild.

  • GuildRecord Create(string name, long leaderAccountId)

    Creates and persists a new guild; the leader is also added as a member.

  • void Delete(long guildId)

    Deletes an empty guild entirely (members/invites/officers included).

  • GuildRecord GetByAccount(long accountId)

    The guild the account belongs to, or null.

  • GuildRecord GetById(long id)

    A guild by id, or null.

  • string GetMotd(long guildId)

    The guild's message of the day (empty when unset).

  • bool HasInvite(long accountId, long guildId)

    True if the account has a pending invite to the guild.

  • bool IsNameTaken(string name)

    True if the name is already in use (case-insensitive).

  • bool IsOfficer(long guildId, long accountId)

    True when the account is an officer of the guild.

  • IReadOnlyList<long> Members(long guildId)

    The account ids of a guild's members.

  • void RemoveInvite(long accountId, long guildId)

    Clears a pending invite of an account to a guild.

  • void RemoveMember(long guildId, long accountId)

    Removes an account from a guild.

  • void SetLeader(long guildId, long accountId)

    Transfers guild leadership.

  • void SetMotd(long guildId, string motd)

    Sets the guild's message of the day (empty clears).

  • void SetOfficer(long guildId, long accountId, bool officer)

    Marks/unmarks an account as an officer of a guild.

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

IGuildMembership

Narrow, READ-ONLY membership/rank query seam over the guild data — the small contract other pieces (e.g. a Guild Vault) depend on so they can gate on guild membership + rank WITHOUT reaching into the mutation surface of IGuildStore (Create/Delete/AddMember/…). Rank is the same byte vocabulary as GuildRank (Member < Officer < Leader).

  • IReadOnlyList<long> Members(long guildId)

    The account ids of a guild's members (empty when the guild is unknown).

  • bool TryGetMembership(long accountId, out long guildId, out byte rank)

    Resolves the guild an account belongs to and their rank. Returns false (with guildId = 0, rank = Member) when the account is in no guild.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

GuildOptions

Configurable guild rules — every tunable for the Guild piece. Defaults live here (never inline in a handler), so a game overrides only what it needs via the configure callback of AddCrossplayGuild. All values have working defaults; the object is safe to use unconfigured.

  • int MaxMembers { get; set; }

    Maximum members per guild, including the leader; accepts over this are refused. Default 50.

  • int MaxMotdLength { get; set; }

    Longest allowed message of the day (trimmed); longer sets are rejected. Default 200.

  • int MaxNameLength { get; set; }

    Maximum guild name length (trimmed); longer creates are rejected. Default 24.

  • int MinNameLength { get; set; }

    Minimum guild name length (trimmed); shorter creates are rejected. Default 3.

Wire messages 18

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

1101 GuildCreateRequest

Client -> server: create a guild with the given name (the sender becomes leader).

1102 GuildCreateResponse

Server -> client: result of a guild creation.

1103 GuildInviteRequest

Client -> server: invite a player (by display name) to the sender's guild.

1104 GuildInviteResponse

Server -> client: result of an invite (delivered to the inviter).

1105 GuildInviteNotification

Server -> client: an invite delivered to the invited target.

1106 GuildAcceptRequest

Client -> server: accept a pending invite to the given guild.

1107 GuildAcceptResponse

Server -> client: result of accepting a guild invite.

1108 GuildLeaveRequest

Client -> server: leave the sender's current guild.

1109 GuildLeaveResponse

Server -> client: result of leaving a guild.

1110 GuildRosterRequest

Client -> server: request the sender's guild roster.

1111 GuildRoster

Server -> client: the full guild roster.

1112 GuildEvent

Server -> client: a guild room event (member joined/left), broadcast to the guild room.

1113 GuildRankSetRequest

Client → server: set a member's rank (leader only; Member ↔ Officer).

1114 GuildRankSetResponse

Server → client: rank-set result.

1115 GuildKickRequest

Client → server: kick a member (officer/leader; officers cannot kick officers).

1116 GuildKickResponse

Server → client: kick result.

1117 GuildMotdSetRequest

Client → server: set the guild message of the day (officer/leader; empty clears).

1118 GuildMotdSetResponse

Server → client: MOTD-set result.

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

IGuildClient

Data-layer guild service (no UI): async create/invite/accept/leave/roster RPCs plus events for inbound invites and guild room events. Self-registers under the Crossplay client context via the framework's attribute discovery.

  • event Action<GuildInviteNotification> InviteReceived

    Raised when an invite is delivered to this client.

  • event Action<GuildEvent> Event

    Raised for a guild room event (member joined/left).

  • UniTask<GuildCreateResponse> CreateAsync(string name, CancellationToken ct = default)

    Creates a guild with the given name (the sender becomes leader).

  • UniTask<GuildInviteResponse> InviteAsync(string targetName, CancellationToken ct = default)

    Invites a player (by display name) to the sender's guild.

  • UniTask<GuildAcceptResponse> AcceptAsync(long guildId, CancellationToken ct = default)

    Accepts a pending invite to the given guild.

  • UniTask<GuildLeaveResponse> LeaveAsync(CancellationToken ct = default)

    Leaves the sender's current guild.

  • UniTask<GuildRoster> RosterAsync(CancellationToken ct = default)

    Requests the sender's guild roster.

  • UniTask<GuildRankSetResponse> RankSetAsync(long targetAccountId, byte rank, CancellationToken ct = default)

    Sets a member's rank (leader only; Member ↔ Officer).

  • UniTask<GuildKickResponse> KickAsync(long targetAccountId, CancellationToken ct = default)

    Kicks a member (officer/leader).

  • UniTask<GuildMotdSetResponse> SetMotdAsync(string motd, CancellationToken ct = default)

    Sets the guild MOTD (officer/leader; empty clears).

UI views you can replace 1

Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.

ICrossplayGuildView

Narrow contract the presenter drives. UI-only: render calls in, user intents out.