Skip to content


Browse files Browse the repository at this point in the history
Implement refreshing auras declaratively
This is the fifth version of the Aura API

We are not deprecating the old system yet, although this is preferrable
over attribute-script based modifiers where relevant.

Previously, we would create an attribute script for atk/cost for some
cards and return a modified value based on the board. For example,
Old Murk-Eye would consistently have its attack depend on the number
of murlocs on the board.

This is a problem for three reasons:
 - It's ugly in the DSL. You'd have to filter the board with race=Race.MURLOC,
   exclude self, etc. The new system allows for reuse of selectors.
 - Attribute scripts are constantly "on". Murk-Eye would then have 3 atk in
   the hand, Mountain Giant would have its cost reduced on the board, etc.
 - We currently have to create custom cards for every single buff. That's a
   huge hassle, and problematic for Kettle.
  • Loading branch information
jleclanche committed Sep 3, 2015
1 parent 97faa53 commit 61f87cf
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 3 deletions.
61 changes: 61 additions & 0 deletions fireplace/
@@ -1,3 +1,4 @@
from .managers import CardManager
from .utils import CardList, fireplace_logger as logger

Expand Down Expand Up @@ -71,3 +72,63 @@ def destroy(self):
del self._buffs
del self._buffed

class AuraBuff:
def __init__(self, source, entity):
self.source = source
self.entity = entity
self.tags = CardManager(self)

def __repr__(self):
return "<AuraBuff on %r from %r>" % (self.entity, self.source)

def update_tags(self, tags):
self.tick =

def destroy(self):

def _getattr(self, attr, i):
value = getattr(self, attr, 0)
if callable(value):
return value(self.entity, i)
return i + value

class Refresh:
Refresh a buff or a set of tags on an entity
def __init__(self, selector, tags):
self.selector = selector
self.tags = tags

def trigger(self, source):
entities = self.selector.eval(, source)
for entity in entities:
tags = {}
for tag, value in self.tags.items():
if not isinstance(value, int) and not callable(value):
value = value.evaluate(source)
tags[tag] = value

entity.refresh_buff(source, tags)

class TargetableByAuras:
def refresh_buff(self, source, tags):
for slot in self.slots[:]:
if isinstance(slot, AuraBuff) and slot.source is source:
# Move the buff position at the end again
buff = AuraBuff(source, self)
4 changes: 2 additions & 2 deletions fireplace/
@@ -1,7 +1,7 @@
from itertools import chain
from . import cards as CardDB, rules
from .actions import Damage, Deaths, Destroy, Heal, Morph, Play, Shuffle, SetCurrentHealth
from .aura import Aura
from .aura import Aura, TargetableByAuras
from .entity import Entity, boolean_property, int_property
from .enums import CardType, PlayReq, Race, Rarity, Zone
from .managers import CardManager
Expand Down Expand Up @@ -120,7 +120,7 @@ def buff(self, target, buff, **kwargs):
return ret

class PlayableCard(BaseCard):
class PlayableCard(BaseCard, TargetableByAuras):
windfury = boolean_property("windfury")

def __init__(self, id, data):
Expand Down
10 changes: 10 additions & 0 deletions fireplace/
Expand Up @@ -33,6 +33,8 @@ def __init__(self, players):
self.auras = []
self.minions_killed_this_turn = CardList()
self.no_aura_refresh = False
self.tick = 0
self.active_aura_buffs = []

def __repr__(self):
return "%s(players=%r)" % (self.__class__.__name__, self.players)
Expand Down Expand Up @@ -211,6 +213,14 @@ def refresh_auras(self):
for aura in self.auras:
for entity in self.entities:
if and hasattr(, "update"):
if not entity.silenced:
for buff in self.active_aura_buffs[:]:
if buff.tick < self.tick:
self.tick += 1

def prepare(self):
self.players[0].opponent = self.players[1]
Expand Down
3 changes: 2 additions & 1 deletion fireplace/
@@ -1,6 +1,7 @@
import random
from itertools import chain
from .actions import Draw, Give, Steal, Summon
from .aura import TargetableByAuras
from .card import Card
from .deck import Deck
from .entity import Entity
Expand All @@ -11,7 +12,7 @@
from .utils import CardList

class Player(Entity):
class Player(Entity, TargetableByAuras):
Manager = PlayerManager
extra_deathrattles = slot_property("extra_deathrattles")
healing_double = slot_property("healing_double", sum)
Expand Down

0 comments on commit 61f87cf

Please sign in to comment.