Appearance
Synchronizing Data Between Player and Guild
How to mirror data between Player to Guild models.
Appearance
How to mirror data between Player to Guild models.
The Player model and the Guild model are isolated and independent. Neither model has direct access to the other, and hence any game logic on one model cannot observe or react to changes in the other. As a consequence, GuildActions cannot act on player data, and PlayerActions cannot act on guild state.
Metaplay solves this by allowing Guilds and Players to synchronize data between each other. By default, only the player's DisplayName is copied from the Player model into the Guild model, making it visible to other guild members. This document describes how to synchronize any custom data between the entities in one or both directions.
Synchronizing data from from Player to Guild can be done for UI or for game logic purposes. For example, we might want to mirror PlayerModel's current icon or avatar to make it visible for all guild members in the guild model. Alternatively, a Player's ongoing IAP Subscription might grant benefits for the whole Guild.
Player-to-Guild mirroring uses GuildMemberPlayerDataBase. The game extends this class to define which additional player fields should be mirrored into the guild member record.
Create a class that extends GuildMemberPlayerDataBase and add the fields you want to mirror. Override ApplyOnMember() to write the new values into the guild member.
[MetaSerializableDerived(1)]
public class MyGuildMemberPlayerData : GuildMemberPlayerDataBase
{
[MetaMember(101)] public PlayerIconId PlayerIcon { get; private set; }
[MetaMember(102)] public bool HasVipSubscription { get; private set; }
public MyGuildMemberPlayerData() { }
public MyGuildMemberPlayerData(PlayerIconId playerIcon, bool hasVipSubscription)
{
PlayerIcon = playerIcon;
HasVipSubscription = hasVipSubscription;
}
public override void ApplyOnMember(GuildMemberBase member, IGuildModelBase guild, GuildMemberPlayerDataUpdateKind updateKind)
{
MyGuildMember myMember = (MyGuildMember)member;
myMember.PlayerIcon = PlayerIcon;
myMember.HasVipSubscription = HasVipSubscription;
}
}Add the corresponding fields to your GuildMember class so they can be read by other guild members and the client:
[MetaSerializableDerived(1)]
public class MyGuildMember : GuildMemberBase
{
[MetaMember(101)] public PlayerIconId PlayerIcon { get; set; }
[MetaMember(102)] public bool HasVipSubscription { get; set; }
}Override CreateGuildMemberPlayerData() in your player actor's guild component to produce the current snapshot:
public sealed class GuildComponent : GuildComponentBase<PlayerActor>
{
public GuildComponent(PlayerActor player) : base(player) { }
protected override GuildMemberPlayerDataBase CreateGuildMemberPlayerData()
{
bool hasVipSubscription = Player.Model.IAPSubscriptions.Subscriptions.Any(/*...*/);
return new MyGuildMemberPlayerData(Player.Model.PlayerIcon, hasVipSubscription);
}
}The guild's copy of player data is always synchronized on every client login. If you also want to update it mid-session, call EnqueueGuildMemberPlayerDataUpdate() from the player actor.
As an example, say we want to push the updated icon to the guild as soon as the player changes it. Game logic runs inside actions on the model, so the player actor reacts to the change through a server listener (see Client and Server Listeners). Add a callback to the listener interface:
public interface IPlayerModelServerListener
{
void OnPlayerIconChanged();
}Invoke the callback from the action that changes the icon:
[ModelAction(ActionCodes.PlayerSetIcon)]
public class PlayerSetIcon : PlayerAction
{
public PlayerIconId Icon { get; private set; }
public override MetaActionResult Execute(PlayerModel player, bool commit)
{
if (commit)
{
player.PlayerIcon = Icon;
player.ServerListener.OnPlayerIconChanged();
}
return MetaActionResult.Success;
}
}Implement the callback in the player actor to push the updated data to the guild:
public class PlayerActor : PlayerActorBase, IPlayerModelServerListener
{
void IPlayerModelServerListener.OnPlayerIconChanged()
{
EnqueueGuildMemberPlayerDataUpdate();
}
}Synchronizing data from Guild to Player can be done for UI or for game logic purposes. For example, we might want to mirror the guild's faction alignment so the client can display it League avatar. Alternatively, the faction could gate which units the player is allowed to buy.
Guild-to-Player mirroring uses PlayerGuildDataBase. The game extends this class to define which guild fields should be mirrored into the player's GuildState. As these fields become part of PlayerModel, the fields are accessible to PlayerActions and to PlayerActor.
Create a class that extends PlayerGuildDataBase and add the fields you want to mirror.
[MetaSerializable]
public enum GuildAlignment { Neutral, Alliance, Horde }
[MetaSerializableDerived(1)]
public class MyPlayerGuildData : PlayerGuildDataBase
{
[MetaMember(101)] public GuildAlignment Alignment { get; private set; }
public MyPlayerGuildData() { }
public MyPlayerGuildData(GuildAlignment alignment)
{
Alignment = alignment;
}
}Override CreatePlayerGuildData() in your guild actor to produce the current snapshot. It receives the playerId of the member the data is being prepared for, so you can return member-specific data:
public sealed class GuildActor : GuildActorBase<GuildModel, PersistedGuild>
{
protected override PlayerGuildDataBase CreatePlayerGuildData(EntityId playerId)
{
return new MyPlayerGuildData(Model.Alignment);
}
}Because the data is part of the player model, it is readable from game logic via GuildState.GuildData:
[ModelAction(ActionCodes.PlayerBuyMercenary)]
public class PlayerBuyMercenary : PlayerAction
{
public MercenaryId Mercenary { get; private set; }
public override MetaActionResult Execute(PlayerModel player, bool commit)
{
MyPlayerGuildData guildData = (MyPlayerGuildData)player.GuildState.GuildData;
// The player must be in a guild.
if (guildData == null)
return ActionResult.NotInGuild;
// Reject if the guild's alignment doesn't match the mercenary.
if (guildData.Alignment != GetAlignment(Mercenary))
return ActionResult.WrongGuildAlignment;
if (commit)
player.Mercenaries.Add(Mercenary);
return MetaActionResult.Success;
}
}The player's copy of guild data is always synchronized on every client login. If you also want to update it mid-session, call RefreshPlayerGuildData() from the guild actor.
This works the same way as on the player side, through a server listener. For example, a guild action that changes the faction alignment notifies IGuildModelServerListener, and the guild actor refreshes the mirrored data in response:
public sealed class GuildActor : GuildActorBase<GuildModel, PersistedGuild>, IGuildModelServerListener
{
void IGuildModelServerListener.OnAlignmentChanged()
{
RefreshPlayerGuildData();
}
}RefreshPlayerGuildData only affects members who are currently online. Members who are offline will receive the updated data the next time they log in.