This commit is contained in:
yaoyanwei
2025-08-04 16:45:48 +08:00
parent 565aa16389
commit 2f2a601227
2296 changed files with 522745 additions and 93 deletions

View File

@@ -0,0 +1,745 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine
{
//Represent the current state of a card during the game (data only)
[System.Serializable]
public class Card
{
public string card_id;
public string uid;
public int player_id;
public string variant_id;
public Slot slot;
public bool exhausted;
public int damage = 0;
public int mana = 0;
public int attack = 0;
public int hp = 0;
public int mana_ongoing = 0;
public int attack_ongoing = 0;
public int hp_ongoing = 0;
public string equipped_uid = null;
public List<CardTrait> traits = new List<CardTrait>();
public List<CardTrait> ongoing_traits = new List<CardTrait>();
public List<CardStatus> status = new List<CardStatus>();
public List<CardStatus> ongoing_status = new List<CardStatus>();
public List<string> abilities = new List<string>();
public List<string> abilities_ongoing = new List<string>();
[System.NonSerialized] private int hash = 0;
[System.NonSerialized] private CardData data = null;
[System.NonSerialized] private VariantData vdata = null;
[System.NonSerialized] private List<AbilityData> abilities_data = null;
public Card(string card_id, string uid, int player_id) { this.card_id = card_id; this.uid = uid; this.player_id = player_id; }
public virtual void Refresh() { exhausted = false; }
public virtual void ClearOngoing() { ongoing_status.Clear(); ongoing_traits.Clear(); ClearOngoingAbility(); attack_ongoing = 0; hp_ongoing = 0; mana_ongoing = 0; }
public virtual void Clear()
{
ClearOngoing(); Refresh(); damage = 0; status.Clear();
SetCard(CardData, VariantData); //Reset to initial stats
equipped_uid = null;
}
public virtual int GetAttack() { return Mathf.Max(attack + attack_ongoing, 0); }
public virtual int GetHP() { return Mathf.Max(hp + hp_ongoing - damage, 0); }
public virtual int GetHP(int offset) { return Mathf.Max(hp + hp_ongoing - damage + offset, 0); }
public virtual int GetHPMax() { return Mathf.Max(hp + hp_ongoing, 0); }
public virtual int GetMana() { return Mathf.Max(mana + mana_ongoing, 0); }
public virtual void SetCard(CardData icard, VariantData cvariant)
{
data = icard;
card_id = icard.id;
variant_id = cvariant.id;
attack = icard.attack;
hp = icard.hp;
mana = icard.mana;
SetTraits(icard);
SetAbilities(icard);
}
public void SetTraits(CardData icard)
{
traits.Clear();
foreach (TraitData trait in icard.traits)
SetTrait(trait.id, 0);
if (icard.stats != null)
{
foreach (TraitStat stat in icard.stats)
SetTrait(stat.trait.id, stat.value);
}
}
public void SetAbilities(CardData icard)
{
abilities.Clear();
abilities_ongoing.Clear();
if (abilities_data != null)
abilities_data.Clear();
foreach (AbilityData ability in icard.abilities)
AddAbility(ability);
}
//------ Custom Traits/Stats ---------
public void SetTrait(string id, int value)
{
CardTrait trait = GetTrait(id);
if (trait != null)
{
trait.value = value;
}
else
{
trait = new CardTrait(id, value);
traits.Add(trait);
}
}
public void AddTrait(string id, int value)
{
CardTrait trait = GetTrait(id);
if (trait != null)
trait.value += value;
else
SetTrait(id, value);
}
public void AddOngoingTrait(string id, int value)
{
CardTrait trait = GetOngoingTrait(id);
if (trait != null)
{
trait.value += value;
}
else
{
trait = new CardTrait(id, value);
ongoing_traits.Add(trait);
}
}
public void RemoveTrait(string id)
{
for (int i = traits.Count - 1; i >= 0; i--)
{
if (traits[i].id == id)
traits.RemoveAt(i);
}
}
public CardTrait GetTrait(string id)
{
foreach (CardTrait trait in traits)
{
if (trait.id == id)
return trait;
}
return null;
}
public CardTrait GetOngoingTrait(string id)
{
foreach (CardTrait trait in ongoing_traits)
{
if (trait.id == id)
return trait;
}
return null;
}
public int GetTraitValue(TraitData trait)
{
if (trait != null)
return GetTraitValue(trait.id);
return 0;
}
public virtual int GetTraitValue(string id)
{
int val = 0;
CardTrait stat1 = GetTrait(id);
CardTrait stat2 = GetOngoingTrait(id);
if (stat1 != null)
val += stat1.value;
if (stat2 != null)
val += stat2.value;
return val;
}
public bool HasTrait(TraitData trait)
{
if (trait != null)
return HasTrait(trait.id);
return false;
}
public bool HasTrait(string id)
{
return GetTrait(id) != null || GetOngoingTrait(id) != null;
}
public List<CardTrait> GetAllTraits()
{
List<CardTrait> all_traits = new List<CardTrait>();
all_traits.AddRange(traits);
all_traits.AddRange(ongoing_traits);
return all_traits;
}
//Alternate names since traits/stats are stored in same var
public void SetStat(string id, int value) => SetTrait(id, value);
public void AddStat(string id, int value) => AddTrait(id, value);
public void AddOngoingStat(string id, int value) => AddOngoingTrait(id, value);
public void RemoveStat(string id) => RemoveTrait(id);
public int GetStatValue(TraitData trait) => GetTraitValue(trait);
public int GetStatValue(string id) => GetTraitValue(id);
public bool HasStat(TraitData trait) => HasTrait(trait);
public bool HasStat(string id) => HasTrait(id);
public List<CardTrait> GetAllStats() => GetAllTraits();
//------ Status Effects ---------
public void AddStatus(StatusData status, int value, int duration)
{
if (status != null)
AddStatus(status.effect, value, duration);
}
public void AddOngoingStatus(StatusData status, int value)
{
if (status != null)
AddOngoingStatus(status.effect, value);
}
public void AddStatus(StatusType type, int value, int duration)
{
if (type != StatusType.None)
{
CardStatus status = GetStatus(type);
if (status == null)
{
status = new CardStatus(type, value, duration);
this.status.Add(status);
}
else
{
status.value += value;
status.duration = Mathf.Max(status.duration, duration);
status.permanent = status.permanent || duration == 0;
}
}
}
public void AddOngoingStatus(StatusType type, int value)
{
if (type != StatusType.None)
{
CardStatus status = GetOngoingStatus(type);
if (status == null)
{
status = new CardStatus(type, value, 0);
ongoing_status.Add(status);
}
else
{
status.value += value;
}
}
}
public void RemoveStatus(StatusType type)
{
for (int i = status.Count - 1; i >= 0; i--)
{
if (status[i].type == type)
status.RemoveAt(i);
}
}
public List<CardStatus> GetAllStatus()
{
List<CardStatus> all_status = new List<CardStatus>();
all_status.AddRange(status);
all_status.AddRange(ongoing_status);
return all_status;
}
public bool HasStatus(StatusType type)
{
return GetStatus(type) != null || GetOngoingStatus(type) != null;
}
public CardStatus GetStatus(StatusType type)
{
foreach (CardStatus status in status)
{
if (status.type == type)
return status;
}
return null;
}
public CardStatus GetOngoingStatus(StatusType type)
{
foreach (CardStatus status in ongoing_status)
{
if (status.type == type)
return status;
}
return null;
}
public virtual int GetStatusValue(StatusType type)
{
CardStatus status1 = GetStatus(type);
CardStatus status2 = GetOngoingStatus(type);
int v1 = status1 != null ? status1.value : 0;
int v2 = status2 != null ? status2.value : 0;
return v1 + v2;
}
public virtual void ReduceStatusDurations()
{
for (int i = status.Count - 1; i >= 0; i--)
{
if (!status[i].permanent)
{
status[i].duration -= 1;
if (status[i].duration <= 0)
status.RemoveAt(i);
}
}
}
//----- Abilities ------------
public void AddAbility(AbilityData ability)
{
abilities.Add(ability.id);
if (abilities_data != null)
abilities_data.Add(ability);
}
public void RemoveAbility(AbilityData ability)
{
abilities.Remove(ability.id);
if (abilities_data != null)
abilities_data.Remove(ability);
}
public void AddOngoingAbility(AbilityData ability)
{
if (!abilities_ongoing.Contains(ability.id) && !abilities.Contains(ability.id))
{
abilities_ongoing.Add(ability.id);
if (abilities_data != null)
abilities_data.Add(ability);
}
}
public void ClearOngoingAbility()
{
if (abilities_data != null)
{
for (int i = abilities_data.Count - 1; i >= 0; i--)
{
AbilityData ability = abilities_data[i];
if (abilities_ongoing.Contains(ability.id))
abilities_data.RemoveAt(i);
}
}
abilities_ongoing.Clear();
}
public AbilityData GetAbility(AbilityTrigger trigger)
{
foreach (AbilityData iability in GetAbilities())
{
if (iability.trigger == trigger)
return iability;
}
return null;
}
public bool HasAbility(AbilityData ability)
{
foreach (AbilityData iability in GetAbilities())
{
if (iability.id == ability.id)
return true;
}
return false;
}
public bool HasAbility(AbilityTrigger trigger)
{
AbilityData iability = GetAbility(trigger);
if (iability != null)
return true;
return false;
}
public bool HasAbility(AbilityTrigger trigger, AbilityTarget target)
{
foreach (AbilityData iability in GetAbilities())
{
if (iability.trigger == trigger && iability.target == target)
return true;
}
return false;
}
public bool HasActiveAbility(Game data, AbilityTrigger trigger)
{
AbilityData iability = GetAbility(trigger);
if (iability != null && CanDoAbilities() && iability.AreTriggerConditionsMet(data, this))
return true;
return false;
}
public bool AreAbilityConditionsMet(AbilityTrigger ability_trigger, Game data, Card caster, Card triggerer)
{
foreach (AbilityData ability in GetAbilities())
{
if (ability && ability.trigger == ability_trigger && ability.AreTriggerConditionsMet(data, caster, triggerer))
return true;
}
return false;
}
public List<AbilityData> GetAbilities()
{
//Load abilities data, important to do this here since this array will be null after being sent through networking (cant serialize it)
if (abilities_data == null)
{
abilities_data = new List<AbilityData>(abilities.Count + abilities_ongoing.Count);
for (int i = 0; i < abilities.Count; i++)
abilities_data.Add(AbilityData.Get(abilities[i]));
for (int i = 0; i < abilities_ongoing.Count; i++)
abilities_data.Add(AbilityData.Get(abilities_ongoing[i]));
}
//Return
return abilities_data;
}
//---- Action Check ---------
public virtual bool CanAttack(bool skip_cost = false)
{
if (HasStatus(StatusType.Paralysed))
return false;
if (!skip_cost && exhausted)
return false; //no more action
return true;
}
public virtual bool CanMove(bool skip_cost = false)
{
//In demo we can move freely, since it has no effect
//if (HasStatusEffect(StatusEffect.Paralysed))
// return false;
//if (!skip_cost && exhausted)
// return false; //no more action
return true;
}
public virtual bool CanDoActivatedAbilities()
{
if (HasStatus(StatusType.Paralysed))
return false;
if (HasStatus(StatusType.Silenced))
return false;
return true;
}
public virtual bool CanDoAbilities()
{
if (HasStatus(StatusType.Silenced))
return false;
return true;
}
public virtual bool CanDoAnyAction()
{
return CanAttack() || CanMove() || CanDoActivatedAbilities();
}
//----------------
public CardData CardData
{
get {
if(data == null || data.id != card_id)
data = CardData.Get(card_id); //Optimization, store for future use
return data;
}
}
public VariantData VariantData
{
get
{
if (vdata == null || vdata.id != variant_id)
vdata = VariantData.Get(variant_id); //Optimization, store for future use
return vdata;
}
}
public CardData Data => CardData; //Alternate name
public int Hash
{
get {
if (hash == 0)
hash = Mathf.Abs(uid.GetHashCode()); //Optimization, store for future use
return hash;
}
}
public static Card Create(CardData icard, VariantData ivariant, Player player)
{
return Create(icard, ivariant, player, GameTool.GenerateRandomID(11, 15));
}
public static Card Create(CardData icard, VariantData ivariant, Player player, string uid)
{
Card card = new Card(icard.id, uid, player.player_id);
card.SetCard(icard, ivariant);
player.cards_all[card.uid] = card;
return card;
}
public static Card CloneNew(Card source)
{
Card card = new Card(source.card_id, source.uid, source.player_id);
Clone(source, card);
return card;
}
//Clone all card variables into another var, used mostly by the AI when building a prediction tree
public static void Clone(Card source, Card dest)
{
dest.card_id = source.card_id;
dest.uid = source.uid;
dest.player_id = source.player_id;
dest.variant_id = source.variant_id;
dest.slot = source.slot;
dest.exhausted = source.exhausted;
dest.damage = source.damage;
dest.attack = source.attack;
dest.hp = source.hp;
dest.mana = source.mana;
dest.mana_ongoing = source.mana_ongoing;
dest.attack_ongoing = source.attack_ongoing;
dest.hp_ongoing = source.hp_ongoing;
dest.equipped_uid = source.equipped_uid;
CardTrait.CloneList(source.traits, dest.traits);
CardTrait.CloneList(source.ongoing_traits, dest.ongoing_traits);
CardStatus.CloneList(source.status, dest.status);
CardStatus.CloneList(source.ongoing_status, dest.ongoing_status);
GameTool.CloneList(source.abilities, dest.abilities);
GameTool.CloneList(source.abilities_ongoing, dest.abilities_ongoing);
GameTool.CloneListRefNull(source.abilities_data, ref dest.abilities_data); //No need to deep copy since AbilityData doesn't change dynamically, its just a reference
}
//Clone a var that could be null
public static void CloneNull(Card source, ref Card dest)
{
//Source is null
if (source == null)
{
dest = null;
return;
}
//Dest is null
if (dest == null)
{
dest = CloneNew(source);
return;
}
//Both arent null, just clone
Clone(source, dest);
}
//Clone dictionary completely
public static void CloneDict(Dictionary<string, Card> source, Dictionary<string, Card> dest)
{
foreach (KeyValuePair<string, Card> pair in source)
{
bool valid = dest.TryGetValue(pair.Key, out Card val);
if (valid)
Clone(pair.Value, val);
else
dest[pair.Key] = CloneNew(pair.Value);
}
}
//Clone list by keeping references from ref_dict
public static void CloneListRef(Dictionary<string, Card> ref_dict, List<Card> source, List<Card> dest)
{
for (int i = 0; i < source.Count; i++)
{
Card scard = source[i];
bool valid = ref_dict.TryGetValue(scard.uid, out Card rcard);
if (valid)
{
if (i < dest.Count)
dest[i] = rcard;
else
dest.Add(rcard);
}
}
if(dest.Count > source.Count)
dest.RemoveRange(source.Count, dest.Count - source.Count);
}
}
[System.Serializable]
public class CardStatus
{
public StatusType type;
public int value;
public int duration = 1;
public bool permanent = true;
[System.NonSerialized]
private StatusData data = null;
public CardStatus() { }
public CardStatus(StatusType type, int value, int duration)
{
this.type = type;
this.value = value;
this.duration = duration;
this.permanent = (duration == 0);
}
public StatusData StatusData {
get
{
if (data == null || data.effect != type)
data = StatusData.Get(type);
return data;
}
}
public StatusData Data => StatusData; //Alternate name
public static CardStatus CloneNew(CardStatus copy)
{
CardStatus status = new CardStatus(copy.type, copy.value, copy.duration);
status.permanent = copy.permanent;
return status;
}
public static void Clone(CardStatus source, CardStatus dest)
{
dest.type = source.type;
dest.value = source.value;
dest.duration = source.duration;
dest.permanent = source.permanent;
}
public static void CloneList(List<CardStatus> source, List<CardStatus> dest)
{
for (int i = 0; i < source.Count; i++)
{
if (i < dest.Count)
Clone(source[i], dest[i]);
else
dest.Add(CloneNew(source[i]));
}
if (dest.Count > source.Count)
dest.RemoveRange(source.Count, dest.Count - source.Count);
}
}
[System.Serializable]
public class CardTrait
{
public string id;
public int value;
[System.NonSerialized]
private TraitData data = null;
public CardTrait(string id, int value)
{
this.id = id;
this.value = value;
}
public CardTrait(TraitData trait, int value)
{
this.id = trait.id;
this.value = value;
}
public TraitData TraitData
{
get
{
if (data == null || data.id != id)
data = TraitData.Get(id);
return data;
}
}
public TraitData Data => TraitData; //Alternate name
public static CardTrait CloneNew(CardTrait copy)
{
CardTrait status = new CardTrait(copy.id, copy.value);
return status;
}
public static void Clone(CardTrait source, CardTrait dest)
{
dest.id = source.id;
dest.value = source.value;
}
public static void CloneList(List<CardTrait> source, List<CardTrait> dest)
{
for (int i = 0; i < source.Count; i++)
{
if (i < dest.Count)
Clone(source[i], dest[i]);
else
dest.Add(CloneNew(source[i]));
}
if (dest.Count > source.Count)
dest.RemoveRange(source.Count, dest.Count - source.Count);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7b7f2eac4a77ae4c84447453e759223
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,610 @@
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.CanPayMana(card))
return false; //Cant pay mana
if (!player.HasCard(player.cards_hand, card))
return false; // Card not in hand
if (player.is_ai && card.CardData.IsDynamicManaCost() && player.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
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,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3d07ed3e0a32b04db41e8d2155c460a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
using Unity.Netcode;
using UnityEngine.Events;
namespace TcgEngine
{
/// <summary>
/// List of game actions and refreshes, that can be performed by the player or received
/// </summary>
public static class GameAction
{
public const ushort None = 0;
//Commands (client to server)
public const ushort PlayCard = 1000;
public const ushort Attack = 1010;
public const ushort AttackPlayer = 1012;
public const ushort Move = 1015;
public const ushort CastAbility = 1020;
public const ushort SelectCard = 1030;
public const ushort SelectPlayer = 1032;
public const ushort SelectSlot = 1034;
public const ushort SelectChoice = 1036;
public const ushort SelectCost = 1037;
public const ushort SelectMulligan = 1038;
public const ushort CancelSelect = 1039;
public const ushort EndTurn = 1040;
public const ushort Resign = 1050;
public const ushort ChatMessage = 1090;
public const ushort PlayerSettings = 1100; //After connect, send player data
public const ushort PlayerSettingsAI = 1102; //After connect, send player data
public const ushort GameSettings = 1105; //After connect, send gameplay settings
//Refresh (server to client)
public const ushort Connected = 2000;
public const ushort PlayerReady = 2001;
public const ushort GameStart = 2010;
public const ushort GameEnd = 2012;
public const ushort NewTurn = 2015;
public const ushort CardPlayed = 2020;
public const ushort CardSummoned = 2022;
public const ushort CardTransformed = 2023;
public const ushort CardDiscarded = 2025;
public const ushort CardDrawn = 2026;
public const ushort CardMoved = 2027;
public const ushort AttackStart = 2030;
public const ushort AttackEnd = 2031;
public const ushort AttackPlayerStart = 2032;
public const ushort AttackPlayerEnd = 2033;
public const ushort CardDamaged = 2036;
public const ushort PlayerDamaged = 2037;
public const ushort CardHealed = 2038;
public const ushort PlayerHealed = 2039;
public const ushort AbilityTrigger = 2040;
public const ushort AbilityTargetCard = 2042;
public const ushort AbilityTargetPlayer = 2043;
public const ushort AbilityTargetSlot = 2044;
public const ushort AbilityEnd = 2048;
public const ushort SecretTriggered = 2060;
public const ushort SecretResolved = 2061;
public const ushort ValueRolled = 2070;
public const ushort ServerMessage = 2190; //Server warning msg
public const ushort RefreshAll = 2100;
public static string GetString(ushort type)
{
if (type == GameAction.PlayCard)
return "play";
if (type == GameAction.Move)
return "move";
if (type == GameAction.Attack)
return "attack";
if (type == GameAction.AttackPlayer)
return "attack_player";
if (type == GameAction.CastAbility)
return "cast_ability";
if (type == GameAction.EndTurn)
return "end_turn";
if (type == GameAction.SelectCard)
return "select_card";
if (type == GameAction.SelectPlayer)
return "select_player";
if (type == GameAction.SelectChoice)
return "select_choice";
if (type == GameAction.SelectCost)
return "select_cost";
if (type == GameAction.SelectSlot)
return "select_slot";
if (type == GameAction.CancelSelect)
return "cancel_select";
if (type == GameAction.Resign)
return "resign";
if (type == GameAction.ChatMessage)
return "chat";
return type.ToString();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 892d094edaaf21b4fb54d54fa21e9e15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a6b9e34b292c584fb9a76c0823947fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,204 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
namespace TcgEngine
{
[System.Serializable]
public enum GameType
{
Solo = 0,
Adventure = 10,
Multiplayer = 20,
HostP2P = 30,
Observer = 40,
}
[System.Serializable]
public enum GameMode
{
Casual = 0,
Ranked = 10,
}
/// <summary>
/// Hold all client's game settings, like game mode, game uid and scene to load
/// will be sent to server when a match start
/// </summary>
[System.Serializable]
public class GameSettings : INetworkSerializable
{
public string server_url; //Server to connect to
public string game_uid; //Game uid on that server
public string scene; //Which scene to load
public int nb_players; //How many players, including AI (UI only supports 2)
public GameType game_type = GameType.Solo; //Multiplayer? Solo? Observer?
public GameMode game_mode = GameMode.Casual; //Ranked or not? Other special game mode?
public string level; //Adventure level ID
public virtual bool IsHost()
{
return game_type == GameType.Solo || game_type == GameType.Adventure || game_type == GameType.HostP2P;
}
public virtual bool IsOffline()
{
return game_type == GameType.Solo || game_type == GameType.Adventure;
}
public virtual bool IsOnline()
{
return game_type == GameType.HostP2P || game_type == GameType.Multiplayer || game_type == GameType.Observer;
}
public virtual bool IsOnlinePlayer()
{
return game_type == GameType.HostP2P || game_type == GameType.Multiplayer;
}
public virtual bool IsRanked()
{
return game_mode == GameMode.Ranked;
}
public virtual string GetUrl()
{
if (!string.IsNullOrEmpty(server_url))
return server_url;
return NetworkData.Get().url;
}
public virtual string GetScene()
{
if (!string.IsNullOrEmpty(scene))
return scene;
return GameplayData.Get().GetRandomArena();
}
public virtual string GetGameModeId()
{
if (game_mode == GameMode.Ranked)
return "ranked";
if (game_mode == GameMode.Casual)
return "casual";
return "";
}
public virtual LevelData GetLevel()
{
if (game_type == GameType.Adventure)
{
return LevelData.Get(level);
}
return null;
}
public virtual void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref server_url);
serializer.SerializeValue(ref game_uid);
serializer.SerializeValue(ref scene);
serializer.SerializeValue(ref game_type);
serializer.SerializeValue(ref game_mode);
serializer.SerializeValue(ref nb_players);
serializer.SerializeValue(ref level);
}
public static string GetRankModeString(GameMode rank_mode)
{
if (rank_mode == GameMode.Ranked)
return "ranked";
if (rank_mode == GameMode.Casual)
return "casual";
return "";
}
public static GameMode GetRankMode(string rank_id)
{
if (rank_id == "ranked")
return GameMode.Ranked;
if (rank_id == "casual")
return GameMode.Casual;
return GameMode.Casual;
}
public static GameSettings Default
{
get
{
GameSettings settings = new GameSettings();
settings.server_url = "";
settings.game_uid = "test";
settings.game_type = GameType.Solo;
settings.game_mode = GameMode.Casual;
settings.nb_players = 2;
settings.scene = "Game";
settings.level = "";
return settings;
}
}
}
/// <summary>
/// Hold all client's player settings, like avatar, cardback, and deck being used
/// will be sent to server when a match start
/// </summary>
[System.Serializable]
public class PlayerSettings : INetworkSerializable
{
public string username;
public string avatar;
public string cardback;
public int ai_level;
public UserDeckData deck = UserDeckData.Default;
public bool HasDeck()
{
return deck != null && !string.IsNullOrEmpty(deck.tid);
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref username);
serializer.SerializeValue(ref avatar);
serializer.SerializeValue(ref cardback);
serializer.SerializeValue(ref ai_level);
serializer.SerializeValue(ref deck);
}
public static PlayerSettings Default
{
get
{
PlayerSettings settings = new PlayerSettings();
settings.username = "Player";
settings.avatar = "";
settings.cardback = "";
settings.deck = UserDeckData.Default;
settings.ai_level = 1;
return settings;
}
}
public static PlayerSettings DefaultAI
{
get
{
PlayerSettings settings = new PlayerSettings();
settings.username = "AI";
settings.avatar = "";
settings.cardback = "";
settings.deck = UserDeckData.Default;
settings.ai_level = 10;
return settings;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d13a8ed425b26c4a94c0c63c5d55dbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,614 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine
{
//Represent the current state of a player during the game (data only)
[System.Serializable]
public class Player
{
public int player_id;
public string username;
public string avatar;
public string cardback;
public string deck;
public bool is_ai = false;
public int ai_level;
public bool connected = false; //Connected to server and game
public bool ready = false; //Sent all player data, ready to play
public int hp;
public int hp_max;
public int mana = 0;
public int mana_max = 0;
public int kill_count = 0;
public Dictionary<string, Card> cards_all = new Dictionary<string, Card>(); //Dictionnary for quick access to any card by UID
public Card hero = null;
public List<Card> cards_deck = new List<Card>(); //Cards in the player's deck
public List<Card> cards_hand = new List<Card>(); //Cards in the player's hand
public List<Card> cards_board = new List<Card>(); //Cards on the board
public List<Card> cards_equip = new List<Card>(); //Cards equipped by characters
public List<Card> cards_discard = new List<Card>(); //Cards in the player's discard
public List<Card> cards_secret = new List<Card>(); //Cards in the player's secret area
public List<Card> cards_temp = new List<Card>(); //Temporary cards that have just been created, not assigned to any zone yet
public List<CardTrait> traits = new List<CardTrait>(); //Current persistant traits the cards has
public List<CardTrait> ongoing_traits = new List<CardTrait>(); //Current ongoing traits the cards has
public List<CardStatus> status = new List<CardStatus>(); //Current persistant (or with duration) traits the cards has
public List<CardStatus> ongoing_status = new List<CardStatus>(); //Current ongoing traits the cards has
public List<ActionHistory> history_list = new List<ActionHistory>(); //History of actions performed by the player
public Player(int id) { this.player_id = id; }
public bool IsReady() { return ready && cards_all.Count > 0; }
public bool IsConnected() { return connected || is_ai; }
public virtual void ClearOngoing() { ongoing_status.Clear(); ongoing_traits.Clear(); }
//---- Cards ---------
public void AddCard(List<Card> card_list, Card card)
{
card_list.Add(card);
}
public void RemoveCard(List<Card> card_list, Card card)
{
card_list.Remove(card);
}
public virtual void RemoveCardFromAllGroups(Card card)
{
cards_deck.Remove(card);
cards_hand.Remove(card);
cards_board.Remove(card);
cards_equip.Remove(card);
cards_discard.Remove(card);
cards_secret.Remove(card);
cards_temp.Remove(card);
UnequipFromAllCards(card);
}
public virtual void UnequipFromAllCards(Card equip)
{
foreach (Card card in cards_board)
{
if (card.equipped_uid == equip.uid)
card.equipped_uid = null;
}
}
public virtual Card GetRandomCard(List<Card> card_list, System.Random rand)
{
if (card_list.Count > 0)
return card_list[rand.Next(0, card_list.Count)];
return null;
}
public bool HasCard(List<Card> card_list, Card card)
{
return card_list.Contains(card);
}
public Card GetHandCard(string uid)
{
foreach (Card card in cards_hand)
{
if (card.uid == uid)
return card;
}
return null;
}
public Card GetBoardCard(string uid)
{
foreach (Card card in cards_board)
{
if (card.uid == uid)
return card;
}
return null;
}
public Card GetEquipCard(string uid)
{
foreach (Card card in cards_equip)
{
if (card.uid == uid)
return card;
}
return null;
}
public Card GetDeckCard(string uid)
{
foreach (Card card in cards_deck)
{
if (card.uid == uid)
return card;
}
return null;
}
public Card GetDiscardCard(string uid)
{
foreach (Card card in cards_discard)
{
if (card.uid == uid)
return card;
}
return null;
}
public Card GetBearerCard(Card equipment)
{
foreach (Card card in cards_board)
{
if (card != null && card.equipped_uid == equipment.uid)
return card;
}
return null;
}
public Card GetSlotCard(Slot slot)
{
foreach (Card card in cards_board)
{
if (card != null && card.slot == slot)
return card;
}
return null;
}
public Card GetCard(string uid)
{
if (uid != null)
{
bool valid = cards_all.TryGetValue(uid, out Card card);
if (valid)
return card;
}
return null;
}
public bool IsOnBoard(Card card)
{
return card != null && GetBoardCard(card.uid) != null;
}
//---- Slots ---------
public Slot GetRandomSlot(System.Random rand)
{
return Slot.GetRandom(player_id, rand);
}
public virtual Slot GetRandomEmptySlot(System.Random rand, List<Slot> list_mem = null)
{
List<Slot> valid = GetEmptySlots(list_mem);
if (valid.Count > 0)
return valid[rand.Next(0, valid.Count)];
return Slot.None;
}
public virtual Slot GetRandomOccupiedSlot(System.Random rand, List<Slot> list_mem = null)
{
List<Slot> valid = GetOccupiedSlots(list_mem);
if (valid.Count > 0)
return valid[rand.Next(0, valid.Count)];
return Slot.None;
}
public List<Slot> GetEmptySlots(List<Slot> list_mem = null)
{
List<Slot> valid = list_mem != null ? list_mem : new List<Slot>();
foreach (Slot slot in Slot.GetAll(player_id))
{
Card slot_card = GetSlotCard(slot);
if (slot_card == null)
valid.Add(slot);
}
return valid;
}
public List<Slot> GetOccupiedSlots(List<Slot> list_mem = null)
{
List<Slot> valid = list_mem != null ? list_mem : new List<Slot>();
foreach (Slot slot in Slot.GetAll(player_id))
{
Card slot_card = GetSlotCard(slot);
if (slot_card != null)
valid.Add(slot);
}
return valid;
}
//------ Custom Traits/Stats ---------
public void SetTrait(string id, int value)
{
CardTrait trait = GetTrait(id);
if (trait != null)
{
trait.value = value;
}
else
{
trait = new CardTrait(id, value);
traits.Add(trait);
}
}
public void AddTrait(string id, int value)
{
CardTrait trait = GetTrait(id);
if (trait != null)
trait.value += value;
else
SetTrait(id, value);
}
public void AddOngoingTrait(string id, int value)
{
CardTrait trait = GetOngoingTrait(id);
if (trait != null)
{
trait.value += value;
}
else
{
trait = new CardTrait(id, value);
ongoing_traits.Add(trait);
}
}
public void RemoveTrait(string id)
{
for (int i = traits.Count - 1; i >= 0; i--)
{
if (traits[i].id == id)
traits.RemoveAt(i);
}
}
public CardTrait GetTrait(string id)
{
foreach (CardTrait trait in traits)
{
if (trait.id == id)
return trait;
}
return null;
}
public CardTrait GetOngoingTrait(string id)
{
foreach (CardTrait trait in ongoing_traits)
{
if (trait.id == id)
return trait;
}
return null;
}
public List<CardTrait> GetAllTraits()
{
List<CardTrait> all_traits = new List<CardTrait>();
all_traits.AddRange(traits);
all_traits.AddRange(ongoing_traits);
return all_traits;
}
public int GetTraitValue(TraitData trait)
{
if (trait != null)
return GetTraitValue(trait.id);
return 0;
}
public virtual int GetTraitValue(string id)
{
int val = 0;
CardTrait stat1 = GetTrait(id);
CardTrait stat2 = GetOngoingTrait(id);
if (stat1 != null)
val += stat1.value;
if (stat2 != null)
val += stat2.value;
return val;
}
public bool HasTrait(TraitData trait)
{
if (trait != null)
return HasTrait(trait.id);
return false;
}
public bool HasTrait(string id)
{
foreach (CardTrait trait in traits)
{
if (trait.id == id)
return true;
}
return false;
}
//---- Status ---------
public void AddStatus(StatusData status, int value, int duration)
{
if (status != null)
AddStatus(status.effect, value, duration);
}
public void AddOngoingStatus(StatusData status, int value)
{
if (status != null)
AddOngoingStatus(status.effect, value);
}
public void AddStatus(StatusType effect, int value, int duration)
{
if (effect != StatusType.None)
{
CardStatus status = GetStatus(effect);
if (status == null)
{
status = new CardStatus(effect, value, duration);
this.status.Add(status);
}
else
{
status.value += value;
status.duration = Mathf.Max(status.duration, duration);
status.permanent = status.permanent || duration == 0;
}
}
}
public void AddOngoingStatus(StatusType effect, int value)
{
if (effect != StatusType.None)
{
CardStatus status = GetOngoingStatus(effect);
if (status == null)
{
status = new CardStatus(effect, value, 0);
ongoing_status.Add(status);
}
else
{
status.value += value;
}
}
}
public void RemoveStatus(StatusType effect)
{
for (int i = status.Count - 1; i >= 0; i--)
{
if (status[i].type == effect)
status.RemoveAt(i);
}
}
public CardStatus GetStatus(StatusType effect)
{
foreach (CardStatus status in status)
{
if (status.type == effect)
return status;
}
return null;
}
public CardStatus GetOngoingStatus(StatusType effect)
{
foreach (CardStatus status in ongoing_status)
{
if (status.type == effect)
return status;
}
return null;
}
public List<CardStatus> GetAllStatus()
{
List<CardStatus> all_status = new List<CardStatus>();
all_status.AddRange(status);
all_status.AddRange(ongoing_status);
return all_status;
}
public bool HasStatus(StatusType effect)
{
return GetStatus(effect) != null || GetOngoingStatus(effect) != null;
}
public virtual int GetStatusValue(StatusType type)
{
CardStatus status1 = GetStatus(type);
CardStatus status2 = GetOngoingStatus(type);
int v1 = status1 != null ? status1.value : 0;
int v2 = status2 != null ? status2.value : 0;
return v1 + v2;
}
public virtual void ReduceStatusDurations()
{
for (int i = status.Count - 1; i >= 0; i--)
{
if (!status[i].permanent)
{
status[i].duration -= 1;
if (status[i].duration <= 0)
status.RemoveAt(i);
}
}
}
//---- History ---------
public void AddHistory(ushort type, Card card)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
history_list.Add(order);
}
public void AddHistory(ushort type, Card card, Card target)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
order.target_uid = target.uid;
history_list.Add(order);
}
public void AddHistory(ushort type, Card card, Player target)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
order.target_id = target.player_id;
history_list.Add(order);
}
public void AddHistory(ushort type, Card card, AbilityData ability)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
order.ability_id = ability.id;
history_list.Add(order);
}
public void AddHistory(ushort type, Card card, AbilityData ability, Card target)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
order.ability_id = ability.id;
order.target_uid = target.uid;
history_list.Add(order);
}
public void AddHistory(ushort type, Card card, AbilityData ability, Player target)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
order.ability_id = ability.id;
order.target_id = target.player_id;
history_list.Add(order);
}
public void AddHistory(ushort type, Card card, AbilityData ability, Slot target)
{
ActionHistory order = new ActionHistory();
order.type = type;
order.card_id = card.card_id;
order.card_uid = card.uid;
order.ability_id = ability.id;
order.slot = target;
history_list.Add(order);
}
//---- Action Check ---------
public virtual bool CanPayMana(Card card)
{
if (card.CardData.IsDynamicManaCost())
return true;
return mana >= card.GetMana();
}
public virtual void PayMana(Card card)
{
if (!card.CardData.IsDynamicManaCost())
mana -= card.GetMana();
}
public virtual bool CanPayAbility(Card card, AbilityData ability)
{
bool exhaust = !card.exhausted || !ability.exhaust;
return exhaust && mana >= ability.mana_cost;
}
public virtual bool IsDead()
{
if (cards_hand.Count == 0 && cards_board.Count == 0 && cards_deck.Count == 0)
return true;
if (hp <= 0)
return true;
return false;
}
//--------------------
//Clone all player variables into another var, used mostly by the AI when building a prediction tree
public static void Clone(Player source, Player dest)
{
dest.player_id = source.player_id;
dest.is_ai = source.is_ai;
dest.ai_level = source.ai_level;
//Commented variables are not needed for ai predictions
//dest.username = source.username;
//dest.avatar = source.avatar;
//dest.deck = source.deck;
//dest.connected = source.connected;
//dest.ready = source.ready;
dest.hp = source.hp;
dest.hp_max = source.hp_max;
dest.mana = source.mana;
dest.mana_max = source.mana_max;
dest.kill_count = source.kill_count;
Card.CloneNull(source.hero, ref dest.hero);
Card.CloneDict(source.cards_all, dest.cards_all);
Card.CloneListRef(dest.cards_all, source.cards_board, dest.cards_board);
Card.CloneListRef(dest.cards_all, source.cards_equip, dest.cards_equip);
Card.CloneListRef(dest.cards_all, source.cards_hand, dest.cards_hand);
Card.CloneListRef(dest.cards_all, source.cards_deck, dest.cards_deck);
Card.CloneListRef(dest.cards_all, source.cards_discard, dest.cards_discard);
Card.CloneListRef(dest.cards_all, source.cards_secret, dest.cards_secret);
Card.CloneListRef(dest.cards_all, source.cards_temp, dest.cards_temp);
CardStatus.CloneList(source.status, dest.status);
CardStatus.CloneList(source.ongoing_status, dest.ongoing_status);
}
}
[System.Serializable]
public class ActionHistory
{
public ushort type;
public string card_id;
public string card_uid;
public string target_uid;
public string ability_id;
public int target_id;
public Slot slot;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6da82cc0b350f5843b6597d730b654ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,210 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
namespace TcgEngine
{
/// <summary>
/// Represent a slot in gameplay (data only)
/// </summary>
[System.Serializable]
public struct Slot : INetworkSerializable
{
public int x; //From 1 to 5
public int y; //Not in use, could be used to add more rows or different locations on the board
public int p; //0 or 1, represent player ID
public static int x_min = 1; //Dont change this, should start at 1 (0,0,0 represent invalid slot)
public static int x_max = 5; //Number of slots in a row/zone
public static int y_min = 1; //Dont change this, should start at 1 (0,0,0 represent invalid slot)
public static int y_max = 1; //Set this to the number of rows/locations you want to have
public static bool ignore_p = false; //Set to true if you dont want to use P value
private static Dictionary<int, List<Slot>> player_slots = new Dictionary<int, List<Slot>>();
private static List<Slot> all_slots = new List<Slot>();
public Slot(int pid)
{
this.x = 0;
this.y = 0;
this.p = pid;
}
public Slot(int x, int y, int pid)
{
this.x = x;
this.y = y;
this.p = pid;
}
public Slot(SlotXY slot, int pid)
{
this.x = slot.x;
this.y = slot.y;
this.p = pid;
}
public bool IsInRangeX(Slot slot, int range)
{
return Mathf.Abs(x - slot.x) <= range;
}
public bool IsInRangeY(Slot slot, int range)
{
return Mathf.Abs(y - slot.y) <= range;
}
public bool IsInRangeP(Slot slot, int range)
{
return Mathf.Abs(p - slot.p) <= range;
}
//No Diagonal, Diagonal = 2 dist
public bool IsInDistanceStraight(Slot slot, int dist)
{
int r = Mathf.Abs(x - slot.x) + Mathf.Abs(y - slot.y) + Mathf.Abs(p - slot.p);
return r <= dist;
}
//Diagonal = 1 dist
public bool IsInDistance(Slot slot, int dist)
{
int dx = Mathf.Abs(x - slot.x);
int dy = Mathf.Abs(y - slot.y);
int dp = Mathf.Abs(p - slot.p);
return dx <= dist && dy <= dist && dp <= dist;
}
public bool IsPlayerSlot()
{
return x == 0 && y == 0;
}
//Check if the slot is valid one (or if out of board)
public bool IsValid()
{
return x >= x_min && x <= x_max && y >= y_min && y <= y_max && p >= 0;
}
public static int MaxP
{
get { return ignore_p ? 0 : 1; }
}
//Return slot P-value of player, usually its same as player_id, unless we ignore P value then its 0 for all
public static int GetP(int pid)
{
return ignore_p ? 0 : pid;
}
//Get a random slot on player side
public static Slot GetRandom(int pid, System.Random rand)
{
int p = GetP(pid);
if (y_max > y_min)
return new Slot(rand.Next(x_min, x_max + 1), rand.Next(y_min, y_max + 1), p);
return new Slot(rand.Next(x_min, x_max + 1), y_min, p);
}
//Get a random slot amongts all valid ones
public static Slot GetRandom(System.Random rand)
{
if (y_max > y_min)
return new Slot(rand.Next(x_min, x_max + 1), rand.Next(y_min, y_max + 1), rand.Next(0, 2));
return new Slot(rand.Next(x_min, x_max + 1), y_min, rand.Next(0, 2));
}
public static Slot Get(int x, int y, int p)
{
List<Slot> slots = GetAll();
foreach (Slot slot in slots)
{
if (slot.x == x && slot.y == y && slot.p == p)
return slot;
}
return new Slot(x, y, p);
}
//Get all slots on player side
public static List<Slot> GetAll(int pid)
{
int p = GetP(pid);
if (player_slots.ContainsKey(p))
return player_slots[p]; //Faster access
List<Slot> list = new List<Slot>();
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
list.Add(new Slot(x, y, p));
}
}
player_slots[p] = list;
return list;
}
//Get all valid slots
public static List<Slot> GetAll()
{
if (all_slots.Count > 0)
return all_slots; //Faster access
for (int p = 0; p <= MaxP; p++)
{
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
all_slots.Add(new Slot(x, y, p));
}
}
}
return all_slots;
}
public static bool operator ==(Slot slot1, Slot slot2)
{
return slot1.x == slot2.x && slot1.y == slot2.y && slot1.p == slot2.p;
}
public static bool operator !=(Slot slot1, Slot slot2)
{
return slot1.x != slot2.x || slot1.y != slot2.y || slot1.p != slot2.p;
}
public override bool Equals(object o)
{
return base.Equals(o);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref x);
serializer.SerializeValue(ref y);
serializer.SerializeValue(ref p);
}
public static Slot None
{
get { return new Slot(0, 0, 0); }
}
}
[System.Serializable]
public struct SlotXY
{
public int x;
public int y;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30f3e2f0cce43684085b88d531bdd8cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: