From 4a282db753733b656a7de7a7dbd3125f7f55228d Mon Sep 17 00:00:00 2001 From: chillu Date: Tue, 10 Sep 2024 15:59:32 +0200 Subject: [PATCH 1/5] feat: initial modifier recipe data logic --- ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj | 1 + .../ModiBuff.Tests/ModifierRecipeDataTests.cs | 29 +++++++++++++ .../Recipe/AddModifierCommonData.cs | 17 ++++++++ ModiBuff/ModiBuff.Units/UnitType.cs | 20 +++++++++ .../Creation/Recipe/ModifierRecipe.cs | 11 +++++ .../Creation/Recipe/ModifierRecipeSaveLoad.cs | 23 +++++++++-- ModiBuff/ModiBuff/ModiBuff.csproj | 6 ++- README.md | 41 +++++++++++++++++++ 8 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs create mode 100644 ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs diff --git a/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj b/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj index 5869f344..a146d609 100644 --- a/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj +++ b/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj @@ -7,6 +7,7 @@ Library net6.0 false + MODIBUFF_SYSTEM_TEXT_JSON diff --git a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs new file mode 100644 index 00000000..aef007c5 --- /dev/null +++ b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs @@ -0,0 +1,29 @@ +using ModiBuff.Core; +using ModiBuff.Core.Units; +using NUnit.Framework; + +namespace ModiBuff.Tests +{ + public sealed class ModifierRecipeDataTests : ModifierTests + { + [Test] + public void LegalActionUnitType() + { + AddRecipe("AddDamage") + .Data(new AddModifierCommonData(ModifierAddType.Self, EnemyUnitType.Goblin)) + .Effect(new DamageEffect(5), EffectOn.Init); + AddEnemySelfBuff("AddDamage", EnemyUnitType.Goblin) + .Effect(new DamageEffect(5), EffectOn.Init); + Setup(); + + Unit.AddModifierSelf("AddDamage"); + + Assert.AreEqual(UnitHealth - UnitDamage, Unit.Health); + + ModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType, string displayName = "", + string description = "") => + Add(name + enemyUnitType, displayName, description) + .Data(new AddModifierCommonData(ModifierAddType.Self, enemyUnitType)); + } + } +} \ No newline at end of file diff --git a/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs b/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs new file mode 100644 index 00000000..9227cd26 --- /dev/null +++ b/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs @@ -0,0 +1,17 @@ +namespace ModiBuff.Core.Units +{ + public enum ModifierAddType + { + Self = 1, + Applier, + } + + public record AddModifierCommonData(ModifierAddType ModifierType, TUnit UnitType); +} + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} \ No newline at end of file diff --git a/ModiBuff/ModiBuff.Units/UnitType.cs b/ModiBuff/ModiBuff.Units/UnitType.cs index bf2e7d81..22840b1d 100644 --- a/ModiBuff/ModiBuff.Units/UnitType.cs +++ b/ModiBuff/ModiBuff.Units/UnitType.cs @@ -11,6 +11,26 @@ public enum UnitType Neutral = 3, } + /// + /// Example of how to use custom modifier data for advanced tagging purposes + /// + public enum AllyUnitType + { + Warrior, + Archer, + Mage, + } + + /// + /// Example of how to use custom modifier data for advanced tagging purposes + /// + public enum EnemyUnitType + { + Slime, + Goblin, + Orc, + } + public static class UnitTypeExtensions { public static bool IsLegalTarget(this UnitType unitType, UnitType target) diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs index c31ebd85..925ed370 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipe.cs @@ -22,6 +22,8 @@ public sealed partial class ModifierRecipe : IModifierRecipe, IEquatable(T data) + { + _data = data; + //_saveInstructions.Add(new SaveInstruction.Data(@object)); + return this; + } + private void AddRemoveEffect(EffectOn effectOn) { if (_removeEffectWrapper != null) @@ -581,6 +590,8 @@ public ModifierInfo CreateModifierInfo() public int GetAuraId() => _auraId; + public object GetData() => _data; + private ModifierRecipe DurationInternal(float duration) { _duration = duration; diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs index 8ef8aa97..95e12204 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipeSaveLoad.cs @@ -120,10 +120,12 @@ public void LoadState(SaveData saveData) ModifierAction(action.ModifierActionFlags, action.EffectOn); #endif break; -// case SaveInstruction.Event.Id: -//#if MODIBUFF_SYSTEM_TEXT_JSON -//#endif -// break; + case SaveInstruction.Data.Id: +#if MODIBUFF_SYSTEM_TEXT_JSON + var data = (SaveInstruction.Data)instruction; + Data(data.SaveData); +#endif + break; default: Logger.LogError($"Unknown instruction with id {instruction.InstructionId}"); break; @@ -184,6 +186,7 @@ public void LoadState(SaveData saveData) [System.Text.Json.Serialization.JsonDerivedType(typeof(CallbackUnit), CallbackUnit.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(Effect), Effect.Id)] [System.Text.Json.Serialization.JsonDerivedType(typeof(ModifierAction), ModifierAction.Id)] + [System.Text.Json.Serialization.JsonDerivedType(typeof(Data), Data.Id)] #endif public record SaveInstruction { @@ -413,6 +416,18 @@ public ModifierAction(ModiBuff.Core.ModifierAction modifierActionFlags, EffectOn EffectOn = effectOn; } } + + public sealed record Data : SaveInstruction + { + public const int Id = ModifierAction.Id + 1; + + public readonly object SaveData; + +#if MODIBUFF_SYSTEM_TEXT_JSON + [System.Text.Json.Serialization.JsonConstructor] +#endif + public Data(object saveData) : base(Id) => SaveData = saveData; + } } public readonly struct SaveData diff --git a/ModiBuff/ModiBuff/ModiBuff.csproj b/ModiBuff/ModiBuff/ModiBuff.csproj index 29179c60..bb1520a5 100644 --- a/ModiBuff/ModiBuff/ModiBuff.csproj +++ b/ModiBuff/ModiBuff/ModiBuff.csproj @@ -21,19 +21,21 @@ - TRACE + TRACE MODIBUFF_SYSTEM_TEXT_JSON - TRACE + TRACE MODIBUFF_SYSTEM_TEXT_JSON + + diff --git a/README.md b/README.md index c2ea9178..983f7b3a 100644 --- a/README.md +++ b/README.md @@ -846,6 +846,47 @@ Add("InitDamageEnemyOnly") .Effect(new DamageEffect(5f), EffectOn.Init); ``` +### Custom Data + +Sometimes the tag system is too limited for our needs. That's why every recipe stores a custom object, that can be +accessed from anywhere like the tag. +It is mostly designed to store basic information about the modifier, one example of this is adding a modifier to every +unit of type X (ex. Goblin). +Instead of storing that information on the goblin unit data itself, we delegate it to the modifiers, also no need for +arbitrary naming conventions this way. + +```csharp +public enum EnemyUnitType +{ + Slime, + Goblin, + Orc, +} + +public enum ModifierAddType +{ + Self = 1, + Applier, +} + +public record AddModifierCommonData(ModifierAddType ModifierType, TUnit UnitType); + +Add("Damage") + .Data(new AddModifierCommonData(ModifierAddType.Self, EnemyUnitType.Goblin)) + .Effect(new DamageEffect(5), EffectOn.Init); +``` + +It also allows for a more standardized recipe creation, by unit types, modifier types, etc, reducing code duplication. + +```csharp +AddEnemySelfBuff("Damage", EnemyUnitType.Goblin) + .Effect(new DamageEffect(5), EffectOn.Init); + +ModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType, string displayName = "", string description = "") => + Add(name + enemyUnitType, displayName, description) + .Data(new AddModifierCommonData(ModifierAddType.Self, enemyUnitType)); +``` + ### Custom Stack Stack is always triggered when we try to add the same type of modifier again. From d063088ba9bfa106874c59822decc5d57afbaeb8 Mon Sep 17 00:00:00 2001 From: chillu Date: Sat, 23 Nov 2024 11:53:52 +0100 Subject: [PATCH 2/5] feat: further custom data on modifiers --- ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj | 1 - .../ModiBuff.Tests/ModifierRecipeDataTests.cs | 26 ++++++++++++------- .../Creation/Recipe/IModifierRecipe.cs | 1 + .../Creation/Recipe/ModifierRecipes.cs | 25 +++++++++++++++++- ModiBuff/ModiBuff/ModiBuff.csproj | 6 ++--- README.md | 3 +-- 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj b/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj index a146d609..5869f344 100644 --- a/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj +++ b/ModiBuff/ModiBuff.Tests/ModiBuff.Tests.csproj @@ -7,7 +7,6 @@ Library net6.0 false - MODIBUFF_SYSTEM_TEXT_JSON diff --git a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs index aef007c5..661fe713 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using ModiBuff.Core; using ModiBuff.Core.Units; using NUnit.Framework; @@ -9,20 +10,25 @@ public sealed class ModifierRecipeDataTests : ModifierTests [Test] public void LegalActionUnitType() { - AddRecipe("AddDamage") - .Data(new AddModifierCommonData(ModifierAddType.Self, EnemyUnitType.Goblin)) - .Effect(new DamageEffect(5), EffectOn.Init); - AddEnemySelfBuff("AddDamage", EnemyUnitType.Goblin) - .Effect(new DamageEffect(5), EffectOn.Init); + const EnemyUnitType enemyType = EnemyUnitType.Goblin; + AddEnemySelfBuff("AddDamage", enemyType) + .Effect(new AddDamageEffect(5), EffectOn.Init); Setup(); - Unit.AddModifierSelf("AddDamage"); + Unit.AddModifierSelf("AddDamage" + enemyType); - Assert.AreEqual(UnitHealth - UnitDamage, Unit.Health); + Assert.AreEqual(UnitDamage + 5, Unit.Damage); - ModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType, string displayName = "", - string description = "") => - Add(name + enemyUnitType, displayName, description) + var enemySelfModifiers = new List(); + foreach ((int id, var data) in ModifierRecipes.GetModifierData>()) + if (data.UnitType == enemyType && data.ModifierType == ModifierAddType.Self) + enemySelfModifiers.Add(id); + + Assert.AreEqual(enemySelfModifiers.Count, 1); + Assert.AreEqual(enemySelfModifiers[0], IdManager.GetId("AddDamage" + enemyType)); + + ModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType) => + AddRecipe(name + enemyUnitType) .Data(new AddModifierCommonData(ModifierAddType.Self, enemyUnitType)); } } diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/IModifierRecipe.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/IModifierRecipe.cs index bd3b2a2c..78aadbce 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/IModifierRecipe.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/IModifierRecipe.cs @@ -9,6 +9,7 @@ public interface IModifierRecipe ModifierInfo CreateModifierInfo(); TagType GetTag(); int GetAuraId(); + object GetData(); //TODO Move/refactor ModifierRecipe.SaveData SaveState(); diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs index bc202004..858efde4 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs @@ -29,6 +29,9 @@ public class ModifierRecipes : IModifierRecipes private ModifierInfo[] _modifierInfos; private TagType[] _tags; private int[] _auraIds; + private object[] _modifierData; + + private readonly List<(int, object)> _modifierDataList; public ModifierRecipes(ModifierIdManager idManager, EffectTypeIdManager effectTypeIdManager) { @@ -40,6 +43,7 @@ public ModifierRecipes(ModifierIdManager idManager, EffectTypeIdManager effectTy _manualGenerators = new Dictionary(64); _modifierGenerators = new Dictionary(64); _registeredNames = new List(16); + _modifierDataList = new List<(int, object)>(16); } //TODO TEMP @@ -59,7 +63,9 @@ public void CreateGenerators() _modifierInfos = new ModifierInfo[_recipes.Count + _manualGenerators.Count]; _tags = new TagType[_recipes.Count + _manualGenerators.Count]; _auraIds = new int[_recipes.Count + _manualGenerators.Count]; - _auraIds.AsSpan().Fill(-1); + for (int i = 0; i < _auraIds.Length; i++) + _auraIds[i] = -1; + _modifierData = new object[_recipes.Count + _manualGenerators.Count]; foreach (var generator in _manualGenerators.Values) { _modifierGenerators.Add(generator.Name, generator); @@ -68,6 +74,7 @@ public void CreateGenerators() _tags[generator.Id] = generator.Tag; if (generator.Tag.HasFlag(TagType.IsAura)) _auraIds[generator.Id] = generator.AuraId; + //TODO Generator data } foreach (var recipe in _recipes.Values) @@ -77,6 +84,7 @@ public void CreateGenerators() _tags[recipe.Id] = recipe.GetTag(); if (recipe.GetTag().HasFlag(TagType.IsAura)) _auraIds[recipe.Id] = recipe.GetAuraId(); + _modifierData[recipe.Id] = recipe.GetData(); } GeneratorCount = _modifierGenerators.Count; @@ -99,6 +107,21 @@ public ModifierInfo GetModifierInfo(int id) public static ref readonly TagType GetTag(int id) => ref _instance._tags[id]; public static int GetAuraId(int id) => _instance._auraIds[id]; + public static T GetModifierData(int id) => (T)_instance._modifierData[id]; + + public static (int Id, T Data)[] GetModifierData() + { + for (int i = 0; i < _instance._modifierData.Length; i++) + if (_instance._modifierData[i] is T data) + _instance._modifierDataList.Add((i, data)); + + var modifierData = new (int, T)[_instance._modifierDataList.Count]; + for (int i = 0; i < modifierData.Length; i++) + modifierData[i] = ((int, T))_instance._modifierDataList[i]; + _instance._modifierDataList.Clear(); + return modifierData; + } + public IModifierGenerator GetGenerator(string name) => _modifierGenerators[name]; public IModifierGenerator[] GetGenerators() => _modifierGenerators.Values.ToArray(); diff --git a/ModiBuff/ModiBuff/ModiBuff.csproj b/ModiBuff/ModiBuff/ModiBuff.csproj index bb1520a5..29179c60 100644 --- a/ModiBuff/ModiBuff/ModiBuff.csproj +++ b/ModiBuff/ModiBuff/ModiBuff.csproj @@ -21,21 +21,19 @@ - TRACE MODIBUFF_SYSTEM_TEXT_JSON + TRACE - TRACE MODIBUFF_SYSTEM_TEXT_JSON + TRACE - - diff --git a/README.md b/README.md index 983f7b3a..72ee5ef3 100644 --- a/README.md +++ b/README.md @@ -530,8 +530,7 @@ In this example we add 5 damage to unit on Init, and the modifier can only be re StrongHit". Essentially a hit that deals more than half units health in damage (ex. game logic). -> Important: there can only be one callback `CallbackUnit` per modifier, but there can be -> multiple effects that trigger on that callback. +> Important: all versions before 0.4/latest master can only have one callback `CallbackUnit` per modifier. ```csharp Add("InitAddDamageRevertibleHalfHealthCallback") From 97ad2d19c2fc6d65a02f02971cdcd99530af6901 Mon Sep 17 00:00:00 2001 From: chillu Date: Fri, 13 Dec 2024 22:14:26 +0100 Subject: [PATCH 3/5] feat: custom modifier add type, readme custom data, readme method extensions for recipes --- .../ModiBuff.Tests/ModifierRecipeDataTests.cs | 27 +++++++ .../Recipe/AddModifierCommonData.cs | 5 ++ ModiBuff/ModiBuff.Units/UnitType.cs | 9 +++ README.md | 75 ++++++++++++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) diff --git a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs index 661fe713..b01400b7 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs @@ -31,5 +31,32 @@ ModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType) => AddRecipe(name + enemyUnitType) .Data(new AddModifierCommonData(ModifierAddType.Self, enemyUnitType)); } + + [Test] + public void SpecialUnitEvent() + { + const EnemyUnitType enemyType = EnemyUnitType.Goblin; + AddGoblinModifier("RemoveDamage", GoblinModifierActionType.OnSurrender) + .Effect(new AddDamageEffect(-5), EffectOn.Init); + Setup(); + + Unit.AddModifierSelf("RemoveDamage" + enemyType); + + Assert.AreEqual(UnitDamage - 5, Unit.Damage); + + var goblinSurrenderModifiers = new List(); + foreach ((int id, var data) in ModifierRecipes + .GetModifierData>()) + if (data.UnitType == enemyType && data.ModifierType == GoblinModifierActionType.OnSurrender) + goblinSurrenderModifiers.Add(id); + + Assert.AreEqual(goblinSurrenderModifiers.Count, 1); + Assert.AreEqual(goblinSurrenderModifiers[0], IdManager.GetId("RemoveDamage" + enemyType)); + + ModifierRecipe AddGoblinModifier(string name, GoblinModifierActionType modifierActionType) => + AddRecipe(name + EnemyUnitType.Goblin) + .Data(new AddModifierCommonData(modifierActionType, + EnemyUnitType.Goblin)); + } } } \ No newline at end of file diff --git a/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs b/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs index 9227cd26..ffa41d07 100644 --- a/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs +++ b/ModiBuff/ModiBuff.Units/Recipe/AddModifierCommonData.cs @@ -7,6 +7,11 @@ public enum ModifierAddType } public record AddModifierCommonData(ModifierAddType ModifierType, TUnit UnitType); + + /// + /// Custom AddModifierCommonData for non-standard/non-generic modifier add actions + /// + public record AddModifierCommonData(TModifier ModifierType, TUnit UnitType); } namespace System.Runtime.CompilerServices diff --git a/ModiBuff/ModiBuff.Units/UnitType.cs b/ModiBuff/ModiBuff.Units/UnitType.cs index 22840b1d..fdab9bf3 100644 --- a/ModiBuff/ModiBuff.Units/UnitType.cs +++ b/ModiBuff/ModiBuff.Units/UnitType.cs @@ -31,6 +31,15 @@ public enum EnemyUnitType Orc, } + public enum GoblinModifierActionType + { + /// + /// Example special goblin mechanic, that says that these modifiers should be triggered on goblins event "OnSurrender" + /// + OnSurrender, + OnRetreat, + } + public static class UnitTypeExtensions { public static bool IsLegalTarget(this UnitType unitType, UnitType target) diff --git a/README.md b/README.md index 72ee5ef3..56d07326 100644 --- a/README.md +++ b/README.md @@ -847,12 +847,12 @@ Add("InitDamageEnemyOnly") ### Custom Data -Sometimes the tag system is too limited for our needs. That's why every recipe stores a custom object, that can be -accessed from anywhere like the tag. +Sometimes the tag system is too limited for our needs, and we want to store custom modifier identification with data. +That's why every recipe stores a custom object, that can be accessed from anywhere like the tag. It is mostly designed to store basic information about the modifier, one example of this is adding a modifier to every unit of type X (ex. Goblin). -Instead of storing that information on the goblin unit data itself, we delegate it to the modifiers, also no need for -arbitrary naming conventions this way. +Instead of storing that information on the goblin unit data itself, we delegate it to the modifiers, also it makes it so +we don't need arbitrary naming conventions for our modifiers. ```csharp public enum EnemyUnitType @@ -876,6 +876,7 @@ Add("Damage") ``` It also allows for a more standardized recipe creation, by unit types, modifier types, etc, reducing code duplication. +And allowing for a set of standards, making the modifier creation less prone to errors. ```csharp AddEnemySelfBuff("Damage", EnemyUnitType.Goblin) @@ -886,6 +887,44 @@ ModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType, string .Data(new AddModifierCommonData(ModifierAddType.Self, enemyUnitType)); ``` +Then to apply all the modifiers to the units, we filter through them. + +```csharp +var goblinSelfModifiers = new List(); +foreach ((int id, var data) in ModifierRecipes.GetModifierData>()) + if (data.UnitType == EnemyType.Goblin && data.ModifierType == ModifierAddType.Self) + goblinSelfModifiers.Add(id); +``` + +It's also possible to create `ModifierRecipe` extensions instead, for ease of use. +This is recommended if you have multiple different entity type combinations, and actions. + +```csharp +public static ModifierRecipe Data(this ModifierRecipe recipe, ModifierAddType modifierAddType, EnemyUnitType enemyUnitType) => + recipe.Data(new AddModifierCommonData(modifierAddType, enemyUnitType)); + +ModifierRecipe AddEnemySelfBuffExtension(string name, EnemyUnitType enemyUnitType, string displayName = "", string description = "") => + AddRecipe(name + enemyUnitType, displayName, description) + .Data(ModifierAddType.Self, enemyUnitType); +``` + +> Note: That modifier appliers should still be two separate modifiers, the applier and the applied modifier. +> Then we just add the appliers based on what we get from `ModifierRecipes.GetModifierData`, and apply them on +> actions/events. + +#### Advanced Custom Data + +It's possible to use custom action types as well, possibly for non-generic actions tied to a unit type. +Ex. goblins having a special event to surrender, and us wanting to trigger some modifiers on certain goblin types. + +```csharp +public record AddModifierCommonData(TModifier ModifierType, TUnit UnitType); + +Add("RemoveDamageOnSurrender") + .Data(new AddModifierCommonData(GoblinModifierActionType.OnSurrender, EnemyUnitType.Goblin) + .Effect(new DamageEffect(-5), EffectOn.Init); +``` + ### Custom Stack Stack is always triggered when we try to add the same type of modifier again. @@ -959,6 +998,34 @@ Add("Full") Each modifier should have at least one effect, unless it's used as a flag. +### Encapsulating same code in method extensions + +When a game has many modifiers that share the same core logic, it's recommended to encapsulate the logic in method +through method extensions. +An example of this from Chillu's test game, where whenever an enemy drops a resource their carrying, they lose that +resources buff/debuff. + +```csharp +public static ModifierRecipe RemoveOnResourceDrop(this ModifierRecipe recipe, ResourceType resourceType) => + recipe.Remove(RemoveEffectOn.CallbackEffect) + .CallbackEffect(CallbackType.EnemyDropResource, effect => + new ResourceDroppedEvent((target, resource) => + { + if (effect is RemoveEffect removeEffect && resource.IsPrimary(resourceType)) + removeEffect.Effect(target, null); + })); + +AddResourceModifier(ResourceType.Red, ResourceModifierAction.OnDigested, "Curse", "Resource eating curse", + "Deal damage with every resource eaten") + .Effect(new DamageEffect(new MutableDamageStatData(10)), EffectOn.CallbackUnit) + .CallbackUnit(CallbackType.EnemyEatResource) + .Remove(15).Refresh() + .RemoveOnResourceDrop(ResourceType.Red); //Then we just call the method extension each time we we need to add the modifier of this type +``` + +This allows for lower cognitive load, since it's easier to understand what "RemoveOnResourceDrop(ResourceType.Red)" +does. + ## Recipe Limitations > Note that these limitations don't matter for 95% of the use cases. From 13f27281ee306a2dd3347c6f759f99605bc1266e Mon Sep 17 00:00:00 2001 From: chillu Date: Sat, 14 Dec 2024 18:07:18 +0100 Subject: [PATCH 4/5] feat: generator custom modifier data --- .../ModiBuff.Tests/ModifierRecipeDataTests.cs | 28 +++++++++++++++++++ ModiBuff/ModiBuff.Tests/ModifierTests.cs | 5 ++-- .../PartialUnitModifierTests.cs | 5 ++-- .../Generation/ManualModifierGenerator.cs | 5 +++- .../Creation/Recipe/ModifierRecipes.cs | 8 +++--- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs index b01400b7..9e792b3a 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs @@ -58,5 +58,33 @@ ModifierRecipe AddGoblinModifier(string name, GoblinModifierActionType modifierA .Data(new AddModifierCommonData(modifierActionType, EnemyUnitType.Goblin)); } + + [Test] + public void LegalActionUnitTypeGenerator() + { + const EnemyUnitType enemyType = EnemyUnitType.Goblin; + AddGenerator("AddDamage" + enemyType, (id, genId, name, tag) => + { + var addDamageEffect = new AddDamageEffect(5); + var initComponent = new InitComponent(false, new IEffect[] { addDamageEffect }, null); + + return new Modifier(id, genId, name, initComponent, null, null, null, + new SingleTargetComponent(), new EffectStateInfo(addDamageEffect), null); + }, Core.Units.TagType.Default, + customModifierData: new AddModifierCommonData(ModifierAddType.Self, enemyType)); + Setup(); + + Unit.AddModifierSelf("AddDamage" + enemyType); + + Assert.AreEqual(UnitDamage + 5, Unit.Damage); + + var enemySelfModifiers = new List(); + foreach ((int id, var data) in ModifierRecipes.GetModifierData>()) + if (data.UnitType == enemyType && data.ModifierType == ModifierAddType.Self) + enemySelfModifiers.Add(id); + + Assert.AreEqual(enemySelfModifiers.Count, 1); + Assert.AreEqual(enemySelfModifiers[0], IdManager.GetId("AddDamage" + enemyType)); + } } } \ No newline at end of file diff --git a/ModiBuff/ModiBuff.Tests/ModifierTests.cs b/ModiBuff/ModiBuff.Tests/ModifierTests.cs index 83a66373..6c852d13 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierTests.cs @@ -72,9 +72,10 @@ public virtual void IterationSetup() protected void AddEffect(string name, params IEffect[] effects) => Effects.Add(name, effects); - protected void AddGenerator(string name, in ModifierGeneratorFunc createFunc, TagType tag = TagType.Default) + protected void AddGenerator(string name, in ModifierGeneratorFunc createFunc, TagType tag = TagType.Default, + int auraId = -1, object customModifierData = null) { - Recipes.Add(name, name, "", in createFunc, tag.ToInternalTag()); + Recipes.Add(name, name, "", in createFunc, tag.ToInternalTag(), auraId, customModifierData); } /// diff --git a/ModiBuff/ModiBuff.Tests/PartialUnitTests/PartialUnitModifierTests.cs b/ModiBuff/ModiBuff.Tests/PartialUnitTests/PartialUnitModifierTests.cs index 94e6e0bb..888c443f 100644 --- a/ModiBuff/ModiBuff.Tests/PartialUnitTests/PartialUnitModifierTests.cs +++ b/ModiBuff/ModiBuff.Tests/PartialUnitTests/PartialUnitModifierTests.cs @@ -74,9 +74,10 @@ public void IterationSetup() protected void AddEffect(string name, params IEffect[] effects) => Effects.Add(name, effects); - protected void AddGenerator(string name, in ModifierGeneratorFunc createFunc, TagType tag = TagType.Default) + protected void AddGenerator(string name, in ModifierGeneratorFunc createFunc, TagType tag = TagType.Default, + int auraId = -1, object customModifierData = null) { - Recipes.Add(name, name, "", in createFunc, tag.ToInternalTag()); + Recipes.Add(name, name, "", in createFunc, tag.ToInternalTag(), auraId, customModifierData); } /// diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ManualModifierGenerator.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ManualModifierGenerator.cs index de24c56a..8c50205a 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ManualModifierGenerator.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Generation/ManualModifierGenerator.cs @@ -8,12 +8,13 @@ public sealed class ManualModifierGenerator : IModifierGenerator public string Description { get; } public TagType Tag { get; } public int AuraId { get; } + public object Data { get; } private readonly ModifierGeneratorFunc _createFunc; private int _genId; public ManualModifierGenerator(int id, string name, string displayName, string description, - in ModifierGeneratorFunc createFunc, TagType tag, int auraId) + in ModifierGeneratorFunc createFunc, TagType tag, int auraId, object customModifierData) { Id = id; Name = name; @@ -27,6 +28,8 @@ public ManualModifierGenerator(int id, string name, string displayName, string d if (auraId != -1) tag |= TagType.IsAura; Tag = tag; + AuraId = auraId; + Data = customModifierData; } public Modifier Create() => _createFunc(Id, _genId++, Name, Tag); diff --git a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs index 858efde4..811e5222 100644 --- a/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs +++ b/ModiBuff/ModiBuff/Core/Modifier/Creation/Recipe/ModifierRecipes.cs @@ -74,7 +74,7 @@ public void CreateGenerators() _tags[generator.Id] = generator.Tag; if (generator.Tag.HasFlag(TagType.IsAura)) _auraIds[generator.Id] = generator.AuraId; - //TODO Generator data + _modifierData[generator.Id] = generator.Data; } foreach (var recipe in _recipes.Values) @@ -157,8 +157,8 @@ public ModifierRecipe Add(string name, string displayName = "", string descripti return recipe; } - public void Add(string name, string displayName, string description, - in ModifierGeneratorFunc createFunc, TagType tag = TagType.Default, int auraId = -1) + public void Add(string name, string displayName, string description, in ModifierGeneratorFunc createFunc, + TagType tag = TagType.Default, int auraId = -1, object customModifierData = null) { if (_recipes.ContainsKey(name)) { @@ -191,7 +191,7 @@ public void Add(string name, string displayName, string description, id = _idManager.GetFreeId(name); var modifierGenerator = new ManualModifierGenerator(id, name, displayName, description, - in createFunc, tag, auraId); + in createFunc, tag, auraId, customModifierData); _manualGenerators.Add(name, modifierGenerator); } From 59c46e140fd1785e38b8b03e9a2dc766151939f8 Mon Sep 17 00:00:00 2001 From: chillu Date: Sat, 14 Dec 2024 18:32:28 +0100 Subject: [PATCH 5/5] feat: multiple add custom modifier data check in test --- .../ModiBuff.Tests/ModifierRecipeDataTests.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs index 9e792b3a..6ccef930 100644 --- a/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs +++ b/ModiBuff/ModiBuff.Tests/ModifierRecipeDataTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using ModiBuff.Core; using ModiBuff.Core.Units; using NUnit.Framework; @@ -13,6 +14,15 @@ public void LegalActionUnitType() const EnemyUnitType enemyType = EnemyUnitType.Goblin; AddEnemySelfBuff("AddDamage", enemyType) .Effect(new AddDamageEffect(5), EffectOn.Init); + AddRecipe("AddDamageApplier" + enemyType) + .Effect(new AddDamageEffect(5), EffectOn.Init) + .Data(new AddModifierCommonData(ModifierAddType.Applier, enemyType)); + AddEnemySelfBuff("AddDamage", EnemyUnitType.Slime) + .Effect(new AddDamageEffect(5), EffectOn.Init); + AddRecipe("AddDamageAdvanced" + enemyType) + .Effect(new AddDamageEffect(5), EffectOn.Init) + .Data(new AddModifierCommonData( + GoblinModifierActionType.OnSurrender, EnemyUnitType.Goblin)); Setup(); Unit.AddModifierSelf("AddDamage" + enemyType); @@ -20,7 +30,10 @@ public void LegalActionUnitType() Assert.AreEqual(UnitDamage + 5, Unit.Damage); var enemySelfModifiers = new List(); - foreach ((int id, var data) in ModifierRecipes.GetModifierData>()) + var addModifierCommonData = ModifierRecipes.GetModifierData>(); + Assert.AreEqual(addModifierCommonData.Length, 3); + Assert.AreEqual(addModifierCommonData.Count(d => d.Data.UnitType == enemyType), 2); + foreach ((int id, var data) in addModifierCommonData) if (data.UnitType == enemyType && data.ModifierType == ModifierAddType.Self) enemySelfModifiers.Add(id);