Skip to content

Latest commit

 

History

History
315 lines (243 loc) · 11.7 KB

HowToAddCard.md

File metadata and controls

315 lines (243 loc) · 11.7 KB

How to add card

Step 1: Select card

The first thing you have to do is to select the cards to add.

See CardList.md for all card list.

Select a card that is not yet implemented in card list.

Let's assume that you have selected the CS2_124 ("Wolfrider") card.

Step 2: Add card to CardSets

The next thing to do is to add selected card to the card set. There are C++ source files for each card set in Sources/Rosetta/PlayMode/CardSets. You need to select right source file by card set. See CardList.md and watch Set label. Card CS2_124 that you will add is a CORE set card. So this card should be added to CoreCardsGen.cpp

The main code for CoreCardsGen.cpp is as follows.

void CoreCardsGen::AddAll(std::map<std::string, Power*>& cards)
{
    AddHeroes(cards);
    AddHeroPowers(cards);

    AddDruid(cards);
    AddDruidNonCollect(cards);

    AddHunter(cards);
    AddHunterNonCollect(cards);

    AddMage(cards);
    AddMageNonCollect(cards);

    AddPaladin(cards);
    AddPaladinNonCollect(cards);

    AddPriest(cards);
    AddPriestNonCollect(cards);

    AddRogue(cards);
    AddRogueNonCollect(cards);

    AddShaman(cards);
    AddShamanNonCollect(cards);

    AddWarlock(cards);
    AddWarlockNonCollect(cards);

    AddWarrior(cards);
    AddWarriorNonCollect(cards);

    AddNeutral(cards);
    AddNeutralNonCollect(cards);
}

Now you need to add a card to appropriate function depending on the card class. Do you not know what class of card is? Visit Hearthstone Wiki and search by card name. Search results for Wolfrider are:

Wolfrider is a neutral minion card, from the Basic set.

So you can add a card to the AddNeutral function.

NOTE: All of the cards in card list can be collected. Therefore, it doesn't add to AddXXXNonCollect function.

Wolfrider is a minion with the Charge ability. Charge is an ability allowing a minion to attack the same turn it is summoned or brought under a new player's control. The default abilities such as Charge, Windfury are saved as GameTag when parsing card data, so you don't need to add power separately. Therefore, you can add a card like this:

void CoreCardsGen::AddNeutral(std::map<std::string, CardDef>& cards)
{
    CardDef cardDef;

    // --------------------------------------- MINION - NEUTRAL
    // [CS2_124] Wolfrider - COST:3 [ATK:3/HP:1]
    // - Faction: Horde, Set: Core, Rarity: Free
    // --------------------------------------------------------
    // Text: <b>Charge</b>
    // --------------------------------------------------------
    // GameTag:
    // - CHARGE = 1
    // --------------------------------------------------------
    cardDef.ClearData();
    cardDef.power.AddPowerTask(nullptr);
    cards.emplace("CS2_124", CardDef(power));
}

Note that the first parameter is card ID and the second parameter is card power.

That's it! Now, it's time to test the card to see if it works.

NOTE: There are many abilities that are not yet implemented. A list of currently implemented abilities can be found in AbilityList.md. If selected card is required an ability to be implemented, please ask collaborators(utilForever, CreatorSeraph, git-rla FYLSunghwan, ShyRoute).

Cards with power

Some cards such as CS2_041 ("Ancestral Healing") require power.

Ancestral Healing is a shaman spell card, from the Basic set. It restores a minion to their current maximum Health value and grants it the Taunt ability.

This card has two powers: One is the power to restore current maximum health, and the other is the power to grant the Taunt ability. Also, this card is a shaman spell card. So you can add a card to the AddShaman function.

void CoreCardsGen::AddShaman(std::map<std::string, CardDef>& cards)
{
    CardDef cardDef;

    // ----------------------------------------- SPELL - SHAMAN
    // [CS2_041] Ancestral Healing - COST:0
    // - Faction: Neutral, Set: Core, Rarity: Free
    // - Spell School: Nature
    // --------------------------------------------------------
    // Text: Restore a minion
    //       to full Health and
    //       give it <b>Taunt</b>.
    // --------------------------------------------------------
    // PlayReq:
    // - REQ_TARGET_TO_PLAY = 0
    // - REQ_MINION_TARGET = 0
    // --------------------------------------------------------
    // Tag:
    // - TAUNT = 1
    // --------------------------------------------------------
    cardDef.ClearData();
    cardDef.power.AddPowerTask(std::make_shared<HealFullTask>(EntityType::TARGET));
    cardDef.power.AddPowerTask(
        std::make_shared<AddEnchantmentTask>("CS2_041e", EntityType::TARGET));
    cards.emplace(
        "CS2_041",
        CardDef(power, PlayReqs{ { PlayReq::REQ_TARGET_TO_PLAY, 0 },
                                 { PlayReq::REQ_MINION_TARGET, 0 } }));
}

Meanwhile, The power to grant the Taunt ability is the enchantment of Ancestral Healing. This enchantment is called CS2_041e ("Ancestral Infusion"). Since this card can't be collected, add it to ShamanNonCollect function.

void CoreCardsGen::AddShamanNonCollect(std::map<std::string, CardDef>& cards)
{
    CardDef cardDef;

    // ----------------------------------- ENCHANTMENT - SHAMAN
    // [CS2_041e] Ancestral Infusion (*) - COST:0
    // - Set: Core
    // --------------------------------------------------------
    // Text: Taunt.
    // --------------------------------------------------------
    // GameTag:
    // - TAUNT = 1
    // --------------------------------------------------------
    cardDef.ClearData();
    cardDef.power.AddEnchant(std::make_unique<Enchant>(Effects::Taunt));
    cards.emplace("CS2_041e", CardDef(power));
}

NOTE: There are many tasks that are not yet implemented. A list of currently implemented tasks can be found in TaskList.md. If selected card is required a task to be implemented, please ask collaborators(utilForever, CreatorSeraph, git-rla FYLSunghwan, ShyRoute).

Step 3: Add test code

The last thing you need to do is test card you've added. There are test files for each card set in Tests/UnitTests/PlayMode/CardSets. Card CS2_124 that you will add is a basic card set. So, you have to see file CoreCardsGenTests.cpp.

Test file has the following structure:

...

// --------------------------------------- MINION - NEUTRAL
// [CS2_124] Wolfrider - COST:3 [ATK:3/HP:1]
// - Faction: Horde, Set: Core, Rarity: Free
// --------------------------------------------------------
// Text: <b>Charge</b>
// --------------------------------------------------------
// GameTag:
// - CHARGE = 1
// --------------------------------------------------------
TEST_CASE("[Neutral : Minion] - CS2_124 : Wolfrider")
{
    ...
}

...

To test card, you need to consider play scenario. Also, you need to consider the abilities and powers that the cards have. For example, Wolfrider has Charge ability.

Charge is an ability allowing a minion to attack the same turn it is summoned or brought under a new player's control.

Therefore, you need to create a scenario to ensure that this ability works properly. An example of a scenario is:

1. Current player draws "Acidic Swamp Ooze" card.
2. Current player plays "Acidic Swap Ooze" card.
3. Current player ends turn.
4. Opponent player draws "Wolfrider" card.
5. Opponent player plays "Wolfrider" card.
6. Opponent player attacks "Acidic Swamp Ooze" with "Wolfrider".

It is important to check whether the processing is normal after each operation. For example, after current player draws "Acidic Swamp Ooze" card, you make sure that there is a card in hand and that card is "Acidic Swamp Ooze". In addition, after opponent player attacks "Acidic Swamp Ooze" with "Wolfrider", you make sure that there are no cards in the field. Let's write test code based on this scenario.

First, you should configure the scenario environment. Game is a class that stores player manages game state.

GameConfig config;
config.player1Class = CardClass::WARRIOR;
config.player2Class = CardClass::ROGUE;
config.startPlayer = PlayerType::PLAYER1;
config.doFillDecks = true;
config.autoRun = false;

Game game(config);
game.Start();
game.ProcessUntil(Step::MAIN_START);

Player& curPlayer = game.GetCurrentPlayer();
Player& opPlayer = game.GetOpponentPlayer();
curPlayer.SetTotalMana(10);
curPlayer.SetUsedMana(0);
opPlayer.SetTotalMana(10);
opPlayer.SetUsedMana(0);

Then, you should perform operations according to the scenario and test it.

// --------------------------------------- MINION - NEUTRAL
// [CS2_124] Wolfrider - COST:3 [ATK:3/HP:1]
// - Faction: Horde, Set: Core, Rarity: Free
// --------------------------------------------------------
// Text: <b>Charge</b>
// --------------------------------------------------------
// GameTag:
// - CHARGE = 1
// --------------------------------------------------------
TEST_CASE("[Neutral : Minion] - CS2_124 : Wolfrider")
{
    GameConfig config;
    config.player1Class = CardClass::WARRIOR;
    config.player2Class = CardClass::ROGUE;
    config.startPlayer = PlayerType::PLAYER1;
    config.doFillDecks = true;
    config.autoRun = false;

    Game game(config);
    game.Start();
    game.ProcessUntil(Step::MAIN_START);

    Player& curPlayer = game.GetCurrentPlayer();
    Player& opPlayer = game.GetOpponentPlayer();
    curPlayer.SetTotalMana(10);
    curPlayer.SetUsedMana(0);
    opPlayer.SetTotalMana(10);
    opPlayer.SetUsedMana(0);

    auto& curField = *(curPlayer->GetFieldZone());
    auto& opField = *(opPlayer->GetFieldZone());

    // 1. Current player draws "Acidic Swamp Ooze" card.
    const auto card1 = Generic::DrawCard(
        curPlayer, Cards::FindCardByName("Acidic Swamp Ooze"));

    // 2. Current player plays "Acidic Swap Ooze" card.
    game.Process(curPlayer, PlayCardTask::Minion(card1));

    // 3. Current player ends turn.
    game.Process(curPlayer, EndTurnTask());
    game.ProcessUntil(Step::MAIN_START);

    // 4. Opponent player draws "Wolfrider" card.
    const auto card2 = Generic::DrawCard(
        opPlayer, Cards::FindCardByName("Wolfrider"));

    // 5. Opponent player plays "Wolfrider" card.
    game.Process(opPlayer, PlayCardTask::Minion(card2));

    // 6. Opponent player attacks "Acidic Swamp Ooze" with "Wolfrider".
    game.Process(opPlayer, AttackTask(card2, card1));
    EXPECT_EQ(curField.GetCount(), 0);
    EXPECT_EQ(opField.GetCount(), 0);
}

When you have finished writing test code, compile and build it. And you have to make sure the test passes.

[doctest] doctest version is "2.3.6"
[doctest] run with "--help" for options
===============================================================================
[doctest] test cases:    583 |    583 passed |      0 failed |      0 skipped
[doctest] assertions:   6336 |   6336 passed |      0 failed |
[doctest] Status: SUCCESS!

Good! Now, let's do a pull request.