From c18c0ee08597b62afe5a26611b7dae7b402e3a83 Mon Sep 17 00:00:00 2001 From: chillu Date: Wed, 4 Feb 2026 21:41:10 +0100 Subject: [PATCH] feat: initial tick logic and support --- .../BenchmarkModifierRecipes.cs | 2 +- .../AddModifierWithDataTests.cs | 45 ++++++++++ ModiBuff/ModiBuff.Tests/CooldownTests.cs | 2 +- ModiBuff/ModiBuff.Tests/DurationTests.cs | 90 +++++++++++++++++++ ModiBuff/ModiBuff.Tests/IntervalTests.cs | 79 ++++++++++++++++ .../ModiBuff.Tests/ModifierRecipeDataTests.cs | 2 +- .../ModiBuff.Tests/ModifierStateInfoTest.cs | 2 +- ModiBuff/ModiBuff.Tests/ModifierTagsTests.cs | 4 +- .../TestModifierInheritanceRecipes.cs | 2 +- .../ModiBuff.Units/TestModifierRecipes.cs | 2 +- ModiBuff/ModiBuff.Units/Unit/Unit.cs | 11 +++ .../Components/Main/DurationTickComponent.cs | 72 +++++++++++++++ .../Main/Interfaces/ITimeComponent.cs | 35 ++++++++ .../Components/Main/IntervalTickComponent.cs | 76 ++++++++++++++++ .../Creation/Generation/ModifierGenerator.cs | 34 ++++++- .../Core/Modifier/Creation/Recipe/EffectOn.cs | 2 + .../Creation/Recipe/ModifierEffectsCreator.cs | 38 ++++++-- .../Creation/Recipe/ModifierRecipe.cs | 90 ++++++++++++++----- .../Creation/Recipe/ModifierRecipeData.cs | 19 ++-- .../Creation/Recipe/ModifierRecipeSaveLoad.cs | 62 +++++++++++-- .../Modifier/Creation/Recipe/RefreshType.cs | 4 +- ModiBuff/ModiBuff/Core/Modifier/IData.cs | 4 + .../Core/Modifier/IModifierDataReference.cs | 3 + ModiBuff/ModiBuff/Core/Modifier/Modifier.cs | 88 +++++++++++++++--- .../Core/Modifier/ModifierController.cs | 22 +++++ 25 files changed, 730 insertions(+), 60 deletions(-) create mode 100644 ModiBuff/ModiBuff.Tests/IntervalTests.cs create mode 100644 ModiBuff/ModiBuff/Core/Modifier/Components/Main/DurationTickComponent.cs create mode 100644 ModiBuff/ModiBuff/Core/Modifier/Components/Main/IntervalTickComponent.cs diff --git a/ModiBuff/ModiBuff.Benchmarks/BenchmarkModifierRecipes.cs b/ModiBuff/ModiBuff.Benchmarks/BenchmarkModifierRecipes.cs index 69e5555e..0b637fef 100644 --- a/ModiBuff/ModiBuff.Benchmarks/BenchmarkModifierRecipes.cs +++ b/ModiBuff/ModiBuff.Benchmarks/BenchmarkModifierRecipes.cs @@ -23,7 +23,7 @@ protected override void SetupRecipes() { var initComponent = new InitComponent(new IEffect[] { new DamageEffect(5) }, null); - var modifier = new Modifier(id, genId, name, initComponent, null, null, null, + var modifier = new Modifier(id, genId, name, initComponent, null, null, null, null, new SingleTargetComponent(), null, null, null); return modifier; diff --git a/ModiBuff/ModiBuff.Tests/AddModifierWithDataTests.cs b/ModiBuff/ModiBuff.Tests/AddModifierWithDataTests.cs index 004b5499..7d6fce4c 100644 --- a/ModiBuff/ModiBuff.Tests/AddModifierWithDataTests.cs +++ b/ModiBuff/ModiBuff.Tests/AddModifierWithDataTests.cs @@ -128,6 +128,30 @@ public void AddWithData_CustomInterval() Assert.AreEqual(UnitHealth - 5 - 5, Unit.Health); } + [Test] + public void AddWithData_CustomIntervalTick() + { + AddRecipe("IntervalTickDamage") + .IntervalTick(1) + .Effect(new DamageEffect(5), EffectOn.IntervalTick); + Setup(); + + IData[] data = + { + new ModifierIntervalTickData(2) + }; + Unit.AddModifierWithDataSelf("IntervalTickDamage", data); + + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth - 5, Unit.Health); + + Unit.UpdateTicks(2); + Assert.AreEqual(UnitHealth - 5 - 5, Unit.Health); + } + [Test] public void AddWithData_CustomDuration() { @@ -149,6 +173,27 @@ public void AddWithData_CustomDuration() Assert.AreEqual(UnitHealth - 5, Unit.Health); } + [Test] + public void AddWithData_CustomDurationTick() + { + AddRecipe("DurationTickDamage") + .DurationTick(2) + .Effect(new DamageEffect(5), EffectOn.DurationTick); + Setup(); + + IData[] data = + { + new ModifierDurationTickData(3) + }; + Unit.AddModifierWithDataSelf("DurationTickDamage", data); + + Unit.UpdateTicks(2); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.UpdateTicks(1); + Assert.AreEqual(UnitHealth - 5, Unit.Health); + } + [Test] public void AddWithData_StartingStacks() { diff --git a/ModiBuff/ModiBuff.Tests/CooldownTests.cs b/ModiBuff/ModiBuff.Tests/CooldownTests.cs index 969009e7..dd09d062 100644 --- a/ModiBuff/ModiBuff.Tests/CooldownTests.cs +++ b/ModiBuff/ModiBuff.Tests/CooldownTests.cs @@ -105,7 +105,7 @@ public void InitDamage_CooldownLowerWhenStunned_Manual() var damageEffect = new DamageEffect(5); var initComponent = new InitComponent(new IEffect[] { callback, damageEffect }, check); - return new Modifier(id, genId, name, initComponent, null, null, check, new SingleTargetComponent(), + return new Modifier(id, genId, name, initComponent, null, null, null, check, new SingleTargetComponent(), null, null, null); }); Setup(); diff --git a/ModiBuff/ModiBuff.Tests/DurationTests.cs b/ModiBuff/ModiBuff.Tests/DurationTests.cs index a8b27ccc..35b6d91c 100644 --- a/ModiBuff/ModiBuff.Tests/DurationTests.cs +++ b/ModiBuff/ModiBuff.Tests/DurationTests.cs @@ -16,8 +16,27 @@ public void Duration_Damage() Unit.AddModifierSelf("DurationDamage"); + Unit.UpdateTicks(5); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.Update(5); + Assert.AreEqual(UnitHealth - 5, Unit.Health); + } + + [Test] + public void DurationTick_Damage() + { + AddRecipe("DurationTickDamage") + .Effect(new DamageEffect(5), EffectOn.DurationTick) + .DurationTick(5); + Setup(); + + Unit.AddModifierSelf("DurationTickDamage"); + Unit.Update(5); + Assert.AreEqual(UnitHealth, Unit.Health); + Unit.UpdateTicks(5); Assert.AreEqual(UnitHealth - 5, Unit.Health); } @@ -35,6 +54,20 @@ public void Duration_Remove() Assert.False(Unit.ContainsModifier("DurationRemove")); } + [Test] + public void DurationTick_Remove() + { + AddRecipe("DurationTickRemove") + .RemoveTicks(5); + Setup(); + + Unit.AddModifierSelf("DurationTickRemove"); + + Unit.UpdateTicks(5); + + Assert.False(Unit.ContainsModifier("DurationTickRemove")); + } + [Test] public void Duration_Damage_Once() { @@ -54,6 +87,25 @@ public void Duration_Damage_Once() Assert.AreEqual(UnitHealth - 5, Unit.Health); } + [Test] + public void DurationTick_Damage_Once() + { + AddRecipe("DurationTickDamage") + .Effect(new DamageEffect(5), EffectOn.DurationTick) + .DurationTick(5); + Setup(); + + Unit.AddModifierSelf("DurationTickDamage"); + + Unit.UpdateTicks(5); + + Assert.AreEqual(UnitHealth - 5, Unit.Health); + + Unit.UpdateTicks(5); + + Assert.AreEqual(UnitHealth - 5, Unit.Health); + } + [Test] public void TwoModifiersSameDurationRemove() { @@ -71,5 +123,43 @@ public void TwoModifiersSameDurationRemove() Assert.False(Unit.ContainsModifier("DurationRemove")); Assert.False(Unit.ContainsModifier("DurationRemove2")); } + + [Test] + public void TwoModifiersSameDurationTickRemove() + { + AddRecipe("DurationTickRemove") + .RemoveTicks(5); + AddRecipe("DurationTickRemove2") + .RemoveTicks(5); + Setup(); + + Unit.AddModifierSelf("DurationTickRemove"); + Unit.AddModifierSelf("DurationTickRemove2"); + + Unit.UpdateTicks(5); + + Assert.False(Unit.ContainsModifier("DurationTickRemove")); + Assert.False(Unit.ContainsModifier("DurationTickRemove2")); + } + + [Test] + public void MixedDurationAndDurationTickRemove() + { + AddRecipe("DurationMixedRemove") + .Remove(5) + .RemoveTicks(10); + Setup(); + + Unit.AddModifierSelf("DurationMixedRemove"); + + Unit.Update(4f); + Unit.UpdateTicks(9); + + Assert.True(Unit.ContainsModifier("DurationMixedRemove")); + + Unit.UpdateTicks(1); + + Assert.False(Unit.ContainsModifier("DurationMixedRemove")); + } } } \ No newline at end of file diff --git a/ModiBuff/ModiBuff.Tests/IntervalTests.cs b/ModiBuff/ModiBuff.Tests/IntervalTests.cs new file mode 100644 index 00000000..afdaed31 --- /dev/null +++ b/ModiBuff/ModiBuff.Tests/IntervalTests.cs @@ -0,0 +1,79 @@ +using ModiBuff.Core; +using ModiBuff.Core.Units; +using NUnit.Framework; + +namespace ModiBuff.Tests +{ + public sealed class IntervalTests : ModifierTests + { + [Test] + public void IntervalTick_Damage() + { + AddRecipe("IntervalTick_Damage") + .Effect(new DamageEffect(5), EffectOn.IntervalTick) + .IntervalTick(2); + Setup(); + + Unit.AddModifierSelf("IntervalTick_Damage"); + + Unit.Update(1); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth - 5, Unit.Health); + + Unit.UpdateTick(); + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth - 10, Unit.Health); + } + + [Test] + public void IntervalTimeTick_Damage() + { + AddRecipe("IntervalTimeTick_Damage") + .Effect(new DamageEffect(5), EffectOn.Interval) + .Interval(2) + .Effect(new DamageEffect(5), EffectOn.IntervalTick) + .IntervalTick(2); + Setup(); + + Unit.AddModifierSelf("IntervalTimeTick_Damage"); + + Unit.Update(1); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth - 5, Unit.Health); + + Unit.Update(1); + Assert.AreEqual(UnitHealth - 10, Unit.Health); + } + + [Test] + public void IntervalTick_Refresh_Damage() + { + AddRecipe("IntervalTick_Refresh_Damage") + .Effect(new DamageEffect(5), EffectOn.Interval) + .Interval(2) + .Effect(new DamageEffect(5), EffectOn.IntervalTick) + .IntervalTick(2).Refresh(); + Setup(); + + Unit.AddModifierSelf("IntervalTick_Refresh_Damage"); + Unit.Update(1); + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth, Unit.Health); + + Unit.AddModifierSelf("IntervalTick_Refresh_Damage"); + Unit.Update(1); + Unit.UpdateTick(); + Assert.AreEqual(UnitHealth - 5, Unit.Health); + } + } +} \ No newline at end of file diff --git a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs index 5cdd1743..766b2781 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs @@ -81,7 +81,7 @@ public void LegalActionUnitTypeGenerator() var addDamageEffect = new AddDamageEffect(5); var initComponent = new InitComponent(new IEffect[] { addDamageEffect }, null); - return new Modifier(id, genId, name, initComponent, null, null, null, + return new Modifier(id, genId, name, initComponent, null, null, null, null, new SingleTargetComponent(), null, new EffectStateInfo((EffectOn.Init, addDamageEffect)), null); }, Core.Units.TagType.Default, customModifierData: new AddModifierCommonData(ModifierAddType.Self, enemyType)); diff --git a/ModiBuff/ModiBuff.Tests/ModifierStateInfoTest.cs b/ModiBuff/ModiBuff.Tests/ModifierStateInfoTest.cs index dd5775a7..543a6048 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierStateInfoTest.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierStateInfoTest.cs @@ -27,7 +27,7 @@ public void InitDamage_CorrectBaseDamage_Manual() var damageEffect = new DamageEffect(5); var initComponent = new InitComponent(new IEffect[] { damageEffect }, null); - return new Modifier(id, genId, name, initComponent, null, null, null, + return new Modifier(id, genId, name, initComponent, null, null, null, null, new SingleTargetComponent(), null, new EffectStateInfo((EffectOn.Init, damageEffect)), null); }); Setup(); diff --git a/ModiBuff/ModiBuff.Tests/ModifierTagsTests.cs b/ModiBuff/ModiBuff.Tests/ModifierTagsTests.cs index 1a556f23..c49e890e 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierTagsTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierTagsTests.cs @@ -105,7 +105,7 @@ public void AutomaticTimeComponentTagging() new IntervalComponent(1f, true, new IEffect[] { new NoOpEffect() }, null) }; - return new Modifier(id, genId, name, null, timeComponents, null, null, new SingleTargetComponent(), + return new Modifier(id, genId, name, null, timeComponents, null, null, null, new SingleTargetComponent(), null, null, null); }); AddGenerator("DurationRefreshDamage", (id, genId, name, tag) => @@ -115,7 +115,7 @@ public void AutomaticTimeComponentTagging() new DurationComponent(1f, true, new IEffect[] { new NoOpEffect() }) }; - return new Modifier(id, genId, name, null, timeComponents, null, null, new SingleTargetComponent(), + return new Modifier(id, genId, name, null, timeComponents, null, null, null, new SingleTargetComponent(), null, null, null); }); Setup(); diff --git a/ModiBuff/ModiBuff.Units/TestModifierInheritanceRecipes.cs b/ModiBuff/ModiBuff.Units/TestModifierInheritanceRecipes.cs index adcc7d16..79cfabc6 100644 --- a/ModiBuff/ModiBuff.Units/TestModifierInheritanceRecipes.cs +++ b/ModiBuff/ModiBuff.Units/TestModifierInheritanceRecipes.cs @@ -24,7 +24,7 @@ protected override void SetupRecipes() { var initComponent = new InitComponent(new IEffect[] { new DamageEffect(5) }, null); - var modifier = new Modifier(id, genId, name, initComponent, null, null, null, + var modifier = new Modifier(id, genId, name, initComponent, null, null, null, null, new SingleTargetComponent(), null, null, null); return modifier; diff --git a/ModiBuff/ModiBuff.Units/TestModifierRecipes.cs b/ModiBuff/ModiBuff.Units/TestModifierRecipes.cs index cae670f1..a52e9563 100644 --- a/ModiBuff/ModiBuff.Units/TestModifierRecipes.cs +++ b/ModiBuff/ModiBuff.Units/TestModifierRecipes.cs @@ -32,7 +32,7 @@ private void SetupRecipes() { var initComponent = new InitComponent(new IEffect[] { new DamageEffect(5) }, null); - var modifier = new Modifier(id, genId, name, initComponent, null, null, null, + var modifier = new Modifier(id, genId, name, initComponent, null, null, null, null, new SingleTargetComponent(), null, null, null); return modifier; diff --git a/ModiBuff/ModiBuff.Units/Unit/Unit.cs b/ModiBuff/ModiBuff.Units/Unit/Unit.cs index d96fe34f..cbd1d0c2 100644 --- a/ModiBuff/ModiBuff.Units/Unit/Unit.cs +++ b/ModiBuff/ModiBuff.Units/Unit/Unit.cs @@ -156,6 +156,17 @@ public void Update(float deltaTime) } } + internal void UpdateTicks(int count) + { + for (int i = 0; i < count; i++) + UpdateTick(); + } + + public void UpdateTick() + { + ModifierController.UpdateTick(); + } + /// /// Should be called before we attack/on attack. For modifiers like split shot that we want to trigger when starting an attack. /// diff --git a/ModiBuff/ModiBuff/Core/Modifier/Components/Main/DurationTickComponent.cs b/ModiBuff/ModiBuff/Core/Modifier/Components/Main/DurationTickComponent.cs new file mode 100644 index 00000000..91ca7c8b --- /dev/null +++ b/ModiBuff/ModiBuff/Core/Modifier/Components/Main/DurationTickComponent.cs @@ -0,0 +1,72 @@ +namespace ModiBuff.Core +{ + public sealed class DurationTickComponent : ITimeTickComponent + { + public float CurrentTicks => _ticks; + public float Ticks => _tickDuration; + + private readonly int _tickDuration; + private readonly bool _isRefreshable; + private int _ticks; + + private ITargetComponent _targetComponent; + + private readonly IEffect[] _effects; + + private int? _customDuration; + + public DurationTickComponent(int tickDuration, bool refreshable, IEffect[] effects) + { + _tickDuration = tickDuration; + _isRefreshable = refreshable; + _effects = effects; + } + + public void SetupTarget(ITargetComponent targetComponent) => _targetComponent = targetComponent; + + public void Update() + { + var tickDuration = _customDuration ?? _tickDuration; + if (_ticks >= tickDuration) + return; + + _ticks++; + + if (_ticks < tickDuration) + return; + + switch (_targetComponent) + { + case MultiTargetComponent targetComponent: + for (int i = 0; i < _effects.Length; i++) + _effects[i].Effect(targetComponent.Targets, targetComponent.Source); + break; + case SingleTargetComponent targetComponent: + for (int i = 0; i < _effects.Length; i++) + _effects[i].Effect(targetComponent.Target, targetComponent.Source); + break; + } + } + + public void Refresh() + { + if (_isRefreshable) + _ticks = 0; + } + + public void SetData(ModifierData data) + { + if (data is ModifierDurationTickData durationTickData) + _customDuration = durationTickData.DurationTick; + } + + public void ResetState() => _ticks = 0; + + public TimeTickComponentSaveData SaveState() => new TimeTickComponentSaveData(_ticks, _customDuration); + public void LoadState(TimeTickComponentSaveData saveData) + { + _ticks = saveData.CurrentTicks; + _customDuration = saveData.CustomTicks; + } + } +} \ No newline at end of file diff --git a/ModiBuff/ModiBuff/Core/Modifier/Components/Main/Interfaces/ITimeComponent.cs b/ModiBuff/ModiBuff/Core/Modifier/Components/Main/Interfaces/ITimeComponent.cs index f361f8a6..a3aff770 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Components/Main/Interfaces/ITimeComponent.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Components/Main/Interfaces/ITimeComponent.cs @@ -10,6 +10,16 @@ public interface ITimeComponent : IStateReset, ITarget, ITimeReference void LoadState(TimeComponentSaveData saveData); } + public interface ITimeTickComponent : IStateReset, ITarget, ITimeTickReference + { + void Update(); + void Refresh(); + void SetData(ModifierData data); + + TimeTickComponentSaveData SaveState(); + void LoadState(TimeTickComponentSaveData saveData); + } + public readonly struct TimeComponentSaveData { public readonly float Timer; @@ -25,6 +35,21 @@ public TimeComponentSaveData(float timer, float? customTime) } } + public readonly struct TimeTickComponentSaveData + { + public readonly int CurrentTicks; + public readonly int? CustomTicks; + +#if MODIBUFF_SYSTEM_TEXT_JSON + [System.Text.Json.Serialization.JsonConstructor] +#endif + public TimeTickComponentSaveData(int currentTicks, int? customTicks) + { + CurrentTicks = currentTicks; + CustomTicks = customTicks; + } + } + public interface ITimeReference { /// @@ -37,4 +62,14 @@ public interface ITimeReference /// float Time { get; } } + + public interface ITimeTickReference + { + float CurrentTicks { get; } + + /// + /// Duration/Interval of the timer + /// + float Ticks { get; } + } } \ No newline at end of file diff --git a/ModiBuff/ModiBuff/Core/Modifier/Components/Main/IntervalTickComponent.cs b/ModiBuff/ModiBuff/Core/Modifier/Components/Main/IntervalTickComponent.cs new file mode 100644 index 00000000..35592622 --- /dev/null +++ b/ModiBuff/ModiBuff/Core/Modifier/Components/Main/IntervalTickComponent.cs @@ -0,0 +1,76 @@ +namespace ModiBuff.Core +{ + public sealed class IntervalTickComponent : ITimeTickComponent + { + public float CurrentTicks => _ticks; + public float Ticks => _tickInterval; + + private readonly int _tickInterval; + private readonly bool _isRefreshable; + private int _ticks; + + private ITargetComponent _targetComponent; + + private readonly IEffect[] _effects; + + private readonly ModifierCheck? _modifierCheck; + + private int? _customInterval; + + public IntervalTickComponent(int tickInterval, bool refreshable, IEffect[] effects, ModifierCheck? check) + { + _tickInterval = tickInterval; + _isRefreshable = refreshable; + _effects = effects; + _modifierCheck = check; + } + + public void SetupTarget(ITargetComponent targetComponent) => _targetComponent = targetComponent; + + public void Update() + { + _ticks++; + + if (_ticks < (_customInterval ?? _tickInterval)) + return; + + _ticks = 0; + + if (_modifierCheck?.CheckUse(_targetComponent.Source) == false) + return; + + switch (_targetComponent) + { + case MultiTargetComponent multiTargetComponent: + for (int i = 0; i < _effects.Length; i++) + _effects[i].Effect(multiTargetComponent.Targets, multiTargetComponent.Source); + break; + case SingleTargetComponent singleTargetComponent: + for (int i = 0; i < _effects.Length; i++) + _effects[i].Effect(singleTargetComponent.Target, singleTargetComponent.Source); + break; + } + } + + public void Refresh() + { + if (_isRefreshable) + _ticks = 0; + } + + public void SetData(ModifierData data) + { + if (data is ModifierIntervalTickData intervalTickData) + _customInterval = intervalTickData.IntervalTick; + } + + public void ResetState() => _ticks = 0; + + public TimeTickComponentSaveData SaveState() => new TimeTickComponentSaveData(_ticks, _customInterval); + public void LoadState(TimeTickComponentSaveData saveData) + { + _ticks = saveData.CurrentTicks; + _customInterval = saveData.CustomTicks; + } + } +} \ No newline at end of file diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ModifierGenerator.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ModifierGenerator.cs index bfe5ad41..6203ee71 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ModifierGenerator.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ModifierGenerator.cs @@ -16,10 +16,14 @@ public sealed class ModifierGenerator : IModifierGenerator, IModifierApplyCheckG private readonly bool _isAura; private readonly float _interval; + private readonly int _ticksInterval; private readonly float _duration; + private readonly int _ticksDuration; - private readonly bool _refreshDuration; private readonly bool _refreshInterval; + private readonly bool _refreshIntervalTick; + private readonly bool _refreshDuration; + private readonly bool _refreshDurationTick; private readonly WhenStackEffect? _whenStackEffect; private readonly int? _maxStacks; @@ -29,6 +33,8 @@ public sealed class ModifierGenerator : IModifierGenerator, IModifierApplyCheckG private readonly int _timeComponentCount; private int _timeComponentIndex; + private readonly int _timeTickComponentCount; + private int _timeTickComponentIndex; private readonly ModifierEffectsCreator _modifierEffectsCreator; @@ -65,9 +71,13 @@ public ModifierGenerator(in ModifierRecipeData data) _isAura = data.IsAura; var tag = data.Tag; _interval = data.Interval; + _ticksInterval = data.TicksInterval; _duration = data.Duration; - _refreshDuration = data.RefreshDuration; + _ticksDuration = data.TicksDuration; _refreshInterval = data.RefreshInterval; + _refreshIntervalTick = data.RefreshIntervalTick; + _refreshDuration = data.RefreshDuration; + _refreshDurationTick = data.RefreshDurationTick; _whenStackEffect = data.WhenStackEffect; _maxStacks = data.MaxStacks; _everyXStacks = data.EveryXStacks; @@ -76,8 +86,12 @@ public ModifierGenerator(in ModifierRecipeData data) if (data.Interval > 0) _timeComponentCount++; + if (data.TicksInterval > 0) + _timeTickComponentCount++; if (data.Duration > 0) _timeComponentCount++; + if (data.TicksDuration > 0) + _timeTickComponentCount++; _modifierEffectsCreator = new ModifierEffectsCreator(data.EffectWrappers, data.RemoveEffectWrapper, data.DispelRegisterWrapper, data.CallbackUnitRegisterWrappers, data.CallbackEffectRegisterWrappers, @@ -154,12 +168,18 @@ Modifier IModifierGenerator.Create() InitComponent? initComponent = null; ITimeComponent[]? timeComponents = null; + ITimeTickComponent[]? timeTickComponents = null; StackComponent? stackComponent = null; if (_timeComponentCount > 0) { _timeComponentIndex = 0; timeComponents = new ITimeComponent[_timeComponentCount]; } + if (_timeTickComponentCount > 0) + { + _timeTickComponentIndex = 0; + timeTickComponents = new ITimeTickComponent[_timeTickComponentCount]; + } var effects = _modifierEffectsCreator.Create(genId); @@ -170,17 +190,25 @@ Modifier IModifierGenerator.Create() timeComponents![_timeComponentIndex++] = new IntervalComponent(_interval, _refreshInterval, effects.IntervalEffects, effectCheck); + if (effects.IntervalTickEffects != null) + timeTickComponents![_timeTickComponentIndex++] = new IntervalTickComponent(_ticksInterval, _refreshIntervalTick, + effects.IntervalTickEffects, effectCheck); + if (effects.DurationEffects != null) timeComponents![_timeComponentIndex++] = new DurationComponent(_duration, _refreshDuration, effects.DurationEffects); + if (effects.DurationTickEffects != null) + timeTickComponents![_timeTickComponentIndex++] = new DurationTickComponent(_ticksDuration, _refreshDurationTick, + effects.DurationTickEffects); + if (effects.StackEffects != null) stackComponent = new StackComponent(_whenStackEffect!.Value, _maxStacks, _everyXStacks, _singleStackTime, _independentStackTime, effects.StackEffects, effectCheck); var targetComponent = !_isAura ? (ITargetComponent)new SingleTargetComponent() : new MultiTargetComponent(); - return new Modifier(Id, genId, Name, initComponent, timeComponents, stackComponent, + return new Modifier(Id, genId, Name, initComponent, timeComponents, timeTickComponents, stackComponent, effectCheck, targetComponent, effects.SetDataEffects, effects.EffectStateInfo, effects.EffectSaveState); } diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/EffectOn.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/EffectOn.cs index 1258779c..0b5273e8 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/EffectOn.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/EffectOn.cs @@ -8,7 +8,9 @@ public enum EffectOn None = 0, Init = 1, Interval = 2, + IntervalTick = 65536, Duration = 4, + DurationTick = 131072, Stack = 8, CallbackUnit = 16, CallbackEffect = 32, diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierEffectsCreator.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierEffectsCreator.cs index 244383fc..d2569981 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierEffectsCreator.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierEffectsCreator.cs @@ -20,7 +20,9 @@ public sealed class ModifierEffectsCreator private ISetDataEffect[] _setDataEffects; private IEffect[] _initEffects; private IEffect[] _intervalEffects; + private IEffect[] _intervalTickEffects; private IEffect[] _durationEffects; + private IEffect[] _durationTickEffects; private IStackEffect[] _stackEffects; private IEffect[][] _callbackUnitEffects; private IEffect[][] _callbackEffectEffects; @@ -30,7 +32,9 @@ public sealed class ModifierEffectsCreator _setDataEffectsIndex, _initEffectsIndex, _intervalEffectsIndex, + _intervalTickEffectsIndex, _durationEffectsIndex, + _durationTickEffectsIndex, _stackEffectsIndex, _callbackUnitEffectsIndex, _callbackEffectEffectsIndex, @@ -77,8 +81,12 @@ public ModifierEffectsCreator(List effectWrappers, EffectWrapper? _initEffectsIndex++; if ((effectWrapper.EffectOn & EffectOn.Interval) != 0) _intervalEffectsIndex++; + if ((effectWrapper.EffectOn & EffectOn.IntervalTick) != 0) + _intervalTickEffectsIndex++; if ((effectWrapper.EffectOn & EffectOn.Duration) != 0) _durationEffectsIndex++; + if ((effectWrapper.EffectOn & EffectOn.DurationTick) != 0) + _durationTickEffectsIndex++; if ((effectWrapper.EffectOn & EffectOn.Stack) != 0) _stackEffectsIndex++; if ((effectWrapper.EffectOn & EffectOn.CallbackUnit) != 0) @@ -126,12 +134,24 @@ public SyncedModifierEffects Create(int genId) _intervalEffectsIndex = 0; } + if (_intervalTickEffectsIndex > 0) + { + _intervalTickEffects = new IEffect[_intervalTickEffectsIndex]; + _intervalTickEffectsIndex = 0; + } + if (_durationEffectsIndex > 0) { _durationEffects = new IEffect[_durationEffectsIndex]; _durationEffectsIndex = 0; } + if (_durationTickEffectsIndex > 0) + { + _durationTickEffects = new IEffect[_durationTickEffectsIndex]; + _durationTickEffectsIndex = 0; + } + if (_stackEffectsIndex > 0) { _stackEffects = new IStackEffect[_stackEffectsIndex]; @@ -253,8 +273,12 @@ public SyncedModifierEffects Create(int genId) _initEffects[_initEffectsIndex++] = effect; if ((effectOn & EffectOn.Interval) != 0) _intervalEffects[_intervalEffectsIndex++] = effect; + if ((effectOn & EffectOn.IntervalTick) != 0) + _intervalTickEffects[_intervalTickEffectsIndex++] = effect; if ((effectOn & EffectOn.Duration) != 0) _durationEffects[_durationEffectsIndex++] = effect; + if ((effectOn & EffectOn.DurationTick) != 0) + _durationTickEffects[_durationTickEffectsIndex++] = effect; if ((effectOn & EffectOn.Stack) != 0) _stackEffects[_stackEffectsIndex++] = (IStackEffect)effect; if ((effectOn & EffectOn.CallbackUnit) != 0) @@ -328,8 +352,8 @@ public SyncedModifierEffects Create(int genId) for (int i = 0; i < _effectWrappers.Length; i++) _effectWrappers[i].Reset(); - return new SyncedModifierEffects(_initEffects, _intervalEffects, _durationEffects, _stackEffects, - _setDataEffects, effectStateInfo, effectSaveState); + return new SyncedModifierEffects(_initEffects, _intervalEffects, _intervalTickEffects, _durationEffects, _durationTickEffects, + _stackEffects, _setDataEffects, effectStateInfo, effectSaveState); } } @@ -337,20 +361,24 @@ public readonly ref struct SyncedModifierEffects { public readonly IEffect[]? InitEffects; public readonly IEffect[]? IntervalEffects; + public readonly IEffect[]? IntervalTickEffects; public readonly IEffect[]? DurationEffects; + public readonly IEffect[]? DurationTickEffects; public readonly IStackEffect[]? StackEffects; public readonly ISetDataEffect[]? SetDataEffects; public readonly EffectStateInfo? EffectStateInfo; public readonly EffectSaveState? EffectSaveState; - public SyncedModifierEffects(IEffect[]? initEffectsArray, IEffect[]? intervalEffectsArray, - IEffect[]? durationEffectsArray, IStackEffect[]? stackEffectsArray, ISetDataEffect[] dataEffects, - EffectStateInfo? effectStateInfo, EffectSaveState? effectSaveState) + public SyncedModifierEffects(IEffect[]? initEffectsArray, IEffect[]? intervalEffectsArray, IEffect[]? intervalTickEffectsArray, + IEffect[]? durationEffectsArray, IEffect[]? durationTickEffectsArray, IStackEffect[]? stackEffectsArray, + ISetDataEffect[]? dataEffects, EffectStateInfo? effectStateInfo, EffectSaveState? effectSaveState) { SetDataEffects = dataEffects; InitEffects = initEffectsArray; IntervalEffects = intervalEffectsArray; + IntervalTickEffects = intervalTickEffectsArray; DurationEffects = durationEffectsArray; + DurationTickEffects = durationTickEffectsArray; StackEffects = stackEffectsArray; EffectStateInfo = effectStateInfo; EffectSaveState = effectSaveState; diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs index 1e7568fc..ec41b9b2 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs @@ -24,9 +24,11 @@ public sealed partial class ModifierRecipe : IModifierRecipe, IEquatable _effectWrappers; - private bool _refreshDuration, _refreshInterval; + private bool _refreshInterval, _refreshIntervalTick, _refreshDuration, _refreshDurationTick; private WhenStackEffect? _whenStackEffect; private int? _maxStacks; @@ -182,11 +184,22 @@ public ModifierRecipe EffectCheck(ICheck check) public ModifierRecipe Interval(float interval) { _interval = interval; - _currentIsInterval = true; + _lastSetRefreshType = RefreshType.Interval; _saveInstructions.Add(new SaveInstruction.Interval(interval)); return this; } + /// + /// How many ticks should pass between the interval effects get applied. + /// + public ModifierRecipe IntervalTick(int ticksInterval) + { + _ticksInterval = ticksInterval; + _lastSetRefreshType = RefreshType.IntervalTick; + //_saveInstructions.Add(new SaveInstruction.IntervalTick(ticksInterval)); //TODO + return this; + } + /// /// How many seconds should pass before the duration effects get triggered (usually modifier removal) /// @@ -197,6 +210,13 @@ public ModifierRecipe Duration(float duration) return this; } + public ModifierRecipe DurationTick(int ticksDuration) + { + DurationTickInternal(ticksDuration); + //_saveInstructions.Add(new SaveInstruction.DurationTick(ticksDuration)); //TODO + return this; + } + /// /// How many seconds should pass before the modifier gets removed. /// @@ -205,7 +225,16 @@ public ModifierRecipe Remove(float duration) DurationInternal(duration); AddRemoveEffect(EffectOn.Duration); _saveInstructions.Add(new SaveInstruction.Remove(SaveInstruction.Remove.Type.Duration, EffectOn.Duration, - duration)); + duration, null)); + return this; + } + + public ModifierRecipe RemoveTicks(int ticksDuration) + { + DurationTickInternal(ticksDuration); + AddRemoveEffect(EffectOn.DurationTick); + _saveInstructions.Add(new SaveInstruction.Remove(SaveInstruction.Remove.Type.DurationTick, EffectOn.Duration, + null, ticksDuration)); return this; } @@ -228,7 +257,7 @@ public ModifierRecipe Remove(RemoveEffectOn removeEffectOn) { AddRemoveEffect(removeEffectOn.ToEffectOn()); _saveInstructions.Add(new SaveInstruction.Remove(SaveInstruction.Remove.Type.RemoveOn, - removeEffectOn.ToEffectOn())); + removeEffectOn.ToEffectOn(), null, null)); return this; } @@ -260,14 +289,7 @@ public ModifierRecipe Refresh() return this; } - if (_currentIsInterval) - { - Refresh(RefreshType.Interval); - return this; - } - - Refresh(RefreshType.Duration); - return this; + return Refresh(_lastSetRefreshType!.Value); } /// @@ -278,11 +300,17 @@ public ModifierRecipe Refresh(RefreshType type) { switch (type) { + case RefreshType.Interval: + _refreshInterval = true; + break; + case RefreshType.IntervalTick: + _refreshIntervalTick = true; + break; case RefreshType.Duration: _refreshDuration = true; break; - case RefreshType.Interval: - _refreshInterval = true; + case RefreshType.DurationTick: + _refreshDurationTick = true; break; } @@ -384,7 +412,7 @@ public ModifierRecipe Effect(IEffect effect, EffectOn effectOn) "use Remove(RemoveEffectOn) or Remove(float) instead"); #endif AddRemoveEffect(effectOn); - _saveInstructions.Add(new SaveInstruction.Remove(SaveInstruction.Remove.Type.RemoveOn, effectOn)); + _saveInstructions.Add(new SaveInstruction.Remove(SaveInstruction.Remove.Type.RemoveOn, effectOn, null, null)); return this; } @@ -547,7 +575,7 @@ public IModifierGenerator CreateModifierGenerator() } } - if (_refreshDuration || _refreshInterval) + if (_refreshInterval || _refreshIntervalTick || _refreshDuration || _refreshDurationTick) _tag |= TagType.IsRefresh; if (_isInstanceStackable) _tag |= TagType.IsInstanceStackable; @@ -560,8 +588,9 @@ public IModifierGenerator CreateModifierGenerator() _dispelRegisterWrapper, _callbackUnitRegisterWrappers.ToArray(), _callbackEffectRegisterWrappers.ToArray(), _callbackEffectUnitsRegisterWrappers.ToArray(), _hasApplyChecks, _applyCheckList, _hasEffectChecks, _effectCheckList, _applyFuncCheckList, - _effectFuncCheckList, _isAura, _tag, _interval, _duration, _refreshDuration, _refreshInterval, - _whenStackEffect, _maxStacks, _everyXStacks, _singleStackTime, _independentStackTime); + _effectFuncCheckList, _isAura, _tag, _interval, _ticksInterval, _duration, _ticksDuration, + _refreshInterval, _refreshIntervalTick, _refreshDuration, _refreshDurationTick, _whenStackEffect, + _maxStacks, _everyXStacks, _singleStackTime, _independentStackTime); return new ModifierGenerator(in data); } @@ -579,7 +608,14 @@ public ModifierInfo CreateModifierInfo() private ModifierRecipe DurationInternal(float duration) { _duration = duration; - _currentIsInterval = false; + _lastSetRefreshType = RefreshType.Duration; + return this; + } + + private ModifierRecipe DurationTickInternal(int ticksDuration) + { + _ticksDuration = ticksDuration; + _lastSetRefreshType = RefreshType.DurationTick; return this; } @@ -635,6 +671,13 @@ private void Validate() + Name + " id: " + Id); } + if (_refreshIntervalTick && _ticksInterval == 0) + { + validRecipe = false; + Logger.LogError("[ModiBuff] Refresh interval tick set, but ticks interval is 0, for modifier: " + + Name + " id: " + Id); + } + if (_refreshDuration && _duration == 0) { validRecipe = false; @@ -642,6 +685,13 @@ private void Validate() + Name + " id: " + Id); } + if (_refreshDurationTick && _ticksDuration == 0) + { + validRecipe = false; + Logger.LogError("[ModiBuff] Refresh duration tick set, but ticks duration is 0, for modifier: " + + Name + " id: " + Id); + } + ValidateCallbacks(EffectOnCallbackEffectData.AllCallbackUnitData, _callbackUnitRegisterWrappers); ValidateCallbacks(EffectOnCallbackEffectData.AllCallbackEffectData, _callbackEffectRegisterWrappers); ValidateCallbacks(EffectOnCallbackEffectData.AllCallbackEffectUnitsData, diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeData.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeData.cs index 66996ce3..e8634cbc 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeData.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeData.cs @@ -22,9 +22,13 @@ public readonly struct ModifierRecipeData public readonly bool IsAura; public readonly TagType Tag; public readonly float Interval; + public readonly int TicksInterval; public readonly float Duration; - public readonly bool RefreshDuration; + public readonly int TicksDuration; public readonly bool RefreshInterval; + public readonly bool RefreshIntervalTick; + public readonly bool RefreshDuration; + public readonly bool RefreshDurationTick; public readonly WhenStackEffect? WhenStackEffect; public readonly int? MaxStacks; public readonly int? EveryXStacks; @@ -36,9 +40,10 @@ public ModifierRecipeData(int id, string name, List effectWrapper EffectWrapper[] callbackUnitRegisterWrappers, EffectWrapper[] callbackEffectRegisterWrappers, EffectWrapper[] callbackEffectUnitsRegisterWrappers, bool hasApplyChecks, List? applyCheckList, bool hasEffectChecks, List? effectCheckList, List>? applyFuncCheckList, - List>? effectFuncCheckList, bool isAura, TagType tag, float interval, - float duration, bool refreshDuration, bool refreshInterval, WhenStackEffect? whenStackEffect, - int? maxStacks, int? everyXStacks, float? singleStackTime, float? independentStackTime) + List>? effectFuncCheckList, bool isAura, TagType tag, float interval, int ticksInterval, + float duration, int ticksDuration, bool refreshInterval, bool refreshIntervalTick, bool refreshDuration, + bool refreshDurationTick, WhenStackEffect? whenStackEffect, int? maxStacks, int? everyXStacks, + float? singleStackTime, float? independentStackTime) { Id = id; Name = name; @@ -57,9 +62,13 @@ public ModifierRecipeData(int id, string name, List effectWrapper IsAura = isAura; Tag = tag; Interval = interval; + TicksInterval = ticksInterval; Duration = duration; - RefreshDuration = refreshDuration; + TicksDuration = ticksDuration; RefreshInterval = refreshInterval; + RefreshIntervalTick = refreshIntervalTick; + RefreshDuration = refreshDuration; + RefreshDurationTick = refreshDurationTick; WhenStackEffect = whenStackEffect; MaxStacks = maxStacks; EveryXStacks = everyXStacks; diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs index 0b8db94a..a320ac75 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs @@ -45,11 +45,21 @@ public void LoadState(SaveData saveData) case SaveInstruction.Interval.Id: #if MODIBUFF_SYSTEM_TEXT_JSON Interval(((SaveInstruction.Interval)instruction).Value); +#endif + break; + case SaveInstruction.IntervalTick.Id: +#if MODIBUFF_SYSTEM_TEXT_JSON + IntervalTick(((SaveInstruction.IntervalTick)instruction).Ticks); #endif break; case SaveInstruction.Duration.Id: #if MODIBUFF_SYSTEM_TEXT_JSON Duration(((SaveInstruction.Duration)instruction).Value); +#endif + break; + case SaveInstruction.DurationTick.Id: +#if MODIBUFF_SYSTEM_TEXT_JSON + DurationTick(((SaveInstruction.DurationTick)instruction).Ticks); #endif break; case SaveInstruction.Remove.Id: @@ -61,7 +71,10 @@ public void LoadState(SaveData saveData) Remove(remove.EffectOn.ToRemoveEffectOn()); break; case SaveInstruction.Remove.Type.Duration: - Remove(remove.Duration); + Remove(remove.Duration!.Value); + break; + case SaveInstruction.Remove.Type.DurationTick: + RemoveTicks(remove.Ticks!.Value); break; default: Logger.LogError( @@ -147,7 +160,7 @@ public void LoadState(SaveData saveData) : effectType.GetConstructors()[0]; var parameters = constructor.GetParameters(); - object[] effectStates = new object[parameters.Length]; + object?[] effectStates = new object[parameters.Length]; int i = 0; //TODO Refactor entire approach foreach (var property in ((System.Text.Json.JsonElement)effect.SaveData).EnumerateObject()) @@ -255,7 +268,7 @@ public void LoadState(SaveData saveData) : conditionType.GetConstructors()[0]; var parameters = constructor.GetParameters(); - object[] states = new object[parameters.Length]; + object?[] states = new object[parameters.Length]; int j = 0; //TODO recipeSaveData is null if SaveRecipeState() returns null foreach (var recipeProperty in recipeSaveData.EnumerateObject()) @@ -356,7 +369,9 @@ public void LoadState(SaveData saveData) [System.Text.Json.Serialization.JsonDerivedType(typeof(InstanceStackable), InstanceStackable.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Aura), Aura.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Interval), Interval.Id)] + [System.Text.Json.Serialization.JsonDerivedType(typeof(IntervalTick), IntervalTick.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Duration), Duration.Id)] + [System.Text.Json.Serialization.JsonDerivedType(typeof(DurationTick), DurationTick.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Remove), Remove.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Refresh), Refresh.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Stack), Stack.Id)] @@ -425,10 +440,25 @@ public Interval(float value) : base(Id) } } - public sealed record Duration : SaveInstruction + public sealed record IntervalTick : SaveInstruction { public const int Id = Interval.Id + 1; + public readonly int Ticks; + +#if MODIBUFF_SYSTEM_TEXT_JSON + [System.Text.Json.Serialization.JsonConstructor] +#endif + public IntervalTick(int ticks) : base(Id) + { + Ticks = ticks; + } + } + + public sealed record Duration : SaveInstruction + { + public const int Id = IntervalTick.Id + 1; + public readonly float Value; #if MODIBUFF_SYSTEM_TEXT_JSON @@ -440,28 +470,46 @@ public Duration(float value) : base(Id) } } + public sealed record DurationTick : SaveInstruction + { + public const int Id = Duration.Id + 1; + + public readonly int Ticks; + +#if MODIBUFF_SYSTEM_TEXT_JSON + [System.Text.Json.Serialization.JsonConstructor] +#endif + public DurationTick(int ticks) : base(Id) + { + Ticks = ticks; + } + } + public sealed record Remove : SaveInstruction { - public const int Id = SaveInstruction.Duration.Id + 1; + public const int Id = DurationTick.Id + 1; public readonly Type RemoveType; public readonly EffectOn EffectOn; - public readonly float Duration; + public readonly float? Duration; + public readonly int? Ticks; public enum Type { RemoveOn, Duration, + DurationTick, } #if MODIBUFF_SYSTEM_TEXT_JSON [System.Text.Json.Serialization.JsonConstructor] #endif - public Remove(Type removeType, EffectOn effectOn, float duration = 0) : base(Id) + public Remove(Type removeType, EffectOn effectOn, float? duration, int? ticks) : base(Id) { RemoveType = removeType; EffectOn = effectOn; Duration = duration; + Ticks = ticks; } } diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/RefreshType.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/RefreshType.cs index 11757254..7c01603c 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/RefreshType.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/RefreshType.cs @@ -2,7 +2,9 @@ namespace ModiBuff.Core { public enum RefreshType { - Duration, Interval, + IntervalTick, + Duration, + DurationTick, } } \ No newline at end of file diff --git a/ModiBuff/ModiBuff/Core/Modifier/IData.cs b/ModiBuff/ModiBuff/Core/Modifier/IData.cs index cde64e53..ed297542 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/IData.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/IData.cs @@ -12,8 +12,12 @@ public abstract record ModifierData : Data; public sealed record ModifierIntervalData(float Interval) : ModifierData; + public sealed record ModifierIntervalTickData(int IntervalTick) : ModifierData; + public sealed record ModifierDurationData(float Duration) : ModifierData; + public sealed record ModifierDurationTickData(int DurationTick) : ModifierData; + public sealed record ModifierStartingStacksData(int Stacks) : ModifierData; public abstract record EffectData((Type EffectType, int EffectNumber)? Data) : Data; diff --git a/ModiBuff/ModiBuff/Core/Modifier/IModifierDataReference.cs b/ModiBuff/ModiBuff/Core/Modifier/IModifierDataReference.cs index 2ee0a0f9..38e2c1d5 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/IModifierDataReference.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/IModifierDataReference.cs @@ -4,6 +4,9 @@ public interface IModifierDataReference { ITimeComponent[] GetTimers(); ITimeReference? GetTimer(int timeComponentNumber = 0) where TTimeComponent : ITimeComponent; + ITimeTickComponent[] GetTickTimers(); + ITimeTickReference? GetTickTimer(int timeTickComponentNumber = 0) + where TTimeTickComponent : ITimeTickComponent; IStackReference? GetStackReference(); (EffectOn EffectOn, TData Data)? GetEffectState(int stateNumber = 0, EffectOn effectOn = EffectOn.None) diff --git a/ModiBuff/ModiBuff/Core/Modifier/Modifier.cs b/ModiBuff/ModiBuff/Core/Modifier/Modifier.cs index 149a433b..d0e12308 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Modifier.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Modifier.cs @@ -21,6 +21,7 @@ public sealed class Modifier : IModifier, IModifierDataReference, IEquatable data) case ModifierData modifierData: for (int i = 0; i < _timeComponents?.Length; i++) _timeComponents[i].SetData(modifierData); + for (int i = 0; i < _timeTickComponents?.Length; i++) + _timeTickComponents[i].SetData(modifierData); _stackComponent?.SetData(modifierData); @@ -238,6 +254,7 @@ public void SetData(IList data) } public ITimeComponent[] GetTimers() => _timeComponents!; + public ITimeTickComponent[] GetTickTimers() => _timeTickComponents!; /// /// Gets a timer reference, used to update UI/UX @@ -280,6 +297,41 @@ public void SetData(IList data) return null; } + public ITimeTickReference? GetTickTimer(int timeTickComponentNumber = 0) + where TTimeTickComponent : ITimeTickComponent + { + if (_timeTickComponents == null) + { + Logger.LogError("[ModiBuff] Trying to get tick timer from a modifier that doesn't have any."); + return null; + } +#if DEBUG && !MODIBUFF_PROFILE + if (timeTickComponentNumber < 0 || timeTickComponentNumber >= _timeTickComponents.Length) + { + Logger.LogError("[ModiBuff] Time tick component number can't be lower than 0 or higher " + + "than time tick components length"); + return null; + } +#endif + int currentNumber = timeTickComponentNumber; + for (int i = 0; i < _timeTickComponents.Length; i++) + { + if (!(_timeTickComponents[i] is TTimeTickComponent)) + continue; + + if (currentNumber > 0) + { + currentNumber--; + continue; + } + + return _timeTickComponents[i]; + } + + Logger.LogError($"[ModiBuff] Couldn't find {typeof(TTimeTickComponent)} at number {timeTickComponentNumber}"); + return null; + } + public IStackReference? GetStackReference() { if (_stackComponent == null) @@ -330,12 +382,19 @@ public SaveData SaveState() for (int i = 0; i < _timeComponents.Length; i++) timeComponentsSaveData[i] = _timeComponents[i].SaveState(); } + TimeTickComponentSaveData[]? timeTickComponentsSaveData = null; + if (_timeTickComponents != null && _timeTickComponents.Length > 0) + { + timeTickComponentsSaveData = new TimeTickComponentSaveData[_timeTickComponents.Length]; + for (int i = 0; i < _timeTickComponents.Length; i++) + timeTickComponentsSaveData[i] = _timeTickComponents[i].SaveState(); + } var effectCheckSaveData = _effectCheck?.SaveState(); var effectSaveState = _effectSaveState?.SaveState(); return new SaveData(Id, targetSaveData, _multiTarget, effectCheckSaveData, stackSaveData, - timeComponentsSaveData, effectSaveState); + timeComponentsSaveData, timeTickComponentsSaveData, effectSaveState); } public void LoadState(SaveData data, IUnit owner) @@ -369,6 +428,9 @@ public void LoadState(SaveData data, IUnit owner) for (int i = 0; i < _timeComponents?.Length; i++) _timeComponents[i].LoadState(data.TimeComponentsSaveData![i]); + for (int i = 0; i < _timeTickComponents?.Length; i++) + _timeTickComponents[i].LoadState(data.TimeTickComponentsSaveData![i]); + if (data.EffectCheckSaveData != null) _effectCheck?.LoadState(data.EffectCheckSaveData.Value); @@ -379,9 +441,10 @@ public void LoadState(SaveData data, IUnit owner) public void ResetState() { _initComponent?.ResetState(); - if (_timeComponents != null) - for (int i = 0; i < _timeComponents.Length; i++) - _timeComponents[i].ResetState(); + for (int i = 0; i < _timeComponents?.Length; i++) + _timeComponents[i].ResetState(); + for (int i = 0; i < _timeTickComponents?.Length; i++) + _timeTickComponents[i].ResetState(); _stackComponent?.ResetState(); _effectCheck?.ResetState(); _targetComponent.ResetState(); @@ -424,6 +487,7 @@ public readonly struct SaveData public readonly ModifierCheck.SaveData? EffectCheckSaveData; public readonly StackComponent.SaveData? StackSaveData; public readonly IReadOnlyList? TimeComponentsSaveData; + public readonly IReadOnlyList? TimeTickComponentsSaveData; public readonly IReadOnlyList? EffectsSaveData; #if MODIBUFF_SYSTEM_TEXT_JSON @@ -432,6 +496,7 @@ public readonly struct SaveData public SaveData(int id, object targetSaveData, bool isMultiTarget, ModifierCheck.SaveData? effectCheckSaveData, StackComponent.SaveData? stackSaveData, IReadOnlyList? timeComponentsSaveData, + IReadOnlyList? timeTickComponentsSaveData, IReadOnlyList? effectsSaveData) { Id = id; @@ -440,6 +505,7 @@ public SaveData(int id, object targetSaveData, bool isMultiTarget, EffectCheckSaveData = effectCheckSaveData; StackSaveData = stackSaveData; TimeComponentsSaveData = timeComponentsSaveData; + TimeTickComponentsSaveData = timeTickComponentsSaveData; EffectsSaveData = effectsSaveData; } } diff --git a/ModiBuff/ModiBuff/Core/Modifier/ModifierController.cs b/ModiBuff/ModiBuff/Core/Modifier/ModifierController.cs index beb334d2..2117e4af 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/ModifierController.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/ModifierController.cs @@ -53,6 +53,22 @@ public void Update(float delta) _modifiersToRemove.Clear(); } + public void UpdateTick() + { + int modifiersTop = _modifiersTop; + for (int i = 0; i < modifiersTop; i++) + _modifiers[i]!.UpdateTick(); + + int removeCount = _modifiersToRemove.Count; + if (removeCount == 0) + return; + + for (int i = 0; i < removeCount; i++) + Remove(_modifiersToRemove[i]); + + _modifiersToRemove.Clear(); + } + public ModifierReference[] GetModifierReferences() { var modifierReferences = new ModifierReference[_modifiersTop]; @@ -74,6 +90,12 @@ public ModifierReference[] GetModifierReferences() return GetModifier(id, genId)?.GetTimer(timeComponentNumber); } + public ITimeTickReference? GetTickTimer(int id, int genId = 0, + int timeTickComponentNumber = 0) where TTimeTickComponent : ITimeTickComponent + { + return GetModifier(id, genId)?.GetTickTimer(timeTickComponentNumber); + } + /// /// Get stack reference, used to update UI/UX ///