Guild
Persistent guilds: officer ranks, MOTD, invites/kicks, leader handoff.
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.
GuildCreateRequest Client -> server: create a guild with the given name (the sender becomes leader).
GuildCreateResponse Server -> client: result of a guild creation.
GuildInviteRequest Client -> server: invite a player (by display name) to the sender's guild.
GuildInviteResponse Server -> client: result of an invite (delivered to the inviter).
GuildInviteNotification Server -> client: an invite delivered to the invited target.
GuildAcceptRequest Client -> server: accept a pending invite to the given guild.
GuildAcceptResponse Server -> client: result of accepting a guild invite.
GuildLeaveRequest Client -> server: leave the sender's current guild.
GuildLeaveResponse Server -> client: result of leaving a guild.
GuildRosterRequest Client -> server: request the sender's guild roster.
GuildRoster Server -> client: the full guild roster.
GuildEvent Server -> client: a guild room event (member joined/left), broadcast to the guild room.
GuildRankSetRequest Client → server: set a member's rank (leader only; Member ↔ Officer).
GuildRankSetResponse Server → client: rank-set result.
GuildKickRequest Client → server: kick a member (officer/leader; officers cannot kick officers).
GuildKickResponse Server → client: kick result.
GuildMotdSetRequest Client → server: set the guild message of the day (officer/leader; empty clears).
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> InviteReceivedRaised when an invite is delivered to this client.
-
event Action<GuildEvent> EventRaised 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.