using System.Collections.Generic; using TcgEngine.AI; using TcgEngine.Gameplay; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; namespace TcgEngine.Server { /// /// Represent one game on the server, when playing solo this will be created locally, /// or if online multiple GameServer, one for each match, will be created by the dedicated server /// Manage receiving actions, sending refresh, and running AI /// public class GameServer { public string game_uid; //Game unique ID public int nb_players = 2; public static float game_expire_time = 30f; //How long for the game to be deleted when no one is connected public static float win_expire_time = 60f; //How long for a player to be declared winnner if hes the only one connected private Game game_data; private GameLogic gameplay; private float expiration = 0f; private float win_expiration = 0f; private bool is_dedicated_server = false; private List players = new List(); //Exclude observers, stays in array when disconnected, only players can send commands private List connected_clients = new List(); //Include obervers, removed from array when disconnected, all clients receive refreshes private List ai_list = new List(); //List of all AI players private Queue queued_actions = new Queue(); //List of action waiting to be processed private Dictionary registered_commands = new Dictionary(); public GameServer(string uid, int players, bool online) { Init(uid, players, online); } ~GameServer() { Clear(); } protected virtual void Init(string uid, int players, bool online) { game_uid = uid; nb_players = Mathf.Max(players, 2); is_dedicated_server = online; game_data = new Game(uid, nb_players); gameplay = new GameLogic(game_data); //Commands RegisterAction(GameAction.PlayerSettings, ReceivePlayerSettings); RegisterAction(GameAction.PlayerSettingsAI, ReceivePlayerSettingsAI); RegisterAction(GameAction.GameSettings, ReceiveGameplaySettings); RegisterAction(GameAction.PlayCard, ReceivePlayCard); RegisterAction(GameAction.Attack, ReceiveAttackTarget); RegisterAction(GameAction.AttackPlayer, ReceiveAttackPlayer); RegisterAction(GameAction.Move, ReceiveMove); RegisterAction(GameAction.CastAbility, ReceiveCastCardAbility); RegisterAction(GameAction.SelectCard, ReceiveSelectCard); RegisterAction(GameAction.SelectPlayer, ReceiveSelectPlayer); RegisterAction(GameAction.SelectSlot, ReceiveSelectSlot); RegisterAction(GameAction.SelectChoice, ReceiveSelectChoice); RegisterAction(GameAction.SelectCost, ReceiveSelectCost); RegisterAction(GameAction.SelectMulligan, ReceiveSelectMulligan); RegisterAction(GameAction.CancelSelect, ReceiveCancelSelection); RegisterAction(GameAction.EndTurn, ReceiveEndTurn); RegisterAction(GameAction.Resign, ReceiveResign); RegisterAction(GameAction.ChatMessage, ReceiveChat); //Events gameplay.onGameStart += OnGameStart; gameplay.onGameEnd += OnGameEnd; gameplay.onTurnStart += OnTurnStart; gameplay.onRefresh += RefreshAll; gameplay.onCardPlayed += OnCardPlayed; gameplay.onCardSummoned += OnCardSummoned; gameplay.onCardMoved += OnCardMoved; gameplay.onCardTransformed += OnCardTransformed; gameplay.onCardDiscarded += OnCardDiscarded; gameplay.onCardDrawn += OnCardDraw; gameplay.onRollValue += OnValueRolled; gameplay.onAbilityStart += OnAbilityStart; gameplay.onAbilityTargetCard += OnAbilityTargetCard; gameplay.onAbilityTargetPlayer += OnAbilityTargetPlayer; gameplay.onAbilityTargetSlot += OnAbilityTargetSlot; gameplay.onAbilityEnd += OnAbilityEnd; gameplay.onAttackStart += OnAttackStart; gameplay.onAttackEnd += OnAttackEnd; gameplay.onAttackPlayerStart += OnAttackPlayerStart; gameplay.onAttackPlayerEnd += OnAttackPlayerEnd; gameplay.onCardDamaged += OnCardDamaged; gameplay.onPlayerDamaged += OnPlayerDamaged; gameplay.onCardHealed += OnCardHealed; gameplay.onPlayerHealed += OnPlayerHealed ; gameplay.onSecretTrigger += OnSecretTriggered; gameplay.onSecretResolve += OnSecretResolved; } protected virtual void Clear() { gameplay.onGameStart -= OnGameStart; gameplay.onGameEnd -= OnGameEnd; gameplay.onTurnStart -= OnTurnStart; gameplay.onRefresh -= RefreshAll; gameplay.onCardPlayed -= OnCardPlayed; gameplay.onCardSummoned -= OnCardSummoned; gameplay.onCardMoved -= OnCardMoved; gameplay.onCardTransformed -= OnCardTransformed; gameplay.onCardDiscarded -= OnCardDiscarded; gameplay.onCardDrawn -= OnCardDraw; gameplay.onRollValue -= OnValueRolled; gameplay.onAbilityStart -= OnAbilityStart; gameplay.onAbilityTargetCard -= OnAbilityTargetCard; gameplay.onAbilityTargetPlayer -= OnAbilityTargetPlayer; gameplay.onAbilityTargetSlot -= OnAbilityTargetSlot; gameplay.onAbilityEnd -= OnAbilityEnd; gameplay.onAttackStart -= OnAttackStart; gameplay.onAttackEnd -= OnAttackEnd; gameplay.onAttackPlayerStart -= OnAttackPlayerStart; gameplay.onAttackPlayerEnd -= OnAttackPlayerEnd; gameplay.onCardDamaged -= OnCardDamaged; gameplay.onPlayerDamaged -= OnPlayerDamaged; gameplay.onSecretTrigger -= OnSecretTriggered; gameplay.onSecretResolve -= OnSecretResolved; } public virtual void Update() { //Game Expiration if no one is connected or game ended int connected_players = CountConnectedClients(); if (HasGameEnded() || connected_players == 0) expiration += Time.deltaTime; //Win expiration if all other players left if (connected_players == 1 && HasGameStarted() && !HasGameEnded()) win_expiration += Time.deltaTime; if (is_dedicated_server && !HasGameEnded() && IsWinExpired()) EndExpiredGame(); //Timer during game if (game_data.state == GameState.Play && !gameplay.IsResolving()) { game_data.turn_timer -= Time.deltaTime; if (game_data.turn_timer <= 0f) { //Time expired during turn gameplay.NextStep(); } } //Start Game when ready if (game_data.state == GameState.Connecting) { bool all_connected = game_data.AreAllPlayersConnected(); bool all_ready = game_data.AreAllPlayersReady(); if (all_connected && all_ready) { StartGame(); } } //Process queued actions if (queued_actions.Count > 0 && !gameplay.IsResolving()) { QueuedGameAction action = queued_actions.Dequeue(); ExecuteAction(action.type, action.client, action.sdata); } //Update game logic gameplay.Update(Time.deltaTime); //Update AI foreach (AIPlayer ai in ai_list) { ai.Update(); } } protected virtual void StartGame() { //Setup AI bool ai_vs_ai = !is_dedicated_server && GameplayData.Get().ai_vs_ai; foreach (Player player in game_data.players) { if (player.is_ai || ai_vs_ai) { AIPlayer ai_gameplay = AIPlayer.Create(GameplayData.Get().ai_type, gameplay, player.player_id, player.ai_level); ai_list.Add(ai_gameplay); } } //Start Game gameplay.StartGame(); } //End game when it has expired (only one player is still connected) protected virtual void EndExpiredGame() { Game gdata = gameplay.GetGameData(); foreach (Player player in gdata.players) { if (player.IsConnected()) { gameplay.EndGame(player.player_id); return; } } } //------ Receive Actions ------- private void RegisterAction(ushort tag, UnityAction callback) { CommandEvent cmdevt = new CommandEvent(); cmdevt.tag = tag; cmdevt.callback = callback; registered_commands.Add(tag, cmdevt); } public void ReceiveAction(ulong client_id, FastBufferReader reader) { ClientData client = GetClient(client_id); if (client != null) { reader.ReadValueSafe(out ushort type); SerializedData sdata = new SerializedData(reader); if (!gameplay.IsResolving()) { //Not resolving, execute now ExecuteAction(type, client, sdata); } else { //Resolving, wait before executing QueuedGameAction action = new QueuedGameAction(); action.type = type; action.client = client; action.sdata = sdata; sdata.PreRead(); queued_actions.Enqueue(action); } } } public void ExecuteAction(ushort type, ClientData client, SerializedData sdata) { bool found = registered_commands.TryGetValue(type, out CommandEvent command); if(found) command.callback.Invoke(client, sdata); } //------- public void ReceivePlayerSettings(ClientData iclient, SerializedData sdata) { PlayerSettings msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null) { SetPlayerSettings(player.player_id, msg); } } public void ReceivePlayerSettingsAI(ClientData iclient, SerializedData sdata) { PlayerSettings msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null) { SetPlayerSettingsAI(player.player_id, msg); } } public void ReceiveGameplaySettings(ClientData iclient, SerializedData sdata) { GameSettings settings = sdata.Get(); if (settings != null) { SetGameSettings(settings); } } public void ReceivePlayCard(ClientData iclient, SerializedData sdata) { MsgPlayCard msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving()) { Card card = player.GetCard(msg.card_uid); if (card != null && card.player_id == player.player_id) { // 检查本回合是否已经上场过场上卡牌(只有怪物牌等场上卡牌受限制) if (card.CardData.IsBoardCard() && player.cards_played_this_turn >= 1) return; // 已经上场过场上卡牌,不能再上场 // 手动上场卡牌:跳过mana消耗 // gameplay.PlayCard(card, msg.slot, true); gameplay.PlayCard(card, msg.slot); // 只有场上卡牌才增加计数 if (card.CardData.IsBoardCard()) player.cards_played_this_turn++; } } } public void ReceiveAttackTarget(ClientData iclient, SerializedData sdata) { MsgAttack msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving()) { Card attacker = player.GetCard(msg.attacker_uid); Card target = game_data.GetCard(msg.target_uid); if (attacker != null && target != null && attacker.player_id == player.player_id) { gameplay.AttackTarget(attacker, target); } } } public void ReceiveAttackPlayer(ClientData iclient, SerializedData sdata) { MsgAttackPlayer msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving()) { Card attacker = player.GetCard(msg.attacker_uid); Player target = game_data.GetPlayer(msg.target_id); if (attacker != null && target != null && attacker.player_id == player.player_id) { gameplay.AttackPlayer(attacker, target); } } } public void ReceiveMove(ClientData iclient, SerializedData sdata) { MsgPlayCard msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving()) { Card card = player.GetCard(msg.card_uid); if (card != null && card.player_id == player.player_id) gameplay.MoveCard(card, msg.slot); } } public void ReceiveCastCardAbility(ClientData iclient, SerializedData sdata) { MsgCastAbility msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving()) { Card card = player.GetCard(msg.caster_uid); AbilityData iability = AbilityData.Get(msg.ability_id); if (card != null && card.player_id == player.player_id) gameplay.CastAbility(card, iability); } } public void ReceiveSelectCard(ClientData iclient, SerializedData sdata) { MsgCard msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving()) { Card target = game_data.GetCard(msg.card_uid); gameplay.SelectCard(target); } } public void ReceiveSelectPlayer(ClientData iclient, SerializedData sdata) { MsgPlayer msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving()) { Player target = game_data.GetPlayer(msg.player_id); gameplay.SelectPlayer(target); } } public void ReceiveSelectSlot(ClientData iclient, SerializedData sdata) { Slot slot = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving()) { if(slot != null && slot.IsValid()) gameplay.SelectSlot(slot); } } public void ReceiveSelectChoice(ClientData iclient, SerializedData sdata) { MsgInt msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving()) { gameplay.SelectChoice(msg.value); } } public void ReceiveSelectCost(ClientData iclient, SerializedData sdata) { MsgInt msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving()) { gameplay.SelectCost(msg.value); } } public void ReceiveCancelSelection(ClientData iclient, SerializedData sdata) { Player player = GetPlayer(iclient); if (player != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving()) { gameplay.CancelSelection(); } } public void ReceiveSelectMulligan(ClientData iclient, SerializedData sdata) { MsgMulligan msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null && game_data.IsPlayerMulliganTurn(player) && !gameplay.IsResolving()) { gameplay.Mulligan(player, msg.cards); } } public void ReceiveEndTurn(ClientData iclient, SerializedData sdata) { Player player = GetPlayer(iclient); if (player != null && game_data.IsPlayerTurn(player)) { gameplay.NextStep(); } } public void ReceiveResign(ClientData iclient, SerializedData sdata) { Player player = GetPlayer(iclient); if (player != null && game_data.state != GameState.Connecting && game_data.state != GameState.GameEnded) { int winner = player.player_id == 0 ? 1 : 0; gameplay.EndGame(winner); } } public void ReceiveChat(ClientData iclient, SerializedData sdata) { MsgChat msg = sdata.Get(); Player player = GetPlayer(iclient); if (player != null && msg != null) { msg.player_id = player.player_id; //Force player id to sending client to avoid spoofing SendToAll(GameAction.ChatMessage, msg, NetworkDelivery.Reliable); } } //--- Setup Commands ------ public virtual async void SetPlayerDeck(int player_id, string username, UserDeckData deck) { Player player = game_data.GetPlayer(player_id); if (player != null && game_data.state == GameState.Connecting) { UserData user = Authenticator.Get().UserData; //Offline game, get local user if(Authenticator.Get().IsApi()) user = await ApiClient.Get().LoadUserData(username); //Online game, validate from api //Use user API deck UserDeckData udeck = user?.GetDeck(deck.tid); if (user != null && udeck != null) { if (user.IsDeckValid(udeck)) { gameplay.SetPlayerDeck(player, udeck); SendPlayerReady(player); return; } else { Debug.Log(user.username + " deck is invalid: " + udeck.title); return; } } //Use premade deck DeckData cdeck = DeckData.Get(deck.tid); if (cdeck != null) gameplay.SetPlayerDeck(player, cdeck); //Trust client in test mode else if (Authenticator.Get().IsTest()) gameplay.SetPlayerDeck(player, deck); //Deck not found else Debug.Log("Player " + player_id + " deck not found: " + deck.tid); SendPlayerReady(player); } } public virtual void SetPlayerSettings(int player_id, PlayerSettings psettings) { if (game_data.state != GameState.Connecting) return; //Cant send setting if game already started Player player = game_data.GetPlayer(player_id); if (player != null && !player.ready) { player.avatar = psettings.avatar; player.cardback = psettings.cardback; player.is_ai = false; player.ready = true; SetPlayerDeck(player_id, player.username, psettings.deck); RefreshAll(); } } public virtual void SetPlayerSettingsAI(int player_id, PlayerSettings psettings) { if (game_data.state != GameState.Connecting) return; //Cant send setting if game already started if (is_dedicated_server) return; //No AI allowed online server Player player = game_data.GetOpponentPlayer(player_id); if (player != null && !player.ready) { player.username = psettings.username; player.avatar = psettings.avatar; player.cardback = psettings.cardback; player.is_ai = true; player.ready = true; player.ai_level = psettings.ai_level; SetPlayerDeck(player.player_id, player.username, psettings.deck); RefreshAll(); } } public virtual void SetGameSettings(GameSettings settings) { if (game_data.state == GameState.Connecting) { game_data.settings = settings; RefreshAll(); } } //------------- public void AddClient(ClientData client) { if (!connected_clients.Contains(client)) connected_clients.Add(client); } public void RemoveClient(ClientData client) { connected_clients.Remove(client); Player player = GetPlayer(client); if (player != null && player.connected) { player.connected = false; RefreshAll(); } } public ClientData GetClient(ulong client_id) { foreach (ClientData client in connected_clients) { if (client.client_id == client_id) return client; } return null; } public int AddPlayer(ClientData client) { if (!players.Contains(client)) players.Add(client); int player_id = FindPlayerID(client.user_id); Player player = game_data.GetPlayer(player_id); if (player != null) { player.username = client.username; player.connected = true; } return player_id; } public int FindPlayerID(string user_id) { int index = 0; foreach (ClientData player in players) { if (player.user_id == user_id) return index; index++; } return -1; } public Player GetPlayer(ClientData client) { return GetPlayer(client.user_id); } public Player GetPlayer(string user_id) { int player_id = FindPlayerID(user_id); return game_data?.GetPlayer(player_id); } public bool IsPlayer(string user_id) { Player player = GetPlayer(user_id); return player != null; } public bool IsConnectedPlayer(string user_id) { Player player = GetPlayer(user_id); return player != null && player.connected; } public int CountPlayers() { return players.Count; } public int CountConnectedClients() { int nb = 0; Game game = GetGameData(); foreach (Player player in game.players) { if (player.IsConnected()) { nb++; } } return nb; } public Game GetGameData() { return gameplay.GetGameData(); } public virtual bool HasGameStarted() { return gameplay.IsGameStarted(); } public virtual bool HasGameEnded() { return gameplay.IsGameEnded(); } public virtual bool IsGameExpired() { return expiration > game_expire_time; //Means that the game expired (everyone left or game ended) } public virtual bool IsWinExpired() { return win_expiration > win_expire_time; //Means that only one player is left, and he should win } protected virtual void OnGameStart() { SendToAll(GameAction.GameStart); if (is_dedicated_server && Authenticator.Get().IsApi()) { //Create Match ApiClient.Get().CreateMatch(game_data); } } protected virtual void OnGameEnd(Player winner) { MsgPlayer msg = new MsgPlayer(); msg.player_id = winner != null ? winner.player_id : -1; SendToAll(GameAction.GameEnd, msg, NetworkDelivery.Reliable); if (is_dedicated_server && Authenticator.Get().IsApi()) { //End Match and give rewards ApiClient.Get().EndMatch(game_data, winner.player_id); } } protected virtual void OnTurnStart() { MsgPlayer msg = new MsgPlayer(); msg.player_id = game_data.current_player; SendToAll(GameAction.NewTurn, msg, NetworkDelivery.Reliable); } protected virtual void OnCardPlayed(Card card, Slot slot) { MsgPlayCard mdata = new MsgPlayCard(); mdata.card_uid = card.uid; mdata.slot = slot; SendToAll(GameAction.CardPlayed, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardMoved(Card card, Slot slot) { MsgPlayCard mdata = new MsgPlayCard(); mdata.card_uid = card.uid; mdata.slot = slot; SendToAll(GameAction.CardMoved, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardSummoned(Card card, Slot slot) { MsgPlayCard mdata = new MsgPlayCard(); mdata.card_uid = card.uid; mdata.slot = slot; SendToAll(GameAction.CardSummoned, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardTransformed(Card card) { MsgCard mdata = new MsgCard(); mdata.card_uid = card.uid; SendToAll(GameAction.CardTransformed, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardDiscarded(Card card) { MsgCard mdata = new MsgCard(); mdata.card_uid = card.uid; SendToAll(GameAction.CardDiscarded, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardDraw(int nb) { MsgInt mdata = new MsgInt(); mdata.value = nb; SendToAll(GameAction.CardDrawn, mdata, NetworkDelivery.Reliable); } protected virtual void OnValueRolled(int nb) { MsgInt mdata = new MsgInt(); mdata.value = nb; SendToAll(GameAction.ValueRolled, mdata, NetworkDelivery.Reliable); } protected virtual void OnAttackStart(Card attacker, Card target) { MsgAttack mdata = new MsgAttack(); mdata.attacker_uid = attacker.uid; mdata.target_uid = target.uid; mdata.damage = 0; SendToAll(GameAction.AttackStart, mdata, NetworkDelivery.Reliable); } protected virtual void OnAttackEnd(Card attacker, Card target) { MsgAttack mdata = new MsgAttack(); mdata.attacker_uid = attacker.uid; mdata.target_uid = target.uid; mdata.damage = 0; SendToAll(GameAction.AttackEnd, mdata, NetworkDelivery.Reliable); } protected virtual void OnAttackPlayerStart(Card attacker, Player target) { MsgAttackPlayer mdata = new MsgAttackPlayer(); mdata.attacker_uid = attacker.uid; mdata.target_id = target.player_id; mdata.damage = 0; SendToAll(GameAction.AttackPlayerStart, mdata, NetworkDelivery.Reliable); } protected virtual void OnAttackPlayerEnd(Card attacker, Player target) { MsgAttackPlayer mdata = new MsgAttackPlayer(); mdata.attacker_uid = attacker.uid; mdata.target_id = target.player_id; mdata.damage = 0; SendToAll(GameAction.AttackPlayerEnd, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardDamaged(Card card, int damage) { MsgCardValue mdata = new MsgCardValue(); mdata.card_uid = card.uid; mdata.value = damage; SendToAll(GameAction.CardDamaged, mdata, NetworkDelivery.Reliable); } protected virtual void OnPlayerDamaged(Player player, int damage) { MsgPlayerValue mdata = new MsgPlayerValue(); mdata.player_id = player.player_id; mdata.value = damage; SendToAll(GameAction.PlayerDamaged, mdata, NetworkDelivery.Reliable); } protected virtual void OnCardHealed(Card card, int hp) { MsgCardValue mdata = new MsgCardValue(); mdata.card_uid = card.uid; mdata.value = hp; SendToAll(GameAction.CardHealed, mdata, NetworkDelivery.Reliable); } protected virtual void OnPlayerHealed(Player player, int hp) { MsgPlayerValue mdata = new MsgPlayerValue(); mdata.player_id = player.player_id; mdata.value = hp; SendToAll(GameAction.PlayerHealed, mdata, NetworkDelivery.Reliable); } protected virtual void OnAbilityStart(AbilityData ability, Card caster) { MsgCastAbility mdata = new MsgCastAbility(); mdata.ability_id = ability.id; mdata.caster_uid = caster.uid; mdata.target_uid = ""; SendToAll(GameAction.AbilityTrigger, mdata, NetworkDelivery.Reliable); } protected virtual void OnAbilityTargetCard(AbilityData ability, Card caster, Card target) { MsgCastAbility mdata = new MsgCastAbility(); mdata.ability_id = ability.id; mdata.caster_uid = caster.uid; mdata.target_uid = target != null ? target.uid : ""; SendToAll(GameAction.AbilityTargetCard, mdata, NetworkDelivery.Reliable); } protected virtual void OnAbilityTargetPlayer(AbilityData ability, Card caster, Player target) { MsgCastAbilityPlayer mdata = new MsgCastAbilityPlayer(); mdata.ability_id = ability.id; mdata.caster_uid = caster.uid; mdata.target_id = target != null ? target.player_id : -1; SendToAll(GameAction.AbilityTargetPlayer, mdata, NetworkDelivery.Reliable); } protected virtual void OnAbilityTargetSlot(AbilityData ability, Card caster, Slot target) { MsgCastAbilitySlot mdata = new MsgCastAbilitySlot(); mdata.ability_id = ability.id; mdata.caster_uid = caster.uid; mdata.slot = target; SendToAll(GameAction.AbilityTargetSlot, mdata, NetworkDelivery.Reliable); } protected virtual void OnAbilityEnd(AbilityData ability, Card caster) { MsgCastAbility mdata = new MsgCastAbility(); mdata.ability_id = ability.id; mdata.caster_uid = caster.uid; mdata.target_uid = ""; SendToAll(GameAction.AbilityEnd, mdata, NetworkDelivery.Reliable); } protected virtual void OnSecretTriggered(Card secret, Card trigger) { MsgSecret mdata = new MsgSecret(); mdata.secret_uid = secret.uid; mdata.triggerer_uid = trigger != null ? trigger.uid : ""; SendToAll(GameAction.SecretTriggered, mdata, NetworkDelivery.Reliable); } protected virtual void OnSecretResolved(Card secret, Card trigger) { MsgSecret mdata = new MsgSecret(); mdata.secret_uid = secret.uid; mdata.triggerer_uid = trigger != null ? trigger.uid : ""; SendToAll(GameAction.SecretResolved, mdata, NetworkDelivery.Reliable); } protected virtual void SendPlayerReady(Player player) { if (player != null && player.IsReady()) { MsgInt mdata = new MsgInt(); mdata.value = player.player_id; SendToAll(GameAction.PlayerReady, mdata, NetworkDelivery.Reliable); } } public virtual void RefreshAll() { MsgRefreshAll mdata = new MsgRefreshAll(); mdata.game_data = GetGameData(); SendToAll(GameAction.RefreshAll, mdata, NetworkDelivery.ReliableFragmentedSequenced); } public void SendToAll(ushort tag) { FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax); writer.WriteValueSafe(tag); foreach (ClientData iclient in connected_clients) { if (iclient != null) { Messaging.Send("refresh", iclient.client_id, writer, NetworkDelivery.Reliable); } } writer.Dispose(); } public void SendToAll(ushort tag, INetworkSerializable data, NetworkDelivery delivery) { FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax); writer.WriteValueSafe(tag); writer.WriteNetworkSerializable(data); foreach (ClientData iclient in connected_clients) { if (iclient != null) { Messaging.Send("refresh", iclient.client_id, writer, delivery); } } writer.Dispose(); } public ulong ServerID { get { return TcgNetwork.Get().ServerID; } } public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } } } public struct QueuedGameAction { public ushort type; public ClientData client; public SerializedData sdata; } }