Решили: три механики мага ветра собраны компонентами эффектов — RangedWardComponent,
EvasionComponent, AllyGustComponent; счётчик принятых Ударов RuntimeUnit.HitsTaken считается в
CombatSimulation.ResolveIncoming, а не в EffectSystem.RunPreDamage.
Почему: место счёта было развилкой. RunPreDamage выглядит естественнее — счётчик нужен только
реакциям, — но он выходит сразу, если на цели нет ни одного эффекта. Тогда период «каждой четвёртой»
поехал бы у бойца, с которого уклонение сняли и повесили заново: удары, принятые без эффекта, в счёт
не попали бы. ResolveIncoming — единственное место, где тратятся все защиты цели, и он зовётся
всегда; там же счёт идёт ДО опроса реакций, зеркально HitsMade у слепоты.
Грабли:
- Заряд реакции не взводится сам.
RuntimeEffect.TryConsumeChargeмолча отвечает «нет готовых», если массив зарядов не создан, а создаёт его сам компонент вOnApplyчерезArmCharges— так делаютDodgeComponent,BlockComponent,ParryComponent,PhaseShiftComponent. Новый компонент с перезарядкой, забывший эту строчку, не срабатывает никогда и без единой ошибки в логе: три теста «Порыва» падали на пустом месте, пока причина не нашлась. Ищется тяжело — симптом выглядит как «условие не выполняется». UnitDataабстрактен.ScriptableObject.CreateInstance<UnitData>()возвращает null, аnull.GetType()в тестовом хелпереWithдаёт голыйNullReferenceExceptionбез намёка на причину. В тестах брать конкретного наследника —EnemyDataилиRelicData.- Результат метода нельзя передать в
in-параметр (CS8156):RunPreDamage(unit, in Hit(...))не компилируется. Либо локальная переменная, либо просто убратьin— компилятор сам заведёт временную. - Дееспособность реакции не всегда про носителя. Маркер
IRequiresAgencyComponentпроверяет того, на ком висит эффект; у «Порыва» действует АВТОР, а носителя просто дёргают. Пришлось спрашиватьauthor.CanActAtTickStartруками — маркер здесь дал бы ровно обратное поведение (оглушённый союзник переставал бы спасаться, а оглушённый маг продолжал бы работать). - Своего поля скорости у юнита нет. Для выбора стороны перпендикулярного рывка направление
движения берётся как
Position − PreviousPosition.
Владелец правды: Assets/_Project/Scripts/Combat/Effects/Components/ (три компонента),
RuntimeUnit.HitsTaken, тест ZephyrSliceTests.