using System.Collections; using System.Collections.Generic; using UnityEngine; namespace TcgEngine { //Contains all gameplay state data that is sync across network [System.Serializable] public class Game { public string game_uid; public GameSettings settings; //Game state public int first_player = 0; public int current_player = 0; public int turn_count = 0; public float turn_timer = 0f; public GameState state = GameState.Connecting; public GamePhase phase = GamePhase.None; //Players public Player[] players; //Selector public SelectorType selector = SelectorType.None; public int selector_player_id = 0; public string selector_ability_id; public string selector_caster_uid; //Other reference values public string last_played; public string last_target; public string last_destroyed; public string last_summoned; public string ability_triggerer; public int rolled_value; public int selected_value; //Other reference arrays public HashSet ability_played = new HashSet(); public HashSet cards_attacked = new HashSet(); public Game() { } public Game(string uid, int nb_players) { this.game_uid = uid; players = new Player[nb_players]; for (int i = 0; i < nb_players; i++) players[i] = new Player(i); settings = GameSettings.Default; } public virtual bool AreAllPlayersReady() { int ready = 0; foreach (Player player in players) { if (player.IsReady()) ready++; } return ready >= settings.nb_players; } public virtual bool AreAllPlayersConnected() { int ready = 0; foreach (Player player in players) { if (player.IsConnected()) ready++; } return ready >= settings.nb_players; } //Check if its player's turn public virtual bool IsPlayerTurn(Player player) { return IsPlayerActionTurn(player) || IsPlayerSelectorTurn(player); } public virtual bool IsPlayerActionTurn(Player player) { return player != null && current_player == player.player_id && state == GameState.Play && phase == GamePhase.Main && selector == SelectorType.None; } public virtual bool IsPlayerSelectorTurn(Player player) { return player != null && selector_player_id == player.player_id && state == GameState.Play && phase == GamePhase.Main && selector != SelectorType.None; } public virtual bool IsPlayerMulliganTurn(Player player) { return phase == GamePhase.Mulligan && !player.ready; } //Check if a card is allowed to be played on slot public virtual bool CanPlayCard(Card card, Slot slot, bool skip_cost = false) { if (card == null) return false; Player player = GetPlayer(card.player_id); if (!skip_cost && !player.CanPayTeamMana(card)) return false; //Cant pay mana if (!player.HasCard(player.cards_hand, card)) return false; // Card not in hand // AI 不能在没有对应阵营mana时使用动态费用卡牌 if (player.is_ai && card.CardData.IsDynamicManaCost()) { string team_id = card.CardData.team?.id; int available_mana = string.IsNullOrEmpty(team_id) ? player.mana : player.GetTeamMana(team_id); if (available_mana == 0) return false; // AI cant play X-cost card at 0 cost } if (card.CardData.IsBoardCard()) { if (!slot.IsValid() || IsCardOnSlot(slot)) return false; //Slot already occupied if (Slot.GetP(card.player_id) != slot.p) return false; //Cant play on opponent side return true; } if (card.CardData.IsEquipment()) { if (!slot.IsValid()) return false; Card target = GetSlotCard(slot); if (target == null || target.CardData.type != CardType.Character || target.player_id != card.player_id) return false; //Target must be an allied character return true; } if (card.CardData.IsRequireTargetSpell()) { return IsPlayTargetValid(card, slot); //Check play target on slot } if (card.CardData.type == CardType.Spell) { return CanAnyPlayAbilityTrigger(card); //Check if spell will have abilities } return true; } //Check if a card is allowed to move to slot public virtual bool CanMoveCard(Card card, Slot slot, bool skip_cost = false) { if (card == null || !slot.IsValid()) return false; if (!IsOnBoard(card)) return false; //Only cards in play can move if (!card.CanMove(skip_cost)) return false; //Card cant move if (Slot.GetP(card.player_id) != slot.p) return false; //Card played wrong side if (card.slot == slot) return false; //Cant move to same slot Card slot_card = GetSlotCard(slot); if (slot_card != null) return false; //Already a card there return true; } //Check if a card is allowed to attack a player public virtual bool CanAttackTarget(Card attacker, Player target, bool skip_cost = false) { if(attacker == null || target == null) return false; if (!attacker.CanAttack(skip_cost)) return false; //Card cant attack if (attacker.player_id == target.player_id) return false; //Cant attack same player if (!IsOnBoard(attacker) || !attacker.CardData.IsCharacter()) return false; //Cards not on board if (target.HasStatus(StatusType.Protected) && !attacker.HasStatus(StatusType.Flying)) return false; //Protected by taunt // 检查目标玩家场上是否有卡牌,如有则无法直接攻击玩家 if (target.cards_board.Count > 0) return false; //Cannot attack player when they have cards on board return true; } //Check if a card is allowed to attack another one public virtual bool CanAttackTarget(Card attacker, Card target, bool skip_cost = false) { if (attacker == null || target == null) return false; if (!attacker.CanAttack(skip_cost)) return false; //Card cant attack if (attacker.player_id == target.player_id) return false; //Cant attack same player if (!IsOnBoard(attacker) || !IsOnBoard(target)) return false; //Cards not on board if (!attacker.CardData.IsCharacter() || !target.CardData.IsBoardCard()) return false; //Only character can attack if (target.HasStatus(StatusType.Stealth)) return false; //Stealth cant be attacked if (target.HasStatus(StatusType.Protected) && !attacker.HasStatus(StatusType.Flying)) return false; //Protected by adjacent card return true; } public virtual bool CanCastAbility(Card card, AbilityData ability) { if (ability == null || card == null || !card.CanDoActivatedAbilities()) return false; //This card cant cast if (ability.trigger != AbilityTrigger.Activate) return false; //Not an activated ability Player player = GetPlayer(card.player_id); if (!player.CanPayAbility(card, ability)) return false; //Cant pay for ability if (!ability.AreTriggerConditionsMet(this, card)) return false; //Conditions not met return true; } //For choice selector public virtual bool CanSelectAbility(Card card, AbilityData ability) { if (ability == null || card == null || !card.CanDoAbilities()) return false; //This card cant cast Player player = GetPlayer(card.player_id); if (!player.CanPayAbility(card, ability)) return false; //Cant pay for ability if (!ability.AreTriggerConditionsMet(this, card)) return false; //Conditions not met return true; } public virtual bool CanAnyPlayAbilityTrigger(Card card) { if (card == null) return false; if (card.CardData.IsDynamicManaCost()) return true; //Cost not decided so condition could be false foreach (AbilityData ability in card.GetAbilities()) { if (ability.trigger == AbilityTrigger.OnPlay && ability.AreTriggerConditionsMet(this, card)) return true; } return false; } //Check if Player play target is valid, play target is the target when a spell requires to drag directly onto another card public virtual bool IsPlayTargetValid(Card caster, Player target) { if (caster == null || target == null) return false; foreach (AbilityData ability in caster.GetAbilities()) { if (ability && ability.trigger == AbilityTrigger.OnPlay && ability.target == AbilityTarget.PlayTarget) { if (!ability.CanTarget(this, caster, target)) return false; } } return true; } //Check if Card play target is valid, play target is the target when a spell requires to drag directly onto another card public virtual bool IsPlayTargetValid(Card caster, Card target) { if (caster == null || target == null) return false; foreach (AbilityData ability in caster.GetAbilities()) { if (ability && ability.trigger == AbilityTrigger.OnPlay && ability.target == AbilityTarget.PlayTarget) { if (!ability.CanTarget(this, caster, target)) return false; } } return true; } //Check if Slot play target is valid, play target is the target when a spell requires to drag directly onto another card public virtual bool IsPlayTargetValid(Card caster, Slot target) { if (caster == null) return false; if (target.IsPlayerSlot()) return IsPlayTargetValid(caster, GetPlayer(target.p)); //Slot 0,0, means we are targeting a player Card slot_card = GetSlotCard(target); if (slot_card != null) return IsPlayTargetValid(caster, slot_card); //Slot has card, check play target on that card foreach (AbilityData ability in caster.GetAbilities()) { if (ability && ability.trigger == AbilityTrigger.OnPlay && ability.target == AbilityTarget.PlayTarget) { if (!ability.CanTarget(this, caster, target)) return false; } } return true; } public Player GetPlayer(int id) { if (id >= 0 && id < players.Length) return players[id]; return null; } public Player GetActivePlayer() { return GetPlayer(current_player); } public Player GetOpponentPlayer(int id) { int oid = id == 0 ? 1 : 0; return GetPlayer(oid); } public Card GetCard(string card_uid) { foreach (Player player in players) { Card acard = player.GetCard(card_uid); if (acard != null) return acard; } return null; } public Card GetBoardCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_board) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetEquipCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_equip) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetHandCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_hand) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetDeckCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_deck) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetDiscardCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_discard) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetSecretCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_secret) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetTempCard(string card_uid) { foreach (Player player in players) { foreach (Card card in player.cards_temp) { if (card != null && card.uid == card_uid) return card; } } return null; } public Card GetSlotCard(Slot slot) { foreach (Player player in players) { foreach (Card card in player.cards_board) { if (card != null && card.slot == slot) return card; } } return null; } public virtual Player GetRandomPlayer(System.Random rand) { Player player = GetPlayer(rand.NextDouble() < 0.5 ? 1 : 0); return player; } public virtual Card GetRandomBoardCard(System.Random rand) { Player player = GetRandomPlayer(rand); return player.GetRandomCard(player.cards_board, rand); } public virtual Slot GetRandomSlot(System.Random rand) { Player player = GetRandomPlayer(rand); return player.GetRandomSlot(rand); } public bool IsInHand(Card card) { return card != null && GetHandCard(card.uid) != null; } public bool IsOnBoard(Card card) { return card != null && GetBoardCard(card.uid) != null; } public bool IsEquipped(Card card) { return card != null && GetEquipCard(card.uid) != null; } public bool IsInDeck(Card card) { return card != null && GetDeckCard(card.uid) != null; } public bool IsInDiscard(Card card) { return card != null && GetDiscardCard(card.uid) != null; } public bool IsInSecret(Card card) { return card != null && GetSecretCard(card.uid) != null; } public bool IsInTemp(Card card) { return card != null && GetTempCard(card.uid) != null; } public bool IsCardOnSlot(Slot slot) { return GetSlotCard(slot) != null; } public bool HasStarted() { return state != GameState.Connecting; } public bool HasEnded() { return state == GameState.GameEnded; } //Same as clone, but also instantiates the variable (much slower) public static Game CloneNew(Game source) { Game game = new Game(); Clone(source, game); return game; } //Clone all variables into another var, used mostly by the AI when building a prediction tree public static void Clone(Game source, Game dest) { dest.game_uid = source.game_uid; dest.settings = source.settings; dest.first_player = source.first_player; dest.current_player = source.current_player; dest.turn_count = source.turn_count; dest.turn_timer = source.turn_timer; dest.state = source.state; dest.phase = source.phase; if (dest.players == null) { dest.players = new Player[source.players.Length]; for(int i=0; i< source.players.Length; i++) dest.players[i] = new Player(i); } for (int i = 0; i < source.players.Length; i++) Player.Clone(source.players[i], dest.players[i]); dest.selector = source.selector; dest.selector_player_id = source.selector_player_id; dest.selector_caster_uid = source.selector_caster_uid; dest.selector_ability_id = source.selector_ability_id; dest.last_destroyed = source.last_destroyed; dest.last_played = source.last_played; dest.last_target = source.last_target; dest.last_summoned = source.last_summoned; dest.ability_triggerer = source.ability_triggerer; dest.rolled_value = source.rolled_value; dest.selected_value = source.selected_value; CloneHash(source.ability_played, dest.ability_played); CloneHash(source.cards_attacked, dest.cards_attacked); } public static void CloneHash(HashSet source, HashSet dest) { dest.Clear(); foreach (string str in source) dest.Add(str); } } [System.Serializable] public enum GameState { Connecting = 0, //Players are not connected Play = 20, //Game is being played GameEnded = 99, } [System.Serializable] public enum GamePhase { None = 0, Mulligan = 5, StartTurn = 10, //Start of turn resolution Main = 20, //Main play phase EndTurn = 30, //End of turn resolutions } [System.Serializable] public enum SelectorType { None = 0, SelectTarget = 10, SelectorCard = 20, SelectorChoice = 30, SelectorCost = 40, } }