Permalink
Browse files

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 2, 2015
1 parent 97faa53 commit 61f87cf4163f740f6475d0966cf1b76c3b25957b
Showing with 75 additions and 3 deletions.
  1. +61 −0 fireplace/aura.py
  2. +2 −2 fireplace/card.py
  3. +10 −0 fireplace/game.py
  4. +2 −1 fireplace/player.py
@@ -1,3 +1,4 @@
from .managers import CardManager
from .utils import CardList, fireplace_logger as logger


@@ -71,3 +72,63 @@ def destroy(self):
del self._buffs
del self._buffed
self.source.auras.remove(self)


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.tags.update(tags)
self.tick = self.source.game.tick

def destroy(self):
self.entity.slots.remove(self)
self.source.game.active_aura_buffs.remove(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.game, 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:
slot.update_tags(tags)
# Move the buff position at the end again
self.slots.remove(slot)
self.slots.append(slot)
break
else:
buff = AuraBuff(source, self)
buff.update_tags(tags)
self.slots.append(buff)
source.game.active_aura_buffs.append(buff)
@@ -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
@@ -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):
@@ -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)
@@ -211,6 +213,14 @@ def refresh_auras(self):
return
for aura in self.auras:
aura.update()
for entity in self.entities:
if entity.data and hasattr(entity.data.scripts, "update"):
if not entity.silenced:
entity.data.scripts.update.trigger(entity)
for buff in self.active_aura_buffs[:]:
if buff.tick < self.tick:
buff.destroy()
self.tick += 1

def prepare(self):
self.players[0].opponent = self.players[1]
@@ -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
@@ -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)

0 comments on commit 61f87cf

Please sign in to comment.