Skip to content

Instantly share code, notes, and snippets.

@LuizMoratelli
Last active August 6, 2023 07:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LuizMoratelli/eb9ac0b29085348daa79702271d4edb1 to your computer and use it in GitHub Desktop.
Save LuizMoratelli/eb9ac0b29085348daa79702271d4edb1 to your computer and use it in GitHub Desktop.
[TCG Engine] Imitating Hearthstone cards and learning in the process

Sobre / About

PT-BR

Esse Gist irá conter várias cartas inspiradas em Hearthstone, como forma de aprendizado e ensino para pessoas que estejam interessadas em aprimorar suas habilidades com o TCG Engine.

EN

This Gist will contain innumerous cards inspired in Hearthstone, as a way to learn and teach people about how be better while using TCG Engine.

Cards

Disclaimer

PT-BR

Esse Gist NÃO irá conter arquivos completos ou diretos do Asset em questão. É OBRIGATÓRIO que você possua o Asset para pode reproduzir os passos, ideias e cartas compartilhados aqui.

As soluções apresentadas nas cartas não são as únicas e possivelmente não são as melhores, sinta-se a vontade para opinar sobre melhorias tanto quanto para criá-las de outras maneiras.

EN

This gist will NOT contain the complete files from the Asset. You MUST have the Asset to be able to reproduce the steps, ideas and cards shared here.

The solutions presented in the cards are not the only ones and possibly not the best ones, feel free to comment on improvements as well as to create them in other ways.

Explosive Shot (Classic)

Description

(5) "Deal 5 damage to a minion and 2 damage to adjacent ones."

Steps

  1. Create a new Ability with "Damage" effect, "value" equals 5.
    image

  2. Create another Ability with "Damage" effect, "value" equals 2, Target "All Cards Board" with conditions "is_slot_next_to" and "not_last_target"*
    image

  3. Create a new Card "explosive shot" with both abilities
    image

  • Check out Swipe to see how not_last_target was made.

Force of Nature (Classic)

Description

(6) "Summon three 2/2 Treants with Charge that die at the end of the turn."

Steps

  1. Create a new Card "charge_treant" set up the stats and add "play_haste" in abilities
    image

  2. Create a new EffectSummon to summon "charge_treant"
    image

  3. Create a new Ability with the "summon_charge_treant" effect*
    image

  4. Create a new Card and add 3x "spell_summon_charge_treant"
    image

Innervate (Classic)

Description

(0) "Gain 2 Mana Crystals this turn only."

Steps

  1. Create a new Ability "spell_gain_mana_2" using the gain mana effect (same used for coin) image

  2. Create a new Card with "spell_gain_mana_2" ability image

Naturalize (Classic)

Description

(1) "Destroy a minion. Your opponent draws 2 cards."

Steps

  1. Create a new Ability "opponent_draw2" using the "draw" (value: 2) effect targeting "Player Opponent" image

  2. Create a new Card with "spell destroy" and "opponent_draw2" abilities image

Power of the wild (Classic)

Description

(2) "Choose One - Give your minions +1/+1; or Summon a 3/2 Panther."

Steps

  1. Create a new Ability "spell_ally_attack_hp_1" using the "add_hp" and "add_attack" (values: 1) effect targeting "All Cards Board" filtered by "is_ally"
    image

  2. Create a new summon "panther" with 2 attack and 3 health.
    image

  3. Create a new Effect "summon_panther" based on "EffectSummon" with the Card Data selected.
    image

  4. Create a new Scriptable Object called "FilterLast" extending FilterData to be able to summon creatures on the left

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace TcgEngine
{
    [CreateAssetMenu(fileName = "filter", menuName = "TcgEngine/Filter/Last", order = 10)]
    public class FilterLast : FilterData
    {
        public int amount = 1; //Number of first targets selected

        public override List<Card> FilterTargets(Game data, AbilityData ability, Card caster, List<Card> source, List<Card> dest)
        {
            int max = source.Count - 1;
            int min = source.Count - Mathf.Min(amount, max);
            for (int i = max; i >= min; i--)
                dest.Add(source[i]);
            return dest;
        }

        public override List<Player> FilterTargets(Game data, AbilityData ability, Card caster, List<Player> source, List<Player> dest)
        {
            int max = source.Count - 1;
            int min = source.Count - Mathf.Min(amount, max);
            for (int i = max; i >= min; i--)
                dest.Add(source[i]);
            return dest;
        }

        public override List<Slot> FilterTargets(Game data, AbilityData ability, Card caster, List<Slot> source, List<Slot> dest)
        {
            int max = source.Count - 1;
            int min = source.Count - Mathf.Min(amount, source.Count);
            for (int i = max; i >= min; i--)
                dest.Add(source[i]);
            return dest;
        }
    }
}
  1. Create a new filter "filter_last_1" based on the SO with amount of 1.
    image

  2. Create a new Ability "spell_summon_panther" using the "summon_panther" effect targeting "All Slots" conditioned by "is_slot_empty" and "is_ally" filtered by "filter_last_1".
    image

  3. Create a new Ability with the 2 abilities as choices and target as "Choice Selector"
    image

  4. Create a new Card to call the spell ability
    image

