Skip to content

Commit

Permalink
Kobolds & Catacombs
Browse files Browse the repository at this point in the history
  • Loading branch information
shinoi2 committed Dec 7, 2023
1 parent 19f0be8 commit a4260b0
Show file tree
Hide file tree
Showing 31 changed files with 1,080 additions and 504 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ A Hearthstone simulator and implementation, written in Python.

* `pip install .`

# Cards Implementation

Now updated to [Patch 10.4.0.23576](https://hearthstone.fandom.com/wiki/Patch_10.4.0.23576)
* **100%** Basic (142 of 142 cards)
* **100%** Classic (239 of 239 cards)
* **100%** Hall of Fame (10 of 10 cards)
* **100%** Curse of Naxxramas (30 of 30 cards)
* **100%** Goblins vs Gnomes (123 of 123 cards)
* **100%** Blackrock Mountain (31 of 31 cards)
* **100%** The Grand Tournament (132 of 132 cards)
* **100%** The League of Explorers (45 of 45 cards)
* **100%** Whispers of the Old Gods (134 of 134 cards)
* **100%** One Night in Karazhan (45 of 45 cards)
* **100%** Mean Streets of Gadgetzan (132 of 132 cards)
* **100%** Journey to Un'Goro (135 of 135 cards)
* **100%** Knights of the Frozen Throne (135 of 135 cards)
* **100%** Kobolds & Catacombs (135 of 135 cards)

## Documentation

Expand Down
44 changes: 36 additions & 8 deletions fireplace/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,8 @@ def do(self, source, card, target, index, choose):
card.zone = Zone.PLAY

# Remember cast on friendly characters
if target and target.controller == source:
card.cast_on_friendly_characters = True
if target and target.type == CardType.MINION and target.controller == source:
card.cast_on_friendly_minions = True

# NOTE: A Play is not a summon! But it sure looks like one.
# We need to fake a Summon broadcast.
Expand Down Expand Up @@ -614,7 +614,8 @@ def do(self, source, target, buff):
if isinstance(v, LazyValue):
v = v.evaluate(source)
setattr(buff, k, v)
return buff.apply(target)
buff.apply(target)
return target


class StoringBuff(TargetedAction):
Expand Down Expand Up @@ -1112,7 +1113,7 @@ def do(self, source, target, amount):
if source.controller.healing_as_damage:
return source.game.queue_actions(source, [Hit(target, amount)])

amount <<= source.controller.healing_double
amount = source.get_heal(amount, target)
amount = min(amount, target.damage)
if amount:
# Undamaged targets do not receive heals
Expand Down Expand Up @@ -1470,18 +1471,26 @@ class CastSpell(TargetedAction):
"""
Cast a spell target random
"""
CARD = CardArg()
SPELL = CardArg()
SPELL_TARGET = CardArg()

def do(self, source, card):
target = None
def get_target_args(self, source, target):
ret = super().get_target_args(source, target)
spell_target = None
if ret:
spell_target = ret[0][0]
return [spell_target]

def do(self, source, card, target=None):
player = source.controller
old_choice = player.choice
player.choice = None
if card.must_choose_one:
card = random.choice(card.choose_cards)
if card.requires_target():
if len(card.targets):
target = random.choice(card.targets)
if target not in card.targets:
target = random.choice(card.targets)
else:
log.info("%s cast spell %s don't have a legal target", source, card)
return
Expand Down Expand Up @@ -1581,6 +1590,25 @@ def do(self, source, target, other, buff):
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
Expand Down
25 changes: 23 additions & 2 deletions fireplace/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,25 @@ def description(self):
self.controller.get_spell_damage(int(match.group("damage")))
),
description)
description = re.sub(
"\\#(?P<heal>\\d+)",
lambda match: str(
self.controller.get_spell_heal(int(match.group("heal")))
),
description)
elif self.type == CardType.HERO_POWER:
description = re.sub(
"\\$(?P<damage>\\d+)",
lambda match: str(
self.controller.get_heropower_damage(int(match.group("damage")))
),
description)
description = re.sub(
"\\#(?P<heal>\\d+)",
lambda match: str(
self.controller.get_heropower_heal(int(match.group("heal")))
),
description)
return description

@property
Expand Down Expand Up @@ -226,6 +238,7 @@ class PlayableCard(BaseCard, Entity, TargetableByAuras):
has_choose_one = boolean_property("has_choose_one")
playable_zone = Zone.HAND
lifesteal = boolean_property("lifesteal")
keep_buff = boolean_property("keep_buff")

def __init__(self, data):
self.cant_play = False
Expand All @@ -237,8 +250,7 @@ def __init__(self, data):
self.choose_cards = CardList()
self.morphed = None
self.upgrade_counter = 0
self.cast_on_friendly_characters = False
self.keep_buff = False
self.cast_on_friendly_minions = False
super().__init__(data)

@property
Expand Down Expand Up @@ -908,6 +920,11 @@ def get_damage(self, amount, target):
amount *= 2
return amount

def get_heal(self, amount, target):
if not self.immune_to_spellpower:
amount = self.controller.get_spell_heal(amount)
return amount

def _set_zone(self, value):
if value == Zone.PLAY:
value = Zone.GRAVEYARD
Expand Down Expand Up @@ -1120,6 +1137,10 @@ def get_damage(self, amount, target):
amount = super().get_damage(amount, target)
return self.controller.get_heropower_damage(amount)

def get_heal(self, amount, target):
amount = super().get_heal(amount, target)
return self.controller.get_heropower_heal(amount)

def use(self, target=None, choose=None):
if choose:
if self.must_choose_one:
Expand Down
Loading

0 comments on commit a4260b0

Please sign in to comment.