using System.Collections; using System.Collections.Generic; using UnityEngine; using TcgEngine.Gameplay; namespace TcgEngine { /// /// 定义所有能力数据 /// [CreateAssetMenu(fileName = "ability", menuName = "TcgEngine/AbilityData", order = 5)] public class AbilityData : ScriptableObject { public string id; [Header("Trigger")] public AbilityTrigger trigger; //WHEN does the ability trigger? public ConditionData[] conditions_trigger; //Condition checked on the card triggering the ability (usually the caster) [Header("Target")] public AbilityTarget target; //WHO is targeted? public ConditionData[] conditions_target; //Condition checked on the target to know if its a valid taget public FilterData[] filters_target; //Condition checked on the target to know if its a valid taget [Header("Effect")] public EffectData[] effects; //WHAT this does? public StatusData[] status; //Status added by this ability public int value; //Value passed to the effect (deal X damage) public int duration; //Duration passed to the effect (usually for status, 0=permanent) [Header("Chain/Choices")] public AbilityData[] chain_abilities; //Abilities that will be triggered after this one [Header("Activated Ability")] public int mana_cost; //Mana cost for activated abilities public bool exhaust; //Action cost for activated abilities [Header("FX")] public GameObject board_fx; public GameObject caster_fx; public GameObject target_fx; public GameObject projectile_fx; public AudioClip cast_audio; public AudioClip target_audio; public bool charge_target; [Header("Text")] public string title; [TextArea(5, 7)] public string desc; public static List ability_list = new List(); //Faster access in loops public static Dictionary ability_dict = new Dictionary(); //Faster access in Get(id) public static void Load(string folder = "") { if (ability_list.Count == 0) { ability_list.AddRange(Resources.LoadAll(folder)); foreach (AbilityData ability in ability_list) ability_dict.Add(ability.id, ability); } } public string GetTitle() { return title; } public string GetDesc() { return desc; } public string GetDesc(CardData card) { string dsc = desc; dsc = dsc.Replace("", card.title); dsc = dsc.Replace("", value.ToString()); dsc = dsc.Replace("", duration.ToString()); return dsc; } //Generic condition for the ability to trigger public bool AreTriggerConditionsMet(Game data, Card caster) { return AreTriggerConditionsMet(data, caster, caster); //Triggerer is the caster } //Some abilities are caused by another card (PlayOther), otherwise most of the time the triggerer is the caster, check condition on triggerer public bool AreTriggerConditionsMet(Game data, Card caster, Card trigger_card) { foreach (ConditionData cond in conditions_trigger) { if (cond != null) { if (!cond.IsTriggerConditionMet(data, this, caster)) return false; if (!cond.IsTargetConditionMet(data, this, caster, trigger_card)) return false; } } return true; } //Some abilities are caused by an action on a player (OnFight when attacking the player), check condition on that player public bool AreTriggerConditionsMet(Game data, Card caster, Player trigger_player) { foreach (ConditionData cond in conditions_trigger) { if (cond != null) { if (!cond.IsTriggerConditionMet(data, this, caster)) return false; if (!cond.IsTargetConditionMet(data, this, caster, trigger_player)) return false; } } return true; } //Check if the card target is valid public bool AreTargetConditionsMet(Game data, Card caster, Card target_card) { foreach (ConditionData cond in conditions_target) { if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_card)) return false; } return true; } //Check if the player target is valid public bool AreTargetConditionsMet(Game data, Card caster, Player target_player) { foreach (ConditionData cond in conditions_target) { if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_player)) return false; } return true; } //Check if the slot target is valid public bool AreTargetConditionsMet(Game data, Card caster, Slot target_slot) { foreach (ConditionData cond in conditions_target) { if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_slot)) return false; } return true; } //Check if the card data target is valid public bool AreTargetConditionsMet(Game data, Card caster, CardData target_card) { foreach (ConditionData cond in conditions_target) { if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_card)) return false; } return true; } //CanTarget is similar to AreTargetConditionsMet but only applies to targets on the board, with extra board-only conditions public bool CanTarget(Game data, Card caster, Card target) { if (target.HasStatus(StatusType.Stealth)) return false; //Hidden if (target.HasStatus(StatusType.SpellImmunity)) return false; //Spell immunity bool condition_match = AreTargetConditionsMet(data, caster, target); return condition_match; } //Can target check additional restrictions and is usually for SelectTarget or PlayTarget abilities public bool CanTarget(Game data, Card caster, Player target) { bool condition_match = AreTargetConditionsMet(data, caster, target); return condition_match; } public bool CanTarget(Game data, Card caster, Slot target) { return AreTargetConditionsMet(data, caster, target); //No additional conditions for slots } //Check if destination array has the target after being filtered, used to support filters in CardSelector public bool IsCardSelectionValid(Game data, Card caster, Card target, ListSwap card_array = null) { List targets = GetCardTargets(data, caster, card_array); return targets.Contains(target); //Card is still in array after filtering } public void DoEffects(GameLogic logic, Card caster) { foreach(EffectData effect in effects) effect?.DoEffect(logic, this, caster); } public void DoEffects(GameLogic logic, Card caster, Card target) { foreach (EffectData effect in effects) effect?.DoEffect(logic, this, caster, target); foreach(StatusData stat in status) target.AddStatus(stat, value, duration); } public void DoEffects(GameLogic logic, Card caster, Player target) { foreach (EffectData effect in effects) effect?.DoEffect(logic, this, caster, target); foreach (StatusData stat in status) target.AddStatus(stat, value, duration); } public void DoEffects(GameLogic logic, Card caster, Slot target) { foreach (EffectData effect in effects) effect?.DoEffect(logic, this, caster, target); } public void DoEffects(GameLogic logic, Card caster, CardData target) { foreach (EffectData effect in effects) effect?.DoEffect(logic, this, caster, target); } public void DoOngoingEffects(GameLogic logic, Card caster, Card target) { foreach (EffectData effect in effects) effect?.DoOngoingEffect(logic, this, caster, target); foreach (StatusData stat in status) target.AddOngoingStatus(stat, value); } public void DoOngoingEffects(GameLogic logic, Card caster, Player target) { foreach (EffectData effect in effects) effect?.DoOngoingEffect(logic, this, caster, target); foreach (StatusData stat in status) target.AddOngoingStatus(stat, value); } public bool HasEffect() where T : EffectData { foreach (EffectData eff in effects) { if (eff != null && eff is T) return true; } return false; } public bool HasStatus(StatusType type) { foreach (StatusData sta in status) { if (sta != null && sta.effect == type) return true; } return false; } public int GetDamage() { int damage = 0; foreach (EffectData eff in effects) { if (eff != null && eff is EffectDamage) { damage += this.value; } } return damage; } private void AddValidCards(Game data, Card caster, List source, List targets) { foreach (Card card in source) { if (AreTargetConditionsMet(data, caster, card)) targets.Add(card); } } //Return cards targets, memory_array is used for optimization and avoid allocating new memory public List GetCardTargets(Game data, Card caster, ListSwap memory_array = null) { if (memory_array == null) memory_array = new ListSwap(); //Slow operation List targets = memory_array.Get(); if (target == AbilityTarget.Self) { if (AreTargetConditionsMet(data, caster, caster)) targets.Add(caster); } if (target == AbilityTarget.AllCardsBoard || target == AbilityTarget.SelectTarget) { foreach (Player player in data.players) { foreach (Card card in player.cards_board) { if (AreTargetConditionsMet(data, caster, card)) targets.Add(card); } } } if (target == AbilityTarget.AllCardsHand) { foreach (Player player in data.players) { foreach (Card card in player.cards_hand) { if (AreTargetConditionsMet(data, caster, card)) targets.Add(card); } } } if (target == AbilityTarget.AllCardsAllPiles || target == AbilityTarget.CardSelector) { foreach (Player player in data.players) { AddValidCards(data, caster, player.cards_deck, targets); AddValidCards(data, caster, player.cards_discard, targets); AddValidCards(data, caster, player.cards_hand, targets); AddValidCards(data, caster, player.cards_secret, targets); AddValidCards(data, caster, player.cards_board, targets); AddValidCards(data, caster, player.cards_equip, targets); AddValidCards(data, caster, player.cards_temp, targets); } } if (target == AbilityTarget.LastPlayed) { Card target = data.GetCard(data.last_played); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } if (target == AbilityTarget.LastDestroyed) { Card target = data.GetCard(data.last_destroyed); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } if (target == AbilityTarget.LastTargeted) { Card target = data.GetCard(data.last_target); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } if (target == AbilityTarget.LastTargetedLeft) { Card lastTarget = data.GetCard(data.last_target); if (lastTarget != null && lastTarget.slot.x > Slot.x_min) { Slot leftSlot = new Slot(lastTarget.slot.x - 1, lastTarget.slot.y, lastTarget.slot.p); Card leftCard = data.GetSlotCard(leftSlot); if (leftCard != null && AreTargetConditionsMet(data, caster, leftCard)) targets.Add(leftCard); } } if (target == AbilityTarget.LastTargetedRight) { Card lastTarget = data.GetCard(data.last_target); if (lastTarget != null && lastTarget.slot.x < Slot.x_max) { Slot rightSlot = new Slot(lastTarget.slot.x + 1, lastTarget.slot.y, lastTarget.slot.p); Card rightCard = data.GetSlotCard(rightSlot); if (rightCard != null && AreTargetConditionsMet(data, caster, rightCard)) targets.Add(rightCard); } } if (target == AbilityTarget.LastSummoned) { Card target = data.GetCard(data.last_summoned); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } if (target == AbilityTarget.AbilityTriggerer) { Card target = data.GetCard(data.ability_triggerer); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } if (target == AbilityTarget.EquippedCard) { if (caster.CardData.IsEquipment()) { //Get bearer of the equipment Player player = data.GetPlayer(caster.player_id); Card target = player.GetBearerCard(caster); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } else if(caster.equipped_uid != null) { //Get equipped card Card target = data.GetCard(caster.equipped_uid); if (target != null && AreTargetConditionsMet(data, caster, target)) targets.Add(target); } } //Filter targets if (filters_target != null && targets.Count > 0) { foreach (FilterData filter in filters_target) { if (filter != null) targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets)); } } return targets; } //Return player targets, memory_array is used for optimization and avoid allocating new memory public List GetPlayerTargets(Game data, Card caster, ListSwap memory_array = null) { if (memory_array == null) memory_array = new ListSwap(); //Slow operation List targets = memory_array.Get(); if (target == AbilityTarget.PlayerSelf) { Player player = data.GetPlayer(caster.player_id); targets.Add(player); } else if (target == AbilityTarget.PlayerOpponent) { for (int tp = 0; tp < data.players.Length; tp++) { if (tp != caster.player_id) { Player oplayer = data.players[tp]; targets.Add(oplayer); } } } else if (target == AbilityTarget.AllPlayers) { foreach (Player player in data.players) { if (AreTargetConditionsMet(data, caster, player)) targets.Add(player); } } //Filter targets if (filters_target != null && targets.Count > 0) { foreach (FilterData filter in filters_target) { if (filter != null) targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets)); } } return targets; } //Return slot targets, memory_array is used for optimization and avoid allocating new memory public List GetSlotTargets(Game data, Card caster, ListSwap memory_array = null) { if (memory_array == null) memory_array = new ListSwap(); //Slow operation List targets = memory_array.Get(); if (target == AbilityTarget.AllSlots) { List slots = Slot.GetAll(); foreach (Slot slot in slots) { if (AreTargetConditionsMet(data, caster, slot)) targets.Add(slot); } } //Filter targets if (filters_target != null && targets.Count > 0) { foreach (FilterData filter in filters_target) { if (filter != null) targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets)); } } return targets; } public List GetCardDataTargets(Game data, Card caster, ListSwap memory_array = null) { if (memory_array == null) memory_array = new ListSwap(); //Slow operation List targets = memory_array.Get(); if (target == AbilityTarget.AllCardData) { foreach (CardData card in CardData.GetAll()) { if (AreTargetConditionsMet(data, caster, card)) targets.Add(card); } } //Filter targets if (filters_target != null && targets.Count > 0) { foreach (FilterData filter in filters_target) { if (filter != null) targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets)); } } return targets; } // Check if there is any valid target, if not, AI wont try to cast activated ability public bool HasValidSelectTarget(Game game_data, Card caster) { if (target == AbilityTarget.SelectTarget) { if (HasValidBoardCardTarget(game_data, caster)) return true; if (HasValidPlayerTarget(game_data, caster)) return true; if (HasValidSlotTarget(game_data, caster)) return true; return false; } if (target == AbilityTarget.CardSelector) { if (HasValidCardTarget(game_data, caster)) return true; return false; } if (target == AbilityTarget.ChoiceSelector) { foreach (AbilityData choice in chain_abilities) { if(choice.AreTriggerConditionsMet(game_data, caster)) return true; } return false; } return true; //Not selecting, valid } public bool HasValidBoardCardTarget(Game game_data, Card caster) { for (int p = 0; p < game_data.players.Length; p++) { Player player = game_data.players[p]; for (int c = 0; c < player.cards_board.Count; c++) { Card card = player.cards_board[c]; if (CanTarget(game_data, caster, card)) return true; } } return false; } public bool HasValidCardTarget(Game game_data, Card caster) { for (int p = 0; p < game_data.players.Length; p++) { Player player = game_data.players[p]; bool v1 = HasValidCardTarget(game_data, caster, player.cards_deck); bool v2 = HasValidCardTarget(game_data, caster, player.cards_discard); bool v3 = HasValidCardTarget(game_data, caster, player.cards_hand); bool v4 = HasValidCardTarget(game_data, caster, player.cards_board); bool v5 = HasValidCardTarget(game_data, caster, player.cards_equip); bool v6 = HasValidCardTarget(game_data, caster, player.cards_secret); bool v7 = HasValidCardTarget(game_data, caster, player.cards_temp); if (v1 || v2 || v3 || v4 || v5 || v6 || v7) return true; } return false; } public bool HasValidCardTarget(Game game_data, Card caster, List list) { for (int c = 0; c < list.Count; c++) { Card card = list[c]; if (AreTargetConditionsMet(game_data, caster, card)) return true; } return false; } public bool HasValidPlayerTarget(Game game_data, Card caster) { for (int p = 0; p < game_data.players.Length; p++) { Player player = game_data.players[p]; if (CanTarget(game_data, caster, player)) return true; } return false; } public bool HasValidSlotTarget(Game game_data, Card caster) { foreach (Slot slot in Slot.GetAll()) { if (CanTarget(game_data, caster, slot)) return true; } return false; } public bool IsSelector() { return target == AbilityTarget.SelectTarget || target == AbilityTarget.CardSelector || target == AbilityTarget.ChoiceSelector; } public static AbilityData Get(string id) { if (id == null) return null; bool success = ability_dict.TryGetValue(id, out AbilityData ability); if (success) return ability; return null; } public static List GetAll() { return ability_list; } } public enum AbilityTrigger { None = 0, Ongoing = 2, //Always active (does not work with all effects) Activate = 5, //Action OnPlay = 10, //When playeds OnPlayOther = 12, //When another card played StartOfTurn = 20, //Every turn EndOfTurn = 22, //Every turn OnBeforeAttack = 30, //When attacking, before damage OnAfterAttack = 31, //When attacking, after damage if still alive OnBeforeDefend = 32, //When being attacked, before damage OnAfterDefend = 33, //When being attacked, after damage if still alive OnKill = 35, //When killing another card during an attack OnDeath = 40, //When dying OnDeathOther = 42, //When another dying } public enum AbilityTarget { None = 0, Self = 1, PlayerSelf = 4, PlayerOpponent = 5, AllPlayers = 7, AllCardsBoard = 10, AllCardsHand = 11, AllCardsAllPiles = 12, AllSlots = 15, AllCardData = 17, //For card Create effects only PlayTarget = 20, //The target selected at the same time the spell was played (spell only) AbilityTriggerer = 25, //The card that triggered the trap EquippedCard = 27, //If equipment, the bearer, if character, the item equipped SelectTarget = 30, //Select a card, player or slot on board CardSelector = 40, //Card selector menu ChoiceSelector = 50, //Choice selector menu LastPlayed = 70, //Last card that was played LastTargeted = 72, //Last card that was targeted with an ability LastTargetedLeft = 73, //Last card that was targeted with an ability and is on the left side of the board LastTargetedRight = 74, //Last card that was targeted with an ability and is on the right side of the board LastDestroyed = 75, //Last card that was killed LastSummoned = 77, //Last card that was summoned or created } }