Soul of the Forest (Classic)

Description

(4) "Give your minions "Deathrattle: Summon a 2/2 Treant."

Steps

  1. Create a new Card "treant" with attack and health equals 2
    image

  2. Create a new Effect Summon with Summon "treant"
    image

  3. Create a new Ability "death_treant" with trigger "On Death", Target "All Slots" conditioned to "is_slot_empty" and "is_ally" with filter of "filter_last_1" and effect "summon_treant"
    image

  4. Create a new Effect SO with the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Gameplay;

namespace TcgEngine
{
    [CreateAssetMenu(fileName = "effect", menuName = "TcgEngine/Effect/GiveAbility", order = 10)]
    public class EffectGiveAbility : EffectData
    {
        public AbilityData abilityToGive;

        public override void DoEffect(GameLogic logic, AbilityData ability, Card caster, Card target)
        {
            target.SetAbility(abilityToGive);
        }
    }
}
  1. Create a new Effect with Ability To Give "death_treant"
    image

  2. Create a new Ability targeting "All Cards Board" conditioned by "is_ally" with the "give_death_summon_treant" effect
    image

  3. Create a new Card with the spell ability
    image

Extra

To enable TCG Engine to add abilities to Cards in runtime we will need some extra code:

  1. Card.cs
// 39 Line
[System.NonSerialized] private List<AbilityData> abilities = new List<AbilityData>();

// 319 Line (inside GetAbility)
foreach (AbilityData iability in abilities)
{
  if (iability.trigger == trigger)
    return iability;
}

// 329 Line
public AbilityData[] GetAllAbilities()
{
    if (abilities == null) return CardData.abilities;


    return abilities.Concat(CardData.abilities).ToArray();
}

public void SetAbility(AbilityData newAbility)
{
    abilities.Add(newAbility);
}

// 349 Line (inside HasAbility)
foreach (AbilityData iability in abilities)
{
    if (iability == ability)
        return true;
}
  1. Game.cs
// 217, 234 and 258 (caster.CardData.abilities)
foreach (AbilityData ability in caster.GetAllAbilities())
  1. GameLogic.cs
// 899 and 931 (caster.CardData.abilities)
foreach (AbilityData iability in caster.GetAllAbilities())

Swipe (Classic)

Description

(4) "Deal 4 damage to an enemy and 1 damage to all other enemies."

Steps

  1. Create a new Conditional SO to filter targets that are not the previous one
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.AI;

namespace TcgEngine
{
    [CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/ConditionNotLastTarget", order = 10)]
    public class ConditionNotLastTarget : ConditionData
    {

        public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
        {
            return target.uid != data.last_selected_target;
        }

        public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
        {
            return target.player_id.ToString() != data.last_selected_target;
        }
    }
}
  1. Create a new conditional using the previous SO
    image

  2. Create a new Ability "spell_damage1_all" that deals 1 damage, conditioned to "is_not_empty", "is_enemy" and "not_last_target"
    image

  3. Create a new Ability "spell_swap" that deals 4 damage and have "spell_damage1_all" as chain ability
    image

  4. Create a new Card "swap"
    image

Extra

In order to make swipe work as in Hearthstone we will need to add a property to store the last selected target that could be a player or a card.

  1. Add a new Enum value to AbilityTarget (AbilityData.cs)
AllCardsBoardAllPlayers = 9,
  1. In AbilityData.cs we also need to change some conditions, and add AreTagetConditionsMet to be able to use conditions in AllPlayers and AllCardsBoardAllPlayers options
// 286 Line (GetCardTargets)
if (target == AbilityTarget.AllCardsBoard || target == AbilityTarget.SelectTarget || target == AbilityTarget.AllCardsBoardAllPlayers)

// 395 Line (GetPlayerTargets)
else if (target == AbilityTarget.AllPlayers || target == AbilityTarget.AllCardsBoardAllPlayers)
{
    foreach (Player player in data.players)
    {
        if (AreTargetConditionsMet(data, caster, player))
            targets.Add(player);
    }

}
  1. In Game.cs we need to add the new variable to store last_selected_target
public string last_selected_target;

// 492 (Clone)
dest.last_selected_target = source.last_selected_target;
  1. In GameLogic.cs we will add the ability to store the last_selected_target and update some conditions to include the new Enum
// 313 Line (ClearTurnData)
game_data.last_selected_target = null;

// 1015 Line (ResolveCardAbilityPlayTarget)
if (iability.CanTarget(game_data, caster, tplayer)) {
    game_data.last_selected_target = tplayer.player_id.ToString();
    ResolveEffectTarget(iability, caster, tplayer);
}

// 1021 Line (ResolveCardAbilityPlayTarget)
if (iability.CanTarget(game_data, caster, slot_card)) {
    game_data.last_selected_target = slot_card.uid;
    ResolveEffectTarget(iability, caster, slot_card);
}

// 1257 Line (UpdateOngoingAbilities)
if (ability.target == AbilityTarget.AllPlayers || ability.target == AbilityTarget.PlayerOpponent || ability.target == AbilityTarget.AllCardsBoardAllPlayers)
 
// 1261 Line 
if (ability.target == AbilityTarget.AllPlayers || ability.target == AbilityTarget.AllCardsBoardAllPlayers || tp != player.player_id)

// 1272 Line
if (ability.target == AbilityTarget.AllCardsAllPiles || ability.target == AbilityTarget.AllCardsHand || ability.target == AbilityTarget.AllCardsBoard || ability.target == AbilityTarget.AllCardsBoardAllPlayers)

// 1293 Line
if (ability.target == AbilityTarget.AllCardsAllPiles || ability.target == AbilityTarget.AllCardsBoard || ability.target == AbilityTarget.AllCardsBoardAllPlayers)

Wild Growth (Classic)

Description

(2) "Gain an empty Mana Crystal." (This card has a hidden ability that gives you a "(0) Draw a a card" if already have mana crystals equals the game limit)

Steps

  1. Create a new Effect SO with the following code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Gameplay;

namespace TcgEngine
{
    [CreateAssetMenu(fileName = "effect", menuName = "TcgEngine/Effect/ManaCrystal", order = 10)]
    public class EffectManaCrystal : EffectData
    {
        public bool empty = true;
        
        public override void DoEffect(GameLogic logic, AbilityData ability, Card caster, Player target)
        {
            if (!empty)
            {
                target.mana = Mathf.Clamp(target.mana_max + ability.value, 0, GameplayData.Get().mana_max);
            }

            target.mana_max = Mathf.Clamp(target.mana_max + ability.value, 0, GameplayData.Get().mana_max);
        }

    }
}
  1. Create a new Effect based on the "EffectManaCrystal" SO with Empty bool has true
    image

  2. Create a new Ability with the "Empty_Mana_Crystal" Effect
    image

  3. Create a new Effect SO "EffectHasMana"

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Gameplay;

namespace TcgEngine
{
    [CreateAssetMenu(fileName = "effect", menuName = "TcgEngine/Effect/HasMana", order = 10)]
    public class EffectHasMana : EffectData
    {
        public CardData generate;
        public bool maxMana = false;
        public int manaRequired;

        public override void DoEffect(GameLogic logic, AbilityData ability, Card caster, Player target)
        {
            if (maxMana && target.mana_max != GameplayData.Get().mana_max) return;

            if (manaRequired > target.mana_max) return;

            logic.SummonCardHand(target, generate, caster.VariantData); //Summon in hand instead of board when target a player
        }
    }
}
  1. Create a new Effect based on the SO "EffectHasMana"
    image

  2. Create a new Ability that uses the "has_mana_wild_growth"
    image

  3. Create a new Card with the 2 abilities
    image

Wrath (Classic)

Description

(2) "Choose One - Deal 3 damage to a minion; or 1 damage and draw a card."

Steps

  1. Create a new Ability with Effect "Damage 3", Target "Select Target" and Conditions ["is_not_empty", "is_character"]
    image

  2. Create a new Ability with Effect "Damage 1" and Chain Ability "spell_draw1", Target "Select Target" and Conditions ["is_not_empty", "is_character"]
    image

  3. Create a new Ability with "Choice Selector" that contains the 2 abilities as choices
    image

  4. Create a new Card that contains the "spell_wrath" Ability
    image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment