Модификаторы атрибутов в играх

1,00
р.
Подскажите, принцип (архитектуру, структуру), как организовать объекты, свойства которых могут зависеть от разных факторов?
Для примера возьмем ХитПоинты и броню
public class Player { decimal _hitPoints decimal _armor
public decimal HitPoints { get { return _hitPoints } set { _hitPoints = value } }
public decimal Armor { get { return _armor } set { _armor = value } } }
Итак, у игрока есть хитпоинты. Их количество может меняться посредством каких либо действий или состояний.
Пример действий:
игрок может сожрать булочку игрок может упасть игрок может покончить жизнь самоубийством игрока может атаковать другой игрок
Пример состояния:
у игрока есть постоянная регенерация на игрока повесили бафы / дебафы
Все это может повлиять на количество хитпоинтов.
Менять состояние в лоб
public DecraseHitpoints(decimal value) { HitPoints -= value * [функция от брони] * [функция от состояния1] * [функция от состояния2] }
проблематично, потому что добавив новую зависимость я замучаюсь все это поддерживать. Функции от состояния в данном случае - что угодно, например, баф - снижение получаемого урона на 50%.
Короче обычные привычные подходы тут не подходят :)
Нужна кардинально другая архитектура.
Я представляю это себе примерно так. Класс имеет коллекцию действий и состояний. Действие должно быть направлено на изменение какого то свойства (или нескольких свойств) и описывать КАК эти свойства должны быть изменены. Опять же должна быть зависимость от состояния. Например, если в данный момент игрок неуязвим, урон вообще не должен проходить.
Голова кругом идет уже.
UPD:
Ну да, при этом действие, нацеленное на изменение свойств, должно не просто учесть состояния, которые могут менять воздействие, но оно не должно знать обо всех состояниях, а только о воздействии состояния. То есть не нужно знать обо всех возможных бафах, нужно знать только о том, что на игрока наложены бафы, которые могут изменить зависимые величины (броню, количество урона непосредственно)...

Ответ
Можно подглядеть как это сделано у других. Например, в Skyrim это делается через систему перков.
К каждому персонажу привязывается динамический список перков, которые могут иметь разные точки входа. Точка входа - это игровое событие (например, получение урона). Перки обрабатывают событие в некотором порядке - и могут менять обрабатываемое значение (в случае получения урона - величину отнимаемого здоровья). У перка могут быть условия срабатывания (день/ночь, источник урона, надетые предметы и пр.).

Полностью копировать такую систему не стоит - просто потому что она была сделана для гейм-дизайнеров, у вас же отдельного гейм-дизайнера нет и вы можете вместо создания мегауниверсального движка просто писать код.
Выглядеть это может таким образом:
class BaseModifier { public virtual void ModifyIncomingDamage(ref decimal value, DamageEvent e) { }
public virtual void ЧтоТоЕще(ref decimal value, КакойТоЕщеEvent e) { } }
class ArmorModifier : BaseModifier { public override void ModifyIncomingDamage(ref decimal value, DamageEvent e) { value *= функцияОтБрони } }
class ModifierCollection { private readonly List modifiers = new List() public void Add(BaseModifier m) => modifiers.Add(m) public void Remove(BaseModifier m) where T : BaseModifier => modifiers.Remove(m) public void Remove() where T : BaseModifier => Remove(modifiers.OfType().First())
public decimal ModifyIncomingDamage(decimal value, DamageEvent e) { foreach (var m in modifiers) m.ModifyIncomingDamage(ref value, e) return value } }
class Character { public decimal HitPoints { get set }
public ModifierCollection Modifiers { get } = new ModifierCollection()
public void Hit(decimal value, DamageEvent e) => HitPoints -= Modifiers.ModifyIncomingDamage(value, e) }

Теперь про упомянутую вами регенерацию здоровья. Сама по себе регенерация - это не состояние, это величина. Точнее, это величина, определяемая "навешанными" на персонажа эффектами.
Хранить ее можно двумя путями.
Не хранить вообще, а при необходимости пробегаться по всем активным эффектам и суммировать их воздействия. Хранить как число, изменять при появлении/исчезновении эффекта.
Второй вариант предпочтительнее (будет работать быстрее) - но требует большей аккуратности при реализации.
Непосредственно реализация регенерации может быть достигнута двумя способами.
По таймеру - периодически обновлять здоровье всех персонажей. Непрерывный вариант - обновлять здоровье при чтении или изменении состояния, в зависимости от прошедшего с момента прошлого обновления времени.
Недостаток первого варианта - в том, что при слишком большом интервале обновления регенерация станет заметно рваной, а слишком низкий интервал нагрузит процессор впустую.
Недостаток второго варианта - надо писать много кода. Плюс может быть проблема с умениями, которые делают текущую регенерацию здоровья зависимой от текущего значения здоровья (пример такой вредной способности - пассивка мурлока в "Жизни на арене").
Поэтому обычно не заморачиваются и обновляют здоровье по таймеру. А чтобы полоска здоровья не дергалась прыжками - в интерфейсе делаются плавные переходы.
В итоге, регенерация здоровья может быть реализована примерно так:
class Character { public decimal HitPoints { get set } public decimal HitPointsRegen { get set }
public ModifierCollection Modifiers { get } = new ModifierCollection()
public void OnUpdate(decimal interval) { HitPoints += Modifiers.ModifyRegen(HitPointsRegen, this) * interval } }
Можно также запоминать модифицированное значение регенерации здоровья для ускорения:
class Character { public decimal HitPoints { get set }
private decimnal _hitPointsRegen, _hitPointsRegenModified public decimal HitPointsRegen { get { return _hitPointsRegen } set { _hitPointsRegen = value _hitPointsRegenModified = Modifiers.ModifyRegen(HitPointsRegen, this) } }
public ModifierCollection Modifiers { get } = new ModifierCollection () public Character() { Modifiers.ModifiersUpdated += OnModifiersUpdated }
protected void OnModifiersUpdated() { HitPointsRegen = HitPointsRegen // Обновление зависимых значений }
public void OnUpdate(decimal interval) { HitPoints += _hitPointsRegenModified * interval } }