Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions hearthstone/entities.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast

from hearthstone.utils import MAESTRA_DISGUISE_DBF_ID, get_original_card_id
from hearthstone.utils import (
MAESTRA_DISGUISE_DBF_ID, START_OF_GAME_SIDEBOARD_CARDS, get_original_card_id
)

from .enums import CardSet, CardType, GameTag, State, Step, Zone
from .typedefs import GameTagsDict
Expand Down Expand Up @@ -220,9 +222,9 @@ def initial_deck(self) -> Iterator["Card"]:
continue

# Allow CREATOR=1 because of monster hunt decks.
# Cards shuffled into the deck at the start of the game by a card with a
# sideboard (eg. Commander Beatrix) are part of the deck list the player
# submitted, so they belong here too.
# Cards shuffled into the deck at the start of the game by one of the cards in
# START_OF_GAME_SIDEBOARD_CARDS (eg. Commander Beatrix) are part of the deck
# list the player submitted, so they belong here too.
# Everything else is likely a false positive.
if entity.initial_creator > 1 and not entity.is_sideboard_deck_card:
continue
Expand Down Expand Up @@ -337,8 +339,9 @@ def can_be_in_deck(self) -> bool:

def _is_sideboard_deck_card(self, creator_id: int) -> bool:
"""
Whether this card was shuffled into the deck during setup by a card that has a
sideboard (eg. Commander Beatrix, who adds copies of her sideboard card).
Whether this card was shuffled into the deck during setup by one of the cards in
START_OF_GAME_SIDEBOARD_CARDS (eg. Commander Beatrix, who adds copies of her
sideboard card).

Those copies are created after the initial deck is dumped and carry a creator, but
their identity is picked during deckbuilding and they are part of the deck list the
Expand All @@ -352,7 +355,8 @@ def _is_sideboard_deck_card(self, creator_id: int) -> bool:
return False

creator = self.game.find_entity_by_id(creator_id)
return bool(creator and creator.tags.get(GameTag.MAX_SIDEBOARD_CARDS, 0))
creator_card_id = getattr(creator, "initial_card_id", None)
return creator_card_id in START_OF_GAME_SIDEBOARD_CARDS

@property
def is_sideboard_deck_card(self) -> bool:
Expand Down
8 changes: 8 additions & 0 deletions hearthstone/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,14 @@ def get_copied_card_id_by_format(card_id, format_type):

MAESTRA_DISGUISE_DBF_ID = 64674

# Cards whose sideboard is shuffled into the deck at the start of the game. Those copies
# are picked during deckbuilding, so they are part of the deck list the player submitted -
# unlike the sideboards of eg. E.T.C., Band Manager, which are only created once the card
# is played.
START_OF_GAME_SIDEBOARD_CARDS = [
"JAIL_397", # Commander Beatrix
]


if __name__ == "__main__":
from enum import IntEnum
Expand Down
38 changes: 37 additions & 1 deletion tests/test_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ def test_initial_deck_with_sideboard_cards_shuffled_in_at_start_of_game(
):
# Commander Beatrix shuffles 10 copies of her sideboard card into the deck
# during CREATE_GAME, before the game is set up. The card was picked during
# deckbuilding, so the copies are part of the submitted deck list.
# deckbuilding, so the copies are part of the submitted deck list. She reveals
# herself first, which is what lets us recognise her as the creator.
beatrix = Card(5, None)
beatrix.tags.update({
GameTag.ZONE: Zone.DECK,
Expand Down Expand Up @@ -203,6 +204,41 @@ def test_initial_deck_with_sideboard_cards_shuffled_in_at_start_of_game(
assert list(player.initial_deck) == [beatrix, copy]
assert player.known_starting_deck_list == ["JAIL_397", "CS2_231"]

def test_initial_deck_with_unlisted_sideboard_card_creating_during_setup(
self, game, player
):
# Only the sideboards we know join the deck at the start of the game count. Having
# a sideboard is not enough on its own: a card that creates something in the deck
# during setup for any other reason must not have it counted as a deck card, which
# is why START_OF_GAME_SIDEBOARD_CARDS is an explicit list.
etc = Card(5, None)
etc.tags.update({
GameTag.ZONE: Zone.DECK,
GameTag.CONTROLLER: player.player_id,
})
game.register_entity(etc)
etc.reveal("ETC_080", {
GameTag.CARDTYPE: CardType.MINION,
GameTag.MAX_SIDEBOARD_CARDS: 3,
})

generated = Card(6, None)
generated.tags.update({
GameTag.ZONE: Zone.DECK,
GameTag.CONTROLLER: player.player_id,
})
game.register_entity(generated)
generated.tag_change(GameTag.DISPLAYED_CREATOR, etc.id)
generated.reveal("CS2_231", {
GameTag.CARDTYPE: CardType.MINION,
GameTag.CREATOR: etc.id,
GameTag.DISPLAYED_CREATOR: etc.id,
})

assert not generated.is_original_entity
assert generated.initial_card_id is None
assert list(player.initial_deck) == [etc]

def test_initial_deck_with_sideboard_cards_created_after_setup(self, game, player):
# E.T.C., Band Manager also has a sideboard, but its cards are created once it is
# played, long after setup. Those are not deck cards.
Expand Down
Loading