From 575ea89c2b23c0d7a063c8006adfc1c69f5cb42d Mon Sep 17 00:00:00 2001 From: shinoi2 Date: Tue, 9 Jan 2024 14:15:16 +0800 Subject: [PATCH 1/2] fix bug --- fireplace/actions.py | 80 +++++++++++++----------- fireplace/card.py | 1 + fireplace/cards/blackrock/collectible.py | 2 +- fireplace/cards/boomsday/neutral_rare.py | 2 +- fireplace/cards/icecrown/rogue.py | 3 +- fireplace/cards/kobolds/rogue.py | 3 +- fireplace/cards/troll/hunter.py | 2 +- fireplace/cards/troll/neutral_epic.py | 2 +- fireplace/cards/troll/rogue.py | 2 +- fireplace/cards/witchwood/priest.py | 8 ++- fireplace/cards/witchwood/shaman.py | 2 +- fireplace/dsl/copy.py | 9 ++- fireplace/dsl/evaluator.py | 3 +- fireplace/dsl/selector.py | 1 - fireplace/player.py | 5 +- tests/test_misc.py | 42 +++++++++++++ tests/test_troll.py | 29 +++++++++ 17 files changed, 144 insertions(+), 52 deletions(-) diff --git a/fireplace/actions.py b/fireplace/actions.py index 056df3077..f62cd7169 100644 --- a/fireplace/actions.py +++ b/fireplace/actions.py @@ -832,11 +832,10 @@ class Damage(TargetedAction): TARGET = ActionArg() AMOUNT = IntArg() - def get_target_args(self, source, target): - return [target.predamage] - - def do(self, source, target, amount): - amount = target._hit(target.predamage) + def do(self, source, target, amount=None): + if not amount: + amount = target.predamage + amount = target._hit(amount) target.predamage = 0 if (source.type == CardType.MINION or source.type == CardType.HERO) and source.stealthed: # TODO this should be an event listener of sorts @@ -1612,9 +1611,6 @@ def get_target_args(self, source, target): spell_target = [None] if ret: spell_target = ret[0] - else: - if target.target: - return [target.target] return [spell_target] def do(self, source, card, targets): @@ -1831,18 +1827,23 @@ def do_step3(self): def done(self): card = self.choosed_cards[0] - new_card = self.player.card(self.potions_card[card.id]) - self.potions = self.potions_choice_map[card.id][:] card1 = self.choosed_cards[1] card2 = self.choosed_cards[2] + self.potions = self.potions_choice_map[card.id][:] if self.potions.index(card1.id) > self.potions.index(card2.id): card1, card2 = card2, card1 - new_card.requirements.update(card1.requirements) - new_card.requirements.update(card2.requirements) - new_card.data.scripts.play = card1.data.scripts.play + card2.data.scripts.play - new_card.requirements = card1.requirements | card2.requirements - new_card.tags[GameTag.CARDTEXT_ENTITY_0] = card1.data.strings[GameTag.CARDTEXT] - new_card.tags[GameTag.CARDTEXT_ENTITY_1] = card2.data.strings[GameTag.CARDTEXT] + + new_card = self.player.card(self.potions_card[card.id]) + new_card.custom_card = True + + def create_custom_card(new_card): + new_card.data.scripts.play = card1.data.scripts.play + card2.data.scripts.play + new_card.requirements = card1.requirements | card2.requirements + new_card.tags[GameTag.CARDTEXT_ENTITY_0] = card1.data.strings[GameTag.CARDTEXT] + new_card.tags[GameTag.CARDTEXT_ENTITY_1] = card2.data.strings[GameTag.CARDTEXT] + + new_card.create_custom_card = create_custom_card + new_card.create_custom_card(new_card) self.player.give(new_card) def choose(self, card): @@ -2047,28 +2048,35 @@ def do_step2(self): self.cards = [self.player.card(id) for id in random.sample(self.second_ids, 3)] def done(self): - zombeast = self.player.card("ICC_828t") card1 = self.choosed_cards[0] card2 = self.choosed_cards[1] - zombeast.tags[GameTag.CARDTEXT_ENTITY_0] = card2.data.strings[GameTag.CARDTEXT] - zombeast.tags[GameTag.CARDTEXT_ENTITY_1] = card1.data.strings[GameTag.CARDTEXT] - zombeast.data.scripts = card1.data.scripts - int_mergeable_attributes = ( - "atk", "cost", "max_health", "incoming_damage_multiplier", "spellpower", - "windfury", - ) - bool_mergeable_attributes = ( - "has_deathrattle", "charge", "has_inspire", "stealthed", "cant_attack", - "cant_be_targeted_by_opponents", "cant_be_targeted_by_abilities", - "cant_be_targeted_by_hero_powers", "heavily_armored", "min_health", - "rush", "taunt", "poisonous", "ignore_taunt", "cannot_attack_heroes", - "unlimited_attacks", "cant_be_damaged", "lifesteal", - "cant_be_targeted_by_op_abilities", "cant_be_targeted_by_op_hero_powers", - ) - for attribute in int_mergeable_attributes: - setattr(zombeast, attribute, getattr(card1, attribute) + getattr(card2, attribute)) - for attribute in bool_mergeable_attributes: - setattr(zombeast, attribute, getattr(card1, attribute) or getattr(card2, attribute)) + + zombeast = self.player.card("ICC_828t") + zombeast.custom_card = True + + def create_custom_card(zombeast): + zombeast.tags[GameTag.CARDTEXT_ENTITY_0] = card2.data.strings[GameTag.CARDTEXT] + zombeast.tags[GameTag.CARDTEXT_ENTITY_1] = card1.data.strings[GameTag.CARDTEXT] + zombeast.data.scripts = card1.data.scripts + int_mergeable_attributes = ( + "atk", "cost", "max_health", "incoming_damage_multiplier", "spellpower", + "windfury", + ) + bool_mergeable_attributes = ( + "has_deathrattle", "charge", "has_inspire", "stealthed", "cant_attack", + "cant_be_targeted_by_opponents", "cant_be_targeted_by_abilities", + "cant_be_targeted_by_hero_powers", "heavily_armored", "min_health", + "rush", "taunt", "poisonous", "ignore_taunt", "cannot_attack_heroes", + "unlimited_attacks", "cant_be_damaged", "lifesteal", + "cant_be_targeted_by_op_abilities", "cant_be_targeted_by_op_hero_powers", + ) + for attribute in int_mergeable_attributes: + setattr(zombeast, attribute, getattr(card1, attribute) + getattr(card2, attribute)) + for attribute in bool_mergeable_attributes: + setattr(zombeast, attribute, getattr(card1, attribute) or getattr(card2, attribute)) + + zombeast.create_custom_card = create_custom_card + zombeast.create_custom_card(zombeast) self.player.give(zombeast) def choose(self, card): diff --git a/fireplace/card.py b/fireplace/card.py index 6c74774ec..676ccb4dd 100644 --- a/fireplace/card.py +++ b/fireplace/card.py @@ -258,6 +258,7 @@ def __init__(self, data): self.upgrade_counter = 0 self.cast_on_friendly_minions = False self.play_right_most = False + self.custom_card = False super().__init__(data) @property diff --git a/fireplace/cards/blackrock/collectible.py b/fireplace/cards/blackrock/collectible.py index c443463d4..e25bffbb2 100644 --- a/fireplace/cards/blackrock/collectible.py +++ b/fireplace/cards/blackrock/collectible.py @@ -153,7 +153,7 @@ class BRM_029: class BRM_030: """Nefarian""" - play = Find(ENEMY_HERO + CLASS_CARD) & ( + play = Find(ENEMY_HERO - NEUTRAL) & ( Give(CONTROLLER, RandomSpell(card_class=ENEMY_CLASS)) * 2 ) | ( Give(CONTROLLER, "BRM_030t") * 2 diff --git a/fireplace/cards/boomsday/neutral_rare.py b/fireplace/cards/boomsday/neutral_rare.py index c408ed29d..c3ba63b68 100644 --- a/fireplace/cards/boomsday/neutral_rare.py +++ b/fireplace/cards/boomsday/neutral_rare.py @@ -13,7 +13,7 @@ class BOT_066: class BOT_098: """Unpowered Mauler""" # Can only attack if you cast a spell this turn. - update = Find(CARDS_PLAYED_THIS_GAME + SPELL) | Refresh(SELF, {GameTag.CANT_ATTACK: True}) + update = Find(CARDS_PLAYED_THIS_TRUN + SPELL) | Refresh(SELF, {GameTag.CANT_ATTACK: True}) class BOT_102: diff --git a/fireplace/cards/icecrown/rogue.py b/fireplace/cards/icecrown/rogue.py index 7ac7d70b3..f7f6c1586 100644 --- a/fireplace/cards/icecrown/rogue.py +++ b/fireplace/cards/icecrown/rogue.py @@ -21,7 +21,8 @@ class ICC_809: class ICC_811: """Lilian Voss""" - play = Morph(FRIENDLY_HAND + SPELL, RandomSpell(card_class=ENEMY_CLASS)) + play = Find(ENEMY_HERO - NEUTRAL) & ( + Morph(FRIENDLY_HAND + SPELL, RandomSpell(card_class=ENEMY_CLASS))) class ICC_910: diff --git a/fireplace/cards/kobolds/rogue.py b/fireplace/cards/kobolds/rogue.py index 0a7c07f93..e05b17994 100644 --- a/fireplace/cards/kobolds/rogue.py +++ b/fireplace/cards/kobolds/rogue.py @@ -46,7 +46,8 @@ class LOOT_211: class LOOT_412: """Kobold Illusionist""" # Deathrattle: Summon a 1/1 copy of a minion from your hand. - deathrattle = Summon(CONTROLLER, Buff(RANDOM(FRIENDLY_HAND + MINION), "LOOT_412e")) + deathrattle = Summon(CONTROLLER, RANDOM(FRIENDLY_HAND + MINION)).then( + Buff(Summon.CARD, "LOOT_412e")) class LOOT_412e: diff --git a/fireplace/cards/troll/hunter.py b/fireplace/cards/troll/hunter.py index 17c00cbc6..c9019b9d2 100644 --- a/fireplace/cards/troll/hunter.py +++ b/fireplace/cards/troll/hunter.py @@ -91,7 +91,7 @@ class TRL_065: """Zul'jin""" # [x]Battlecry: Cast all spells you've played this game (targets chosen # randomly). - play = CastSpell(CARDS_PLAYED_THIS_GAME + SPELL) + play = CastSpell(Copy(CARDS_PLAYED_THIS_GAME + SPELL)) class TRL_065h: diff --git a/fireplace/cards/troll/neutral_epic.py b/fireplace/cards/troll/neutral_epic.py index 9fa3df283..072693a62 100644 --- a/fireplace/cards/troll/neutral_epic.py +++ b/fireplace/cards/troll/neutral_epic.py @@ -66,7 +66,7 @@ class TRL_535: """Snapjaw Shellfighter""" # [x]Whenever an adjacent minion takes damage, this _minion takes it instead. events = Predamage(SELF_ADJACENT).on( - Predamage(Predamage.TARGET, 0), Hit(SELF, Predamage.AMOUNT) + Predamage(Predamage.TARGET, 0), Damage(SELF, Predamage.AMOUNT) ) diff --git a/fireplace/cards/troll/rogue.py b/fireplace/cards/troll/rogue.py index 78c54aae8..000cd1fc1 100644 --- a/fireplace/cards/troll/rogue.py +++ b/fireplace/cards/troll/rogue.py @@ -53,7 +53,7 @@ class TRL_409: """Gral, the Shark""" # [x]Battlecry: Eat a minion in your deck and gain its stats. # Deathrattle: Add it to your hand. - play = ( + play = Find(FRIENDLY_DECK + MINION) & ( Retarget(SELF, RANDOM(FRIENDLY_DECK + MINION)), Reveal(TARGET), Destroy(TARGET), diff --git a/fireplace/cards/witchwood/priest.py b/fireplace/cards/witchwood/priest.py index c0037e614..a1b22c73b 100644 --- a/fireplace/cards/witchwood/priest.py +++ b/fireplace/cards/witchwood/priest.py @@ -9,14 +9,18 @@ class GIL_142: # Each turn this is in your hand, transform it into a card your opponent is holding. class Hand: events = OWN_TURN_BEGIN.on( - Morph(SELF, ExactCopy(RANDOM(ENEMY_HAND))).then(Buff(Morph.CARD, "GIL_142e")) + Find(ENEMY_HAND) & ( + Morph(SELF, ExactCopy(RANDOM(ENEMY_HAND))).then(Buff(Morph.CARD, "GIL_142e")) + ) ) class GIL_142e: class Hand: events = OWN_TURN_BEGIN.on( - Morph(OWNER, ExactCopy(RANDOM(ENEMY_HAND))).then(Buff(Morph.CARD, "GIL_142e")) + Find(ENEMY_HAND) & ( + Morph(OWNER, ExactCopy(RANDOM(ENEMY_HAND))).then(Buff(Morph.CARD, "GIL_142e")) + ) ) events = REMOVED_IN_PLAY diff --git a/fireplace/cards/witchwood/shaman.py b/fireplace/cards/witchwood/shaman.py index f80398b1c..96abb53db 100644 --- a/fireplace/cards/witchwood/shaman.py +++ b/fireplace/cards/witchwood/shaman.py @@ -36,7 +36,7 @@ class GIL_820: """Shudderwock""" # [x]Battlecry: Repeat all other Battlecries from cards you played this # game (targets chosen randomly). - play = Battlecry(RANDOM(CARDS_PLAYED_THIS_GAME + BATTLECRITES - SELF) * 30, None) + play = Battlecry(RANDOM(CARDS_PLAYED_THIS_GAME + BATTLECRY - ID("GIL_820")) * 30, None) ## diff --git a/fireplace/dsl/copy.py b/fireplace/dsl/copy.py index 9144b73fb..e87e5c3ce 100644 --- a/fireplace/dsl/copy.py +++ b/fireplace/dsl/copy.py @@ -19,7 +19,12 @@ def copy(self, source, entity): Return a copy of \a entity """ log.info("Creating a copy of %r", entity) - return source.controller.card(entity.id, source) + new_entity = source.controller.card(entity.id, source) + if entity.custom_card: + new_entity.custom_card = True + new_entity.create_custom_card = entity.create_custom_card + new_entity.create_custom_card(new_entity) + return new_entity def evaluate(self, source) -> list[str]: if isinstance(self.selector, LazyValue): @@ -53,7 +58,7 @@ def copy(self, source, entity): for buff in entity.buffs: # Recreate the buff stack new_buff = source.controller.card(buff.id) - new_buff.source = source + new_buff.source = buff.source attributes = ["atk", "max_health", "_xatk", "_xhealth", "_xcost", "store_card"] for attribute in attributes: if hasattr(buff, attribute): diff --git a/fireplace/dsl/evaluator.py b/fireplace/dsl/evaluator.py index e7eb331fa..ab61dc989 100644 --- a/fireplace/dsl/evaluator.py +++ b/fireplace/dsl/evaluator.py @@ -127,7 +127,8 @@ def check(self, source): if isinstance(self.selector, Selector): entities = self.selector.eval(source.game, source) else: - entities = [self.selector.evaluate(source)] + entity = self.selector.evaluate(source) + entities = [entity] if entity else [] for target in entities: if target.dead: return True diff --git a/fireplace/dsl/selector.py b/fireplace/dsl/selector.py index cfeb23670..e6d246976 100644 --- a/fireplace/dsl/selector.py +++ b/fireplace/dsl/selector.py @@ -445,7 +445,6 @@ def CONTROLLED_BY(selector): BATTLECRY = EnumSelector(GameTag.BATTLECRY) CHARGE = EnumSelector(GameTag.CHARGE) COMBO = EnumSelector(GameTag.COMBO) -BATTLECRITES = EnumSelector(GameTag.BATTLECRY) DAMAGED = EnumSelector(GameTag.DAMAGE) DEATHRATTLE = EnumSelector(GameTag.DEATHRATTLE) DIVINE_SHIELD = EnumSelector(GameTag.DIVINE_SHIELD) diff --git a/fireplace/player.py b/fireplace/player.py index 313146b6d..6a85aad99 100644 --- a/fireplace/player.py +++ b/fireplace/player.py @@ -303,8 +303,9 @@ def draw(self, count=1): return ret def give(self, id): - cards = self.game.cheat_action(self, [Give(self, id)])[0] - return cards[0][0] + cards = self.game.cheat_action(self, [Give(self, id)])[0][0] + if len(cards) > 0: + return cards[0] def concede(self): ret = self.game.cheat_action(self, [Concede(self)]) diff --git a/tests/test_misc.py b/tests/test_misc.py index d1282e219..77c7d0908 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -85,3 +85,45 @@ def test_lifesteal_and_auchenai(): game.player1.give("TRL_512").play(target=game.player2.hero) assert game.player1.hero.health == 29 assert game.player2.hero.health == 29 + + +def test_mirror_entity_and_pumpkin_peasant(): + game = prepare_empty_game() + game.player1.give("GIL_201") + game.end_turn() + game.player2.give("EX1_294").play() + game.end_turn() + for _ in range(3): + game.skip_turn() + game.player1.hand[0].play() + minion1 = game.player1.field[0] + minion2 = game.player2.field[0] + assert minion1.id == minion2.id + assert minion1.atk == minion2.atk + assert minion1.health == minion2.health + + +def test_nefarian_and_ragnaros_hero(): + game = prepare_empty_game() + game.end_turn() + game.player2.give("BRM_027").play().destroy() + assert game.player2.hero.card_class == CardClass.NEUTRAL + game.end_turn() + game.player1.give("BRM_030").play() + assert len(game.player1.hand) == 2 + assert game.player1.hand[0].id == "BRM_030t" + assert game.player1.hand[0].id == "BRM_030t" + + +def test_lilian_voss_andragnaros_hero(): + game = prepare_empty_game() + game.end_turn() + game.player2.give("BRM_027").play().destroy() + assert game.player2.hero.card_class == CardClass.NEUTRAL + game.end_turn() + game.player1.give(MOONFIRE) + game.player1.give(MOONFIRE) + game.player1.give("ICC_811").play() + assert len(game.player1.hand) == 2 + assert game.player1.hand[0].id == MOONFIRE + assert game.player1.hand[0].id == MOONFIRE diff --git a/tests/test_troll.py b/tests/test_troll.py index 9b5a9ddf8..a3c677536 100644 --- a/tests/test_troll.py +++ b/tests/test_troll.py @@ -36,6 +36,17 @@ def test_hakkar(): assert len(game.player2.deck) == 4 +def test_hakkar_full(): + game = prepare_empty_game() + game.player1.give("KAR_712").play() + for _ in range(60): + blood = game.player1.give("TRL_541t") + blood.shuffle_into_deck() + assert len(game.player1.deck) == 60 + game.skip_turn() + assert len(game.player1.deck) == 60 + + def test_overkill(): game = prepare_game() wisp = game.player1.give(WISP).play() @@ -63,6 +74,15 @@ def test_snapjaw_shellfighter(): assert shellfighter.damage == 1 +def test_two_snapjaw_shellfighters(): + game = prepare_game() + shellfighter1 = game.player1.give("TRL_535").play() + shellfighter2 = game.player1.give("TRL_535").play() + game.player1.give(MOONFIRE).play(target=shellfighter1) + assert shellfighter1.damage == 0 + assert shellfighter2.damage == 1 + + def test_treespeaker(): game = prepare_game() game.player1.give("EX1_571").play() @@ -175,3 +195,12 @@ def test_daring_fire_eater(): game.player1.hero.power.use(target=game.player2.field[1]) for i in range(3): assert game.player2.field[i].damage == 4 + + +def test_zuljin(): + game = prepare_game() + game.player1.give(THE_COIN).play() + game.player1.give(MOONFIRE).play(target=game.player2.hero) + assert game.player1.temp_mana == 0 + game.player1.give("TRL_065").play() + assert game.player1.temp_mana == 1 From c8935bda80a96d499f5bb3d85ec03e128c4f0f97 Mon Sep 17 00:00:00 2001 From: shinoi2 Date: Tue, 9 Jan 2024 14:36:09 +0800 Subject: [PATCH 2/2] Spirit of the Shark only work on minion --- fireplace/actions.py | 9 ++++++--- fireplace/cards/troll/rogue.py | 4 ++-- fireplace/enums.py | 3 ++- fireplace/managers.py | 3 ++- fireplace/player.py | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/fireplace/actions.py b/fireplace/actions.py index f62cd7169..82a817c0b 100644 --- a/fireplace/actions.py +++ b/fireplace/actions.py @@ -937,12 +937,15 @@ def do(self, source, card, target=None): source.game.main_power(source, actions, target) if ( - player.extra_combos and card.type == CardType.MINION and + player.minion_extra_combos and card.type == CardType.MINION and card.has_combo and player.combo + ) or ( + player.extra_battlecries and card.has_battlecry + ) or ( + player.minion_extra_battlecries and card.type == CardType.MINION and + card.has_battlecry ): source.game.main_power(source, actions, target) - elif player.extra_battlecries and card.has_battlecry: - source.game.main_power(source, actions, target) if card.overload: source.game.queue_actions(card, [Overload(player, card.overload)]) diff --git a/fireplace/cards/troll/rogue.py b/fireplace/cards/troll/rogue.py index 000cd1fc1..d4242ffc4 100644 --- a/fireplace/cards/troll/rogue.py +++ b/fireplace/cards/troll/rogue.py @@ -36,8 +36,8 @@ class TRL_092: OWN_TURN_BEGIN.on(Unstealth(SELF)), ) update = ( - Refresh(CONTROLLER, {enums.EXTRA_BATTLECRIES: True}), - Refresh(CONTROLLER, {enums.EXTRA_COMBOS: True}), + Refresh(CONTROLLER, {enums.MINION_EXTRA_BATTLECRIES: True}), + Refresh(CONTROLLER, {enums.MINION_EXTRA_COMBOS: True}), ) diff --git a/fireplace/enums.py b/fireplace/enums.py index 6fe060c60..a625736ab 100644 --- a/fireplace/enums.py +++ b/fireplace/enums.py @@ -17,4 +17,5 @@ CANT_BE_TARGETED_BY_OP_HERO_POWERS = -23 KEEP_BUFF = -24, DAMAGED_THIS_TURN = -25 -EXTRA_COMBOS = -26 +MINION_EXTRA_COMBOS = -26 +MINION_EXTRA_BATTLECRIES = -27 diff --git a/fireplace/managers.py b/fireplace/managers.py index 86b9556a2..db638b604 100644 --- a/fireplace/managers.py +++ b/fireplace/managers.py @@ -250,7 +250,8 @@ class PlayerManager(Manager): enums.ALWAYS_WINS_BRAWLS: "always_wins_brawls", enums.CAST_ON_FRIENDLY_MINIONS: "cast_on_friendly_minions", enums.EXTRA_BATTLECRIES: "extra_battlecries", - enums.EXTRA_COMBOS: "extra_combos", + enums.MINION_EXTRA_BATTLECRIES: "minio_extra_battlecries", + enums.MINION_EXTRA_COMBOS: "minio_extra_combos", enums.KILLED_THIS_TURN: "killed_this_turn", enums.DISCARDED: "discarded", enums.MURLOCS_COST_HEALTH: "murlocs_cost_health", diff --git a/fireplace/player.py b/fireplace/player.py index 6a85aad99..970e60284 100644 --- a/fireplace/player.py +++ b/fireplace/player.py @@ -18,7 +18,8 @@ class Player(Entity, TargetableByAuras): cant_overload = slot_property("cant_overload") choose_both = slot_property("choose_both") extra_battlecries = slot_property("extra_battlecries") - extra_combos = slot_property("extra_combos") + minion_extra_battlecries = slot_property("minion_extra_battlecries") + minion_extra_combos = slot_property("minion_extra_combos") extra_deathrattles = slot_property("extra_deathrattles") extra_end_turn_effect = slot_property("extra_end_turn_effect") healing_double = slot_property("healing_double", sum)