From 42686c70e406298062aaaa0205ba50a40b564b74 Mon Sep 17 00:00:00 2001 From: shinoi2 Date: Thu, 14 Dec 2023 21:31:55 +0800 Subject: [PATCH] Update some card implementations * https://github.com/jleclanche/fireplace/issues/313 --- fireplace/actions.py | 41 +++------------------- fireplace/card.py | 9 ++++- fireplace/cards/classic/mage.py | 4 +-- fireplace/cards/classic/paladin.py | 4 +-- fireplace/cards/gangs/neutral_legendary.py | 23 ++++-------- fireplace/cards/gangs/priest.py | 6 +--- fireplace/cards/gvg/priest.py | 4 +-- fireplace/cards/gvg/warrior.py | 11 ++---- fireplace/cards/kobolds/mage.py | 2 +- fireplace/cards/kobolds/priest.py | 2 +- fireplace/cards/kobolds/shaman.py | 4 +-- fireplace/cards/league/collectible.py | 4 +-- fireplace/cards/ungoro/druid.py | 12 +++---- fireplace/cards/ungoro/shaman.py | 4 +-- fireplace/cards/wog/druid.py | 7 ++-- fireplace/cards/wog/mage.py | 8 ++--- fireplace/cards/wog/neutral_epic.py | 3 -- fireplace/cards/wog/paladin.py | 8 ++--- fireplace/cards/wog/priest.py | 7 ++-- fireplace/cards/wog/warlock.py | 8 ++--- fireplace/dsl/evaluator.py | 3 ++ fireplace/dsl/selector.py | 16 +++++++-- tests/test_gangs.py | 19 ++++++++++ tests/test_misc.py | 2 +- 24 files changed, 89 insertions(+), 122 deletions(-) diff --git a/fireplace/actions.py b/fireplace/actions.py index aa395b028..a008439b7 100644 --- a/fireplace/actions.py +++ b/fireplace/actions.py @@ -574,6 +574,8 @@ def trigger(self, source): times = times.evaluate(source) elif isinstance(times, Action): times = times.trigger(source)[0] + elif isinstance(times, Selector): + times = times.eval(source.game, source) for i in range(times): ret += self._trigger(i, source) @@ -1409,24 +1411,6 @@ def do(self, source, card): card.zone = old_zone -class SwapHealth(TargetedAction): - """ - Swap health between two minions using \a buff. - """ - TARGET = ActionArg() - OTHER = ActionArg() - BUFF = ActionArg() - - def do(self, source, target, other, buff): - other = other[0] - buff1 = source.controller.card(buff) - buff1.health = other.health - buff2 = source.controller.card(buff) - buff2.health = target.health - buff1.apply(target) - buff2.apply(other) - - class Steal(TargetedAction): """ Make the controller take control of targets. @@ -1598,34 +1582,17 @@ def do(self, source, target, other, buff): log.info("swap state %s and %s", target, other) other = other[0] buff1 = source.controller.card(buff) + buff1.source = source buff1._xatk = other.atk buff1._xhealth = other.health buff2 = source.controller.card(buff) + buff2.source = source buff2._xatk = target.atk buff2._xhealth = target.health buff1.apply(target) buff2.apply(other) -class SwapAtk(TargetedAction): - """ - Swap atk between two minions using \a buff. - """ - TARGET = ActionArg() - OTHER = ActionArg() - BUFF = ActionArg() - - def do(self, source, target, other, buff): - log.info("swap atk %s and %s", target, other) - other = other[0] - buff1 = source.controller.card(buff) - buff1._xatk = other.atk - buff2 = source.controller.card(buff) - buff2._xatk = target.atk - buff1.apply(target) - buff2.apply(other) - - class CopyState(TargetedAction): """ Copy target state, buff on self diff --git a/fireplace/card.py b/fireplace/card.py index dc567237d..0ade8777e 100644 --- a/fireplace/card.py +++ b/fireplace/card.py @@ -455,6 +455,12 @@ def shuffle_into_deck(self): """ return self.game.cheat_action(self, [actions.Shuffle(self.controller, self)]) + def put_on_top(self): + """ + Put the card into the controller's deck top + """ + return self.game.cheat_action(self, [actions.PutOnTop(self.controller, self)]) + def battlecry_requires_target(self): """ True if the play action of the card requires a target @@ -600,7 +606,7 @@ class Character(LiveEntity): cant_be_targeted_by_op_hero_powers = boolean_property("cant_be_targeted_by_op_hero_powers") heavily_armored = boolean_property("heavily_armored") - min_health = boolean_property("min_health") + min_health = int_property("min_health") rush = boolean_property("rush") taunt = boolean_property("taunt") poisonous = boolean_property("poisonous") @@ -999,6 +1005,7 @@ class Enchantment(BaseCard): incoming_damage_multiplier = int_property("incoming_damage_multiplier") max_health = int_property("max_health") spellpower = int_property("spellpower") + min_health = int_property("min_health") buffs = [] slots = [] diff --git a/fireplace/cards/classic/mage.py b/fireplace/cards/classic/mage.py index 19006ec31..c52d7774d 100644 --- a/fireplace/cards/classic/mage.py +++ b/fireplace/cards/classic/mage.py @@ -132,9 +132,7 @@ class EX1_275: class EX1_277: """Arcane Missiles""" - def play(self): - count = self.controller.get_spell_damage(3) - yield Hit(RANDOM_ENEMY_CHARACTER, 1) * count + play = Hit(RANDOM_ENEMY_CHARACTER, 1) * SPELL_DAMAGE(3) class EX1_279: diff --git a/fireplace/cards/classic/paladin.py b/fireplace/cards/classic/paladin.py index 1b4ef3949..b9bf0d217 100644 --- a/fireplace/cards/classic/paladin.py +++ b/fireplace/cards/classic/paladin.py @@ -156,9 +156,7 @@ class EX1_371: class EX1_384: """Avenging Wrath""" - def play(self): - count = self.controller.get_spell_damage(8) - yield Hit(RANDOM_ENEMY_CHARACTER, 1) * count + play = Hit(RANDOM_ENEMY_CHARACTER, 1) * SPELL_DAMAGE(8) class EX1_619: diff --git a/fireplace/cards/gangs/neutral_legendary.py b/fireplace/cards/gangs/neutral_legendary.py index 3a8265a7d..df9443d33 100644 --- a/fireplace/cards/gangs/neutral_legendary.py +++ b/fireplace/cards/gangs/neutral_legendary.py @@ -46,13 +46,7 @@ class CFM_672: PlayReq.REQ_MINION_TARGET: 0, PlayReq.REQ_TARGET_IF_AVAILABLE: 0} - def play(self): - targets = self.controller.deck.filter(type=CardType.MINION) - if targets: - target = random.choice(targets) - target.zone = Zone.SETASIDE - yield Shuffle(CONTROLLER, TARGET) - yield Summon(CONTROLLER, target) + play = Swap(TARGET, RANDOM(FRIENDLY_DECK + MINION)) class CFM_685: @@ -65,16 +59,11 @@ class CFM_685: class CFM_806: """Wrathion""" - def play(self): - while True: - current_handsize = len(self.controller.hand) - yield Draw(self.controller) - if len(self.controller.hand) == current_handsize: - # Unable to draw card due to fatigue or max hand size - break - card = self.controller.hand[-1] - if card.type != CardType.MINION or card.race != Race.DRAGON: - break + play = Draw(CONTROLLER).then( + Find(Draw.CARD + DRAGON) | ( + Find(LazyValueSelector(Draw.CARD)) & Battlecry(SELF, None) + ) + ) class CFM_807: diff --git a/fireplace/cards/gangs/priest.py b/fireplace/cards/gangs/priest.py index 350fc9261..3fd67ada9 100644 --- a/fireplace/cards/gangs/priest.py +++ b/fireplace/cards/gangs/priest.py @@ -16,11 +16,7 @@ class CFM_020e: class CFM_605: """Drakonid Operative""" powered_up = HOLDING_DRAGON - - def play(self): - decklist = [i.id for i in self.controller.opponent.deck] - if decklist: - yield HOLDING_DRAGON & DISCOVER(RandomID(*decklist)) + play = powered_up & GenericChoice(CONTROLLER, Copy(RANDOM(DeDuplicate(ENEMY_DECK)) * 3)) class CFM_606: diff --git a/fireplace/cards/gvg/priest.py b/fireplace/cards/gvg/priest.py index 714dd975f..f28f85dbb 100644 --- a/fireplace/cards/gvg/priest.py +++ b/fireplace/cards/gvg/priest.py @@ -21,11 +21,11 @@ class GVG_011: class GVG_014: """Vol'jin""" requirements = {PlayReq.REQ_MINION_TARGET: 0, PlayReq.REQ_TARGET_IF_AVAILABLE: 0} - play = SwapHealth(SELF, TARGET, "GVG_014a") + play = SwapState(SELF, TARGET, "GVG_014a") class GVG_014a: - max_health = lambda self, i: self.health + max_health = lambda self, i: self._xhealth class GVG_072: diff --git a/fireplace/cards/gvg/warrior.py b/fireplace/cards/gvg/warrior.py index 4e28df0c8..58d02bd42 100644 --- a/fireplace/cards/gvg/warrior.py +++ b/fireplace/cards/gvg/warrior.py @@ -55,14 +55,9 @@ class GVG_086: class GVG_050: """Bouncing Blade""" requirements = {PlayReq.REQ_MINIMUM_TOTAL_MINIONS: 1} - - def play(self): - targets = self.game.board.filter(dead=False) - while True: - live_targets = [t for t in targets if t.health > t.min_health] - if live_targets != targets: - break - yield Hit(random.choice(targets), 1) + play = Hit(RANDOM(ALL_MINIONS - IMMUNE - (CURRENT_HEALTH == MIN_HEALTH)), 1).then( + Dead(Hit.TARGET) | CastSpell("GVG_050") + ) class GVG_052: diff --git a/fireplace/cards/kobolds/mage.py b/fireplace/cards/kobolds/mage.py index ca4677d83..44b49b783 100644 --- a/fireplace/cards/kobolds/mage.py +++ b/fireplace/cards/kobolds/mage.py @@ -94,7 +94,7 @@ class Hand: class LOOT_104e: class Hand: events = OWN_TURN_BEGIN.on( - Morph(SELF, RandomSpell(card_class=CardClass.MAGE)).then( + Morph(OWNER, RandomSpell(card_class=CardClass.MAGE)).then( Buff(Morph.CARD, "LOOT_104e")) ) events = REMOVED_IN_PLAY diff --git a/fireplace/cards/kobolds/priest.py b/fireplace/cards/kobolds/priest.py index 5751780df..5de30f5da 100644 --- a/fireplace/cards/kobolds/priest.py +++ b/fireplace/cards/kobolds/priest.py @@ -14,7 +14,7 @@ class LOOT_528: """Twilight Acolyte""" # Battlecry: If you're holding a Dragon, swap this minion's Attack with another # minion's. - play = HOLDING_DRAGON & SwapAtk(TARGET, SELF, "LOOT_528e") + play = HOLDING_DRAGON & SwapState(TARGET, SELF, "LOOT_528e") class LOOT_528e: diff --git a/fireplace/cards/kobolds/shaman.py b/fireplace/cards/kobolds/shaman.py index 922f8c2fa..dc58a28c4 100644 --- a/fireplace/cards/kobolds/shaman.py +++ b/fireplace/cards/kobolds/shaman.py @@ -97,9 +97,7 @@ class LOOT_344e: class LOOT_373: """Healing Rain""" # Restore #12 Health randomly split among all friendly characters. - def play(self): - count = self.controller.get_spell_heal(12) - yield Heal(RANDOM_FRIENDLY_CHARACTER, 1) * count + play = Heal(RANDOM_FRIENDLY_CHARACTER, 1) * SPELL_HEAL(12) class LOOT_504: diff --git a/fireplace/cards/league/collectible.py b/fireplace/cards/league/collectible.py index 4bd689341..204612afe 100644 --- a/fireplace/cards/league/collectible.py +++ b/fireplace/cards/league/collectible.py @@ -198,9 +198,7 @@ class LOEA16_3: class LOEA16_4: """Timepiece of Horror""" - def play(self): - count = self.controller.get_spell_damage(10) - yield Hit(RANDOM_ENEMY_CHARACTER, 1) * count + play = Hit(RANDOM_ENEMY_CHARACTER, 1) * SPELL_DAMAGE(10) class LOEA16_5: diff --git a/fireplace/cards/ungoro/druid.py b/fireplace/cards/ungoro/druid.py index c9564c08d..099dd89c1 100644 --- a/fireplace/cards/ungoro/druid.py +++ b/fireplace/cards/ungoro/druid.py @@ -56,13 +56,13 @@ class UNG_108: class UNG_111: """Living Mana""" - def play(self): - count = min( - self.controller.max_mana, - self.game.MAX_MINIONS_ON_FIELD - len(self.controller.field) + play = (MANA(CONTROLLER) > 0) & ( + FULL_BOARD | ( + GainEmptyMana(CONTROLLER, -1), + Summon(CONTROLLER, "UNG_111t1"), + Battlecry(SELF, None) ) - yield GainEmptyMana(CONTROLLER, -count) - yield Summon(CONTROLLER, "UNG_111t1") * count + ) class UNG_111t1: diff --git a/fireplace/cards/ungoro/shaman.py b/fireplace/cards/ungoro/shaman.py index d9df11e4a..8cbf385a5 100644 --- a/fireplace/cards/ungoro/shaman.py +++ b/fireplace/cards/ungoro/shaman.py @@ -62,9 +62,7 @@ class UNG_938: class UNG_025: """Volcano""" - def play(self): - count = self.controller.get_spell_damage(15) - yield Hit(RANDOM_MINION, 1) * count + play = Hit(RANDOM_MINION, 1) * SPELL_DAMAGE(15) class UNG_817: diff --git a/fireplace/cards/wog/druid.py b/fireplace/cards/wog/druid.py index 74330f74f..eb9e8f1fb 100644 --- a/fireplace/cards/wog/druid.py +++ b/fireplace/cards/wog/druid.py @@ -6,10 +6,9 @@ class OG_051: """Forbidden Ancient""" - def play(self): - mana = self.controller.mana - yield SpendMana(CONTROLLER, mana) - yield Buff(SELF, "OG_051e") * mana + play = SpendMana(CONTROLLER, CURRENT_MANA(CONTROLLER)).then( + Buff(SELF, "OG_051e") * SpendMana.AMOUNT + ) OG_051e = buff(+1, +1) diff --git a/fireplace/cards/wog/mage.py b/fireplace/cards/wog/mage.py index 41e8608bb..62ceeb31b 100644 --- a/fireplace/cards/wog/mage.py +++ b/fireplace/cards/wog/mage.py @@ -54,8 +54,6 @@ class OG_090: class OG_086: """Forbidden Flame""" requirements = {PlayReq.REQ_MINION_TARGET: 0, PlayReq.REQ_TARGET_TO_PLAY: 0} - - def play(self): - mana = self.controller.mana - yield SpendMana(CONTROLLER, mana) - yield Hit(TARGET, mana) + play = SpendMana(CONTROLLER, CURRENT_MANA(CONTROLLER)).then( + Hit(TARGET, SpendMana.AMOUNT) + ) diff --git a/fireplace/cards/wog/neutral_epic.py b/fireplace/cards/wog/neutral_epic.py index 9b2ed631e..124131afa 100644 --- a/fireplace/cards/wog/neutral_epic.py +++ b/fireplace/cards/wog/neutral_epic.py @@ -66,9 +66,6 @@ class OG_102e: atk = lambda self, i: self._xatk max_health = lambda self, i: self._xhealth - def apply(self, target): - target.damage = 0 - class OG_174: """Faceless Shambler""" diff --git a/fireplace/cards/wog/paladin.py b/fireplace/cards/wog/paladin.py index 134d410a0..9bc7fe844 100644 --- a/fireplace/cards/wog/paladin.py +++ b/fireplace/cards/wog/paladin.py @@ -75,8 +75,6 @@ class OG_222: class OG_198: """Forbidden Healing""" requirements = {PlayReq.REQ_TARGET_TO_PLAY: 0} - - def play(self): - mana = self.controller.mana - yield SpendMana(CONTROLLER, mana) - yield Heal(TARGET, mana * 2) + play = SpendMana(CONTROLLER, CURRENT_MANA(CONTROLLER)).then( + Heal(TARGET, SpendMana.AMOUNT * 2) + ) diff --git a/fireplace/cards/wog/priest.py b/fireplace/cards/wog/priest.py index 03d773ee9..3c436bee2 100644 --- a/fireplace/cards/wog/priest.py +++ b/fireplace/cards/wog/priest.py @@ -65,7 +65,6 @@ class OG_100: class OG_101: """Forbidden Shaping""" - def play(self): - mana = self.controller.mana - yield SpendMana(CONTROLLER, mana) - yield Summon(CONTROLLER, RandomMinion(cost=mana)) + play = SpendMana(CONTROLLER, CURRENT_MANA(CONTROLLER)).then( + Summon(CONTROLLER, RandomMinion(cost=SpendMana.AMOUNT)) + ) diff --git a/fireplace/cards/wog/warlock.py b/fireplace/cards/wog/warlock.py index 4c4a371b3..ec892505a 100644 --- a/fireplace/cards/wog/warlock.py +++ b/fireplace/cards/wog/warlock.py @@ -49,7 +49,6 @@ class OG_116: class OG_118: """Renounce Darkness""" def play(self): - import random classes = [ (CardClass.DRUID, "CS2_017"), (CardClass.HUNTER, "DS1h_292"), @@ -85,7 +84,6 @@ def play(self): class OG_114: """Forbidden Ritual""" - def play(self): - mana = self.controller.mana - yield SpendMana(CONTROLLER, mana) - yield Summon(CONTROLLER, "OG_114a") * mana + play = SpendMana(CONTROLLER, CURRENT_MANA(CONTROLLER)).then( + Summon(CONTROLLER, "OG_114a") * SpendMana.AMOUNT + ) diff --git a/fireplace/dsl/evaluator.py b/fireplace/dsl/evaluator.py index b7b835377..62afaa4b8 100644 --- a/fireplace/dsl/evaluator.py +++ b/fireplace/dsl/evaluator.py @@ -154,6 +154,9 @@ def __init__(self, *selectors): super().__init__() self.selectors = selectors + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.selectors) + def check(self, source): return all( bool(len(selector.eval(source.game, source))) diff --git a/fireplace/dsl/selector.py b/fireplace/dsl/selector.py index f43404a4a..46de9d700 100644 --- a/fireplace/dsl/selector.py +++ b/fireplace/dsl/selector.py @@ -127,6 +127,7 @@ def __repr__(self): CONTROLLER = AttrValue(GameTag.CONTROLLER) MAX_HEALTH = AttrValue(GameTag.HEALTH) CURRENT_HEALTH = AttrValue("health") +MIN_HEALTH = AttrValue(GameTag.HEALTH_MINIMUM) COST = AttrValue(GameTag.COST) DAMAGE = AttrValue(GameTag.DAMAGE) MANA = AttrValue(GameTag.RESOURCES) @@ -154,7 +155,11 @@ def eval(self, entities, source): ) return [ e for e in entities - if self.op(self.left.value(e, source), right_value) + if self.op( + self.left.value(e, source), + right_value.value(e, source) if isinstance(right_value, SelectorEntityValue) + else right_value + ) ] def __repr__(self): @@ -269,7 +274,8 @@ def __repr__(self): def LazyValueSelector(value): - return FuncSelector(lambda entities, source: [value.evaluate(source)]) + return FuncSelector( + lambda entities, source: [value.evaluate(source)] if value.evaluate(source) else []) def ID(id): @@ -432,6 +438,7 @@ def CONTROLLED_BY(selector): CLASS_CARD = EnumSelector(GameTag.CLASS) DORMANT = EnumSelector(GameTag.DORMANT) LIFESTEAL = EnumSelector(GameTag.LIFESTEAL) +IMMUNE = EnumSelector(GameTag.IMMUNE) ALWAYS_WINS_BRAWLS = AttrValue(enums.ALWAYS_WINS_BRAWLS) == True # noqa KILLED_THIS_TURN = AttrValue(enums.KILLED_THIS_TURN) == True # noqa @@ -550,3 +557,8 @@ def CONTROLLED_BY(selector): STARTING_DECK = FuncSelector( lambda entities, source: source.controller.starting_deck) + +SPELL_DAMAGE = lambda amount: FuncSelector( + lambda entities, source: source.controller.get_spell_damage(amount)) +SPELL_HEAL = lambda amount: FuncSelector( + lambda entities, source: source.controller.get_spell_heal(amount)) diff --git a/tests/test_gangs.py b/tests/test_gangs.py index a97e9c8f3..350b99536 100644 --- a/tests/test_gangs.py +++ b/tests/test_gangs.py @@ -362,3 +362,22 @@ def test_madam_goya(): game.player1.give("CFM_672").play(target=wisp) assert wisp.zone == Zone.DECK assert murloc.zone == Zone.PLAY + + +def test_wrathion(): + game = prepare_empty_game() + game.player1.give(WISP).put_on_top() + game.player1.give("NEW1_023").put_on_top() + for _ in range(4): + game.player1.give(WISP).put_on_top() + assert len(game.player1.hand) == 0 + game.player1.give("CFM_806").play() + assert len(game.player1.hand) == 5 + + +def test_wrathion_empty(): + game = prepare_empty_game() + game.player1.cant_fatigue = False + game.player1.give(WISP).put_on_top() + game.player1.give("CFM_806").play() + assert game.player1.hero.health == 30 - 1 diff --git a/tests/test_misc.py b/tests/test_misc.py index ace98cd5a..48d080405 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -57,7 +57,7 @@ def test_anubar_ambusher_cult_master(): ambusher1.destroy() assert len(game.player1.hand) == 2 assert cultmaster1 in game.player1.hand - game.end_turn(); game.end_turn() + game.skip_turn() game.player1.discard_hand() ambusher2 = game.player1.summon("FP1_026")