#!/usr/bin/env python from effect import Effect """ There are infinately many ways to represent any given stat in a dependency tree, here is one that is not entirely stupid, I'm sure you can do better, but I'm happy with good enough. A complex HP formula might look like MaxHP - Formula, composed of (ClassHP * LevelHP) + WearwolfRacialHP + ConstitutionHP ClassHP - PrimitiveStat, set to 4, Knights are hard to kill! LevelHP - Formula, composed of (Level * 7) Level - PrimitiveStat, set to 2 RacialHP - Formula, composed of (Level + MoonPhase) Level - Same PrimitiveStat as used in LevelHP, thus still set to 2 MoonPhase - PrimitiveStat, points to world.moonphase.MoonStat, a PrimitiveStat stored on the world instead of on the ent, but updates the stats on many ents when it changes ConstitutionHP -Formula, composed of (Constitution * 8) Constitution - CompoundStat, composed of - multipliers - MultiplicitiveBonusCollection - Hungry, -.05 - bonuses - AddingBonusCollection - Amulet Of Health, +7 - Cursed Boots, -2 - base - PrimitiveStat, set to 10 """ class ChainUpdatingNumber(object): __doc__ = "Parent for internal use, chain updates other objects when _set, should not be set directly" def __init__(self): self._updates = [] self.value = 0 self.pseudoClass = str(self.__class__) object.__init__(self) def calculate(self): self._set(self.formula()) def _set(self, val): self.value = val for stat in self._updates: stat.calculate() def addToStatCollection(self, collection): collection[self.pseudoClass] = self def removeFromStatCollection(self, collection): del(collection[self.pseudoClass]) class PrimitiveStat(ChainUpdatingNumber): __doc__ = "Terminal Stat that is not updated by any other ChainUpdatingNumber, and the only ChainUpdatingNumber that can be set. Use this for stats such as level, class_strength_contribution, etc." def __init__(self): ChainUpdatingNumber.__init__(self) self.set = self._set class CalculatedStat(ChainUpdatingNumber): __doc__ = "Stat that is updated according to a formula each time one of the items it is watching is updated. Typically every stat reference in the formula will be included in this stat's watch list." def __init__(self, formula, updated_by): """The constructor requires two arguments, the first is a lambda function such as lambda: strength.value + level.value, and the second is a list of items that, if changed, must cause this Stat to be recalculated, such as [level, strength].""" ChainUpdatingNumber.__init__(self) for stat in updated_by: stat._updates.append(self) self.formula = formula class Bonus(ChainUpdatingNumber): __doc__ = "Unmodified bonus, doesn't do much for now, but will be helpful down the road for explaining why players have the values they do." def __init__(self, pseudoClass, value): ChainUpdatingNumber.__init__(self) self.pseudoClass = pseudoClass self.value = value def cancel(self): """removes the bonus from all BonusCollections""" for bonusCollection in self._updates: bonusCollection.remove(self) class RobsMetatype(type): def X__new__(meta, name, bases, attrs): print "WHEE" return type.__new__(meta, name, bases, attrs) def __sub__(meta, value): return Bonus(meta.AddersClass.pseudoClass, -value) def __set__(a,b): print "WHEE" class RobsMetatypeB(type): def __new__(meta, name, bases, attrs): print "WHEE" return type.__new__(meta, name, bases, attrs) def __sub__(meta, value): return Bonus(meta.AddersClass.pseudoClass, -value) def __unimplemented_get__(a,b,c): return "COWZ" class BonusCollectionParent(ChainUpdatingNumber): __doc__ = "Collection of bonuses, recalculated when items are added or removed from the collection." def __init__(self): ChainUpdatingNumber.__init__(self) self.bonuses = set() self.calculate() def apply(self, item): self.bonuses.add(item) item._updates.append(self) self.calculate() def remove(self, item): item._updates.remove(self) self.bonuses.remove(item) self.calculate() def bonus(self, val): r = Bonus(self.pseudoClass, val) return r class AddingBonusCollection(BonusCollectionParent): __doc__ = "Bonus collection for bonuses that are added together." def formula(self): x = 0 for bonus in self.bonuses: x += bonus.value return x class MultiplicationBonusCollection(BonusCollectionParent): __doc__ = "Bonus collection for bonuses that are multiplied together" def formula(self): x = 1 for bonus in self.bonuses: x *= (1 + bonus.value) return x class CompoundStat(ChainUpdatingNumber): __metaclass__ = RobsMetatype __doc__ = "Convenience stat, don't use it if you don't like it. This is a Stat that is updated by 3 other stats according to the formula (multipliers * base) + bonus. Probably useful for base stats such as strength, that you will want to be able to set directly, as well as modify by both absolute amount and by percentage. Multipliers and bonuses are both BonusCollection stats which can have bonuses applyed and removeed with them. Base is a stat primitive which can be directly .set()." class MultipliersClass(MultiplicationBonusCollection): pass class AddersClass(AddingBonusCollection): pass AddersClass.pseudoClass = id(AddersClass) MultipliersClass.pseudoClass = id(MultipliersClass) def Multiplier(self, value): return Bonus(self.MultipliersClass.pseudoClass, value) def Adder(self, value): return Bonus(self.AddersClass.pseudoClass, value) Multiplier = classmethod(Multiplier) Adder= classmethod(Adder) def __init__(self, base=PrimitiveStat()): ChainUpdatingNumber.__init__(self) self.base = base self.multipliers = self.MultipliersClass() self.multipliers.pseudoClass = self.MultipliersClass.pseudoClass self.adders = self.AddersClass() self.adders.pseudoClass = self.AddersClass.pseudoClass for stat in [self.multipliers, self.adders, self.base]: stat._updates.append(self) def formula(self): return (self.multipliers.value * self.base.value) + self.adders.value def addToStatCollection(self, collection): self.adders.addToStatCollection(collection) self.multipliers.addToStatCollection(collection) ChainUpdatingNumber.addToStatCollection(self, collection) def removeFromStatCollection(self, collection): ChainUpdatingNumber.removeFromStatCollection(self, collection) class StatBuff(Effect): def __init__(self, bonuses): Effect.__init__(self) self.bonuses = bonuses def onApply(self, ent): for bonus in self.bonuses: ent.stats.apply(bonus) def onRemove(self, ent): for bonus in self.bonuses: bonus.cancel() class StatCollection(dict): def __init__(self): dict.__init__(self) def apply(self, bonus): self[bonus.pseudoClass].apply(bonus)