#!/usr/bin/env python"""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 likeMaxHP - 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: __doc__ = "Parent for internal use, chain updates other objects when _set, should not be set directly" def __init__(self): self._updates = [] self.value = 0 def calculate(self): self._set(self.formula()) def _set(self, val): self.value = val for stat in self._updates: stat.calculate()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._setclass CompoundStat(ChainUpdatingNumber): __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()." def __init__(self, base=PrimitiveStat()): ChainUpdatingNumber.__init__(self) self.multipliers = MultiplicationBonusCollection() self.bonuses = AddingBonusCollection() self.base = base for stat in [self.multipliers, self.bonuses, self.base]: stat._updates.append(self) def formula(self): return (self.multipliers.value * self.base.value) + self.bonuses.valueclass 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 = formulaclass 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, value): ChainUpdatingNumber.__init__(self) self.value = value def cancel(self): """removes the bonus from all BonusCollections""" for bonusCollection in self._updates: bonusCollection.remove(self)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()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 xclass MultiplicationBonusCollection(BonusCollectionParent): __doc__ = "Bonus collection for bonuses that are multiplied together" def formula(self): x = 1 for bonus in self.bonuses: x *= (1 + bonus) return x