620 lines
20 KiB
C#
620 lines
20 KiB
C#
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<string> ability_played = new HashSet<string>();
|
|
public HashSet<string> cards_attacked = new HashSet<string>();
|
|
|
|
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<string> source, HashSet<string> 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,
|
|
}
|
|
} |