{"id":22765930,"url":"https://github.com/chillu1/modibuff","last_synced_at":"2025-04-09T12:07:16.431Z","repository":{"id":112250458,"uuid":"568740176","full_name":"Chillu1/ModiBuff","owner":"Chillu1","description":"Buff/Debuff/Modifier library focused on feature set and performance, while maintaining 0 GC. Fully pooled","archived":false,"fork":false,"pushed_at":"2024-05-12T13:32:54.000Z","size":2245,"stargazers_count":122,"open_issues_count":12,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-05-16T02:13:27.506Z","etag":null,"topics":["buff","debuff","game-development","gamedev","modifier","stats"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Chillu1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-21T10:05:45.000Z","updated_at":"2024-06-15T19:30:12.538Z","dependencies_parsed_at":"2024-06-15T19:30:04.306Z","dependency_job_id":"5b006475-efb4-4852-9373-18f52f993078","html_url":"https://github.com/Chillu1/ModiBuff","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chillu1%2FModiBuff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chillu1%2FModiBuff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chillu1%2FModiBuff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chillu1%2FModiBuff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Chillu1","download_url":"https://codeload.github.com/Chillu1/ModiBuff/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248036067,"owners_count":21037092,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["buff","debuff","game-development","gamedev","modifier","stats"],"created_at":"2024-12-11T12:16:08.364Z","updated_at":"2025-04-09T12:07:16.400Z","avatar_url":"https://github.com/Chillu1.png","language":"C#","readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"Docs/LogoTest.png\" width=\"500\"/\u003e\n\u003cimg src=\"Docs/ModiBuff.png\" width=\"500\"/\u003e\n\u003c/p\u003e\n\n\u003ch1\u003e\u003c/h1\u003e\n\n[![Godot](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgodotengine.org%2Fasset-library%2Fapi%2Fasset%2F2166\u0026query=%24.version_string\u0026logo=GodotEngine\u0026label=Godot)](https://godotengine.org/asset-library/asset/2166)\n[![Nuget](https://img.shields.io/nuget/v/ModiBuff)](https://www.nuget.org/packages/ModiBuff/)\n![Coverage](Docs/badge_linecoverage.svg)\n\n- [What is this?](#what-is-this)\n- [Features](#features)\n- [RoadMap](#roadmap)\n- [Benchmarks](#benchmarks)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Usage](#usage)\n\t- [Recipe](#recipe)\n\t- [Adding Modifiers to Units](#adding-modifiers-to-units)\n\t- [Effect](#effect)\n\t- [Serialization](#serialization)\n- [FAQ](#faq)\n- [Examples](#examples)\n- [Differences to ModiBuffEcs and Old](#differences-to-modibuffecs-and-old)\n\n# What is this?\n\nThis zero dependency, engine-agnostic library was made to make a standardized powerful system that allows for\nmanipulation of effects on entities.\n\n**It focuses on Feature Set, Performance and Ease of use, in that order.**\n\n**Library goals:**\n\n* Capable of creating very sophisticated modifiers\n* Modifiers being fast\n* Creating them easily\n\nThe library is split into two core parts:\n\nModiBuff is the core backend part that handles all the modifier logic and is mostly unopinionated when it comes to the\ngame logic.\n\nMeanwhile ModiBuff.Units is a fully featured implementation of the library, that showcases how to tie the library into a\ngame.\n\n\u003e Note: The library is currently in development, and it will most likely encounter breaking API changes.\n\n## Why do I need this?\n\nThe vast majority of games make their own buff/debuff systems, to fit their game logic.\nExamples of this are: Dota 2, League of Legends, Path of Exile, Diablo, World of Warcraft, etc.\n\nWhile this is a great approach, it's also very time consuming, and requires a fair amount of research to design well.\n\nThis library solves that, but also allows for more complex and deeper modifiers than the aforementioned games.\n\n# Features\n\n* No GC/runtime heap allocations (fully pooled with state reset)\n* Low memory usage (2-5 MB for 10_000 modifiers)\n* Fast effects [10_000 damage modifiers in 0.24ms](#benchmarks)\n* Fast iteration [10_000 interval modifiers \u0026 10_000 units in 1.37ms](#benchmarks)\n* Easy high level API [recipes](#recipe)\n* Instance Stackable modifiers (multiple instances of the same modifier type)\n* Effects on actions\n\t* Init\n\t* Interval\n\t* Duration\n\t* Stack\n\t* Callbacks (any user logic, support mutable and serializable state inside)\n\t\t* Unit callbacks/events (ex. When Attacked/Cast/Killed/Healed/Stunned/Silenced, On Attack/Cast/Kill/Heal)\n\t\t* Effect callbacks\n\t\t* Custom signature callbacks\n* Effect implementation examples\n\t* Damage (\u0026 self damage)\n\t* Heal\n\t* Status effects (stun, silence, disarm, etc.) \u0026 immunity\n\t* [Multi instance Status effects](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/Effects/StatusEffectEffect.cs)\n\t  [impl](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/StatusEffect/MultiInstanceStatusEffectController.cs)\n\t  (2 same stuns, 2 different sources, unique timers)\n\t* Dispel status effect(s)\n\t* Add stat (Damage, Heal)\n\t* Actions (Attack, Heal, Cast, etc.)\n\t* Centralized Effect\n\t* And more, see [the rest](ModiBuff/ModiBuff.Units/Effects)\n* Internal Effects\n\t* [Special Applier (another Modifier)](#applier-effect)\n\t\t* Applying Applier Modifiers as Applier Modifiers\n\t* Modifier Action (refresh, reset stacks, custom stack)\n\t* Remove (remove modifier)\n\t* Revert Action\n* Modifierless-Effects\n* Meta \u0026 Post effect manipulation (ex. lifesteal)\n* Stack Logic\n\t* Custom (callback/interval/duration triggers)\n\t* Stack timer\n\t* Independent stack timers\n\t* Revertable independent stacks\n* Condition implementations (checks)\n\t* Chance 0-100%\n\t* Cooldown\n\t\t* Charges (multiple use with cooldown)\n\t* Health/Mana cost, flat \u0026 percent\n\t* General:\n\t\t* Stat (health/mana/damage) \u003e/=/\u003c than X\n\t\t* Stat is full/empty\n\t\t* Has LegalAction (can attack, cast spell, move, etc.)\n\t\t* Has StatusEffect (stunned, silenced, disarmed, etc.)\n\t\t* Has Modifier\n* Applier Modifiers\n\t* OnAttack\n\t* Cast\n* Fully revertible effects\n* Manual modifier generation (for full control)\n* Open generic serialization (of all mutable state and id's)\n\t* System.Text.Json\n\n# Short showcase\n\nWe'll start off super basic, this is a modifier that deals 5 damage on target unit when added.\nRecipe explanations are down in the [Usage](#usage) section.\n\n```csharp\nAdd(\"InitDamage\")\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\nLet's bring out the heavy stuff. These will be very complex, but will show the capabilities of the library.\nSo **don't** be scared off by the code complexity, this is (almost) as complex as it gets. Only limited by imagination.\n\n```csharp\nAdd(\"DamageOnStun_HealOnAnyNotStunStatusEffectRemoved_StackDamageWhenLongStunned\")\n    .Tag(TagType.CustomStack | TagType.CustomRefresh)\n    .Interval(2f).Refresh()\n    .ModifierAction(ModifierAction.ResetStacks, EffectOn.Interval)\n    .Stack(WhenStackEffect.Always)\n    .ModifierAction(ModifierAction.Stack | ModifierAction.Refresh, EffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.DamageChanged, effect =\u003e new DamageChangedEvent(\n        (source, newDamage, deltaDamage) =\u003e\n        {\n            if (deltaDamage \u003e= 1f)\n                effect.Effect(source, source);\n        }))\n    .Effect(new DamageEffect(5, true, StackEffectType.Add, 2), EffectOn.Stack | EffectOn.CallbackEffect2)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e new AddStatusEffectEvent(\n        (target, source, duration, statusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (statusEffect.HasStatusEffect(StatusEffectType.Stun))\n                effect.Effect(target, source);\n        }))\n    .Effect(new HealEffect(5, HealEffect.EffectState.ValueIsRevertible, StackEffectType.Add, 2),\n        EffectOn.Stack | EffectOn.CallbackEffect3)\n    .CallbackEffect(CallbackType.StatusEffectRemoved, effect =\u003e new RemoveStatusEffectEvent(\n        (target, source, statusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (!statusEffect.HasStatusEffect(StatusEffectType.Stun))\n                effect.Effect(target, source);\n        }));\n```\n\nIt's a mouthful, but if we split up the effects it's easy to understand.\n\n1. Every time a stun status effect gets **added**, we deal 5 damage to the unit.\n2. Every time a non-stun status effect (ex. freeze, root) gets **removed** (either by dispel, or duration), we heal the\n   unit for 5.\n3. Every time the source unit changes its damage value, we add 2 damage and heal value stacks to effects above, each\n   time.\n4. Every 2 seconds, we reset the stacks on above damage and heal effects\n5. If the unit gets extra damage, it refreshes the 2 seconds stack timer (so the stacks don't get removed)\n\n# RoadMap\n\n| V0.4.0-V0.?.0                                   | V1.0.0                                                         |\n|-------------------------------------------------|----------------------------------------------------------------|\n| GenId based\u003cbr/\u003emodifier stacking               | Fully released open source\u003cbr/\u003egame using ModiBuff at its core |\n| Proper game samples\u003cbr/\u003e(Godot and maybe Unity) | 98% of game mechanics\u003cbr/\u003eimplementable                        |\n| Improved Appliers API?                          | ...                                                            |\n\n# Benchmarks\n\nTL;DR: It's fast. Takes 1ms± to add \u0026 update 10000 modifiers every frame.\n\nBenchmarkDotNet v0.13.6, EndeavourOS  \nIntel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical and 4 physical cores  \n.NET SDK 6.0.120  \n.NET 6.0.20 (6.0.2023.36801), X64 RyuJIT AVX2\n\n**N: 10_000**  \nDelta: 0.0167 * N\n\n#### Add/Apply/Update Modifier table\n\nPre-allocated Pools\n\n| Library                                               | NoOp*\u003cbr/\u003e(1 unit) | Apply\u003cbr/\u003eInitDmg\u003cbr/\u003e(1 unit) | Apply\u003cbr/\u003eInitStackDmg\u003cbr/\u003e(1 unit) | Apply Multi\u003cbr/\u003einstance DoT |\n|-------------------------------------------------------|--------------------|--------------------------------|-------------------------------------|------------------------------|\n| ModiBuff (this)                                       | 0.18ms, 0 B        | 0.26ms, 0 B                    | 0.44ms, 0 B                         | 1.01ms, 0 B                  |\n| [ModiBuffEcs](https://github.com/Chillu1/ModiBuffEcs) | ?                  | 1.02ms, 0 GC                   | ?                                   | X                            |\n| [Old](https://github.com/Chillu1/ModifierLibrary)     | ?                  | 21.4ms, 24 GC                  | ?                                   | X                            |\n\n| Library                                               | Update DoT**\u003cbr/\u003e(10_000 units, N:1) | Update Instance\u003cbr/\u003eStackable DoT |\n|-------------------------------------------------------|--------------------------------------|-----------------------------------|\n| ModiBuff (this)                                       | 1.96ms, 0 B                          | 0.13ms, 0 B                       |\n| [ModiBuffEcs](https://github.com/Chillu1/ModiBuffEcs) | 0.44ms, 0 B                          | X                                 |\n| [Old](https://github.com/Chillu1/ModifierLibrary)     | ?                                    | X                                 |\n\n#### New Modifier/Pool table\n\n| Library                                               | DoT pool rent | DoT pool\u003cbr/\u003ereset return |\n|-------------------------------------------------------|---------------|---------------------------|\n| ModiBuff (this)                                       | 0.04ms, 0 B   | 0.18ms, 0 B               |\n| [ModiBuffEcs](https://github.com/Chillu1/ModiBuffEcs) | 1.64ms, 0 GC  | 4.26ms, 0 GC              |\n| [Old](https://github.com/Chillu1/ModifierLibrary)     | X             | X                         |\n\n\u003e Important: Non-pool (\"New\") benchmarks below don't matter for runtime performance in ModiBuff and ModiBuffEcs, since\n\u003e it will only be\n\u003e slower when allocating the new modifiers in the pools. Which if handled correctly, should only happen on\n\u003e initialization.\n\n| Library                                               | New\u003cbr/\u003eInitDmg\u003cbr/\u003eManual | New\u003cbr/\u003eInitDmg\u003cbr/\u003eRecipe | New\u003cbr/\u003eDoT*\u003cbr/\u003eRecipe |\n|-------------------------------------------------------|----------------------------|----------------------------|-------------------------|\n| ModiBuff (this)                                       | 0.54ms, 2.2 MB             | 1.44ms, 2.8 MB             | 2.99ms, 6.0 MB          |\n| [ModiBuffEcs](https://github.com/Chillu1/ModiBuffEcs) | X                          | 10.4ms,   2 GC             | 16.7ms,   2 GC          |\n| [Old](https://github.com/Chillu1/ModifierLibrary)     | 92.0ms,  90 GC             | X                          | 140 ms, 126 GC          |\n\nSetting up all recipes, with 64 pool allocation per recipe takes 60ns, and 104KB.\n\nPre-allocating 1_000 modifiers of each recipe (currently 100±) takes 67ms, and 35MB.\n\nPooling in ModiBuff is 500X faster than original old version (because of pool rent \u0026 return)  \nBut it's also much faster in cases of doing init/stack/refresh on an existing modifier (we don't create a new modifier\nanymore)  \nModiBuffEcs is a bit on the slow side for now, because of how pooling works, with enabling and disabling entities.\n\n*NoOp is an empty effect, so it just measures the benchmark time of the library without unit logic (ex. taking\ndamage).  \n**DoT = InitDoTSeparateDamageRemove\n\n# Requirements\n\nModiBuff is compatible with .NETStandard 1.1 and .NETStandard 2.0, C# 7.2 (C# 7.0 is also possible, take a look\nat [Godot Branch](https://github.com/Chillu1/ModiBuff/tree/godot))\n\nFor development net 6.0 is required to build and run all tests. The tests depend on NUnit, and benchmarks depend on\nBenchmarkDotNet.\n\n# Installation\n\nCurrently the library is on [NuGet](https://www.nuget.org/packages/ModiBuff/) and\n[Godot Asset Library](https://godotengine.org/asset-library/asset/2166), it will also be coming to Unity Asset Store\nat some point.\n\n\u003e Note: It's recommended to use the source/non-DLL/debug version of the library in development, since it has\n\u003e useful logging/debugging features.\n\n## Unity\n\n\u003e Important: Unity installation is currently not maintained, you will be missing new files and their meta files.\n\u003e Ideally, download the source code manually.\n\nInstall package from git url: https://github.com/Chillu1/ModiBuff.git#upm\n\n## Step by step installation\n\n1. Download the latest DLL from [Releases](https://github.com/Chillu1/ModiBuff/releases) or ModiBuff source code.\n2. Add the DLL to your project.\n3. Make your own `ModifierRecipes` class that either\n   [inherits](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/TestModifierInheritanceRecipes.cs)\n   from `ModiBuff.ModifierRecipes`or\n   [encapsulates](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/TestModifierRecipes.cs)\n   it and fill it with your modifier recipes.\n4. Make your own logger implementation, by inheriting `ILogger`, or use one of the built-in ones.\n5. Call ModiBuff setup systems in the initialization of your game.    \n   5.1. You can change the internal config values inside `Config`\n\n```csharp\nLogger.SetLogger\u003cMyLogger\u003e();\n//Config.MaxPoolSize = 10000;\n\nvar idManager = new ModifierIdManager();\n_recipes = new ModifierRecipes(idManager); //Your version of ModifierRecipes\n_pool = new ModifierPool(_recipes);\n```\n\nIf you want to use the Units implementation, go to [ModiBuff.Units](#modibuffunits).\nOtherwise go to [Custom Units](#custom-units).\n\n### ModiBuff.Units\n\n6. Download the latest ModiBuff.Units DLL from [Releases](https://github.com/Chillu1/ModiBuff/releases) or ModiBuff\n   source code.\n7. Add the DLL to your project.\n8. Now you can create your units, and apply modifiers to them.\n\n### Custom Units\n\n6. Implement `IUnit` and `IModifierOwner` interfaces on your unit class.  \n   6.1. Optionally add some of the ModiBuff.Units `IUnit` [interfaces](ModiBuff/ModiBuff.Units/Unit/Interfaces) that you\n   want to use.\n7. Create your own interfaces that your effects will use, and implement them on your unit class.\n\n# Usage\n\n\u003e Important: this readme is updated often, and is usually on par with the master branch code.\n\u003e If you're not using master branch code, go to [V0.3.0](https://github.com/Chillu1/ModiBuff/tree/0.3.0#usage)\n\u003e or [V0.2.0](https://github.com/Chillu1/ModiBuff/tree/0.2.0#usage) readme versions for the most accurate usage.\n\n## Recipe\n\nModifier Recipes are the high level API for creating modifiers, they use the builder pattern/method chaining/fluent\ninterface to create modifiers (without the need for calling a Finish/Complete method).\n\nEasiest modifier, that does 5 damage when added, can be created like this:\n\n```csharp\nAdd(\"InitDamage\")\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\nMore advanced damage over time modifier, with 10 damage when added, and 5 damage per interval.  \nRemoved after 3 seconds, with the duration being refreshable.\n\n```csharp\nAdd(\"Init_DoT_Remove_Refreshable\")\n    .Interval(1)\n    .Effect(new DamageEffect(10), EffectOn.Init)\n    .Effect(new DamageEffect(5), EffectOn.Interval)\n    .Remove(3)\n    .Refresh();\n```\n\nYou're also able to create modifiers with same effect instance on multiple actions.  \nEx. Same damage on Init and Interval.\n\u003e Note: init will be triggered each time we try to add the modifier to the unit (unless we set `.OneTimeInit()`).\n\n```csharp\nAdd(\"InitDoT\")\n    .Interval(1)\n    .Effect(new DamageEffect(10), EffectOn.Init | EffectOn.Interval);\n``` \n\nAny IEffect can be added.  \nEx. simple stun effect\n\n```csharp\nAdd(\"InitStun\")\n    .Effect(new StatusEffectEffect(StatusEffectType.Stun, 2), EffectOn.Init)\n    .Remove(2);\n```\n\n## In-depth Recipe Creation\n\nEvery modifier needs a unique name.\nIt will be registered under this name in the backend,\nand assigned an unique ID.\n\n```csharp\nAdd(\"ModifierName\")\n```\n\nRecipes have a methods that determine the functionality of the made modifier.\n\n### Recipe Effect\n\nThe main method to setup effects is `Effect(IEffect, EffectOn)`.  \n`IEffect` is the effect that will be applied to the unit, it can be anything, as long as it implements IEffect\ninterface.  \n`EffectOn` is the action that will trigger the effect: Init, Interval, Duration, Stack.\n`EffectOn` is a flag enum, so the effect can be triggered on multiple actions.  \n`Targeting` tells the effect how it should be targeted, if we should target the owner (source) of the modifier, or the\ntargeted unit.\nIf you're unsure what this means, leave it at default. There will be more examples of this later.\n\n```csharp\nAdd(\"InitDamage\")\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\n### Interval\n\nThe first most common action method is `Interval(float)`. It's used to set the interval of the modifier.\nAnd interval effects will be triggered every X seconds.\n\n```csharp\nAdd(\"IntervalDamage\")\n    .Interval(1)\n    .Effect(new DamageEffect(5), EffectOn.Interval);\n```\n\n### Duration\n\nNext is `Duration(float)`. It's used to set the duration of the duration effects. It's usually used to remove the\nmodifier after X seconds.\nBut it can be used for any effect.\n\n```csharp\nAdd(\"InitDamageDurationRemove\")\n    .Effect(new DamageEffect(5), EffectOn.Init)\n    .Effect(new RemoveEffect(), EffectOn.Duration)\n    .Duration(5);\n```\n\n\u003e Note: When we want to remove the modifier after X seconds, it's simpler to use the `Remove(float)` method,\n\u003e which is just a QoL wrapper for `Duration(float)`.\n\n```csharp\nAdd(\"InitDamageDurationRemove\")\n    .Effect(new DamageEffect(5), EffectOn.Init)\n    .Remove(5);\n```\n\n### Refresh\n\nThen we have `Refresh(RefreshType)` method. That makes either the interval or duration component refreshable.\nMeaning that if a modifier gets added again to a unit, it will refresh the timer. This is most often used with the\nduration timer.\n\n```csharp\nAdd(\"DamageOverTimeRefreshableDuration\")\n    .Interval(1)\n    .Effect(new DamageEffect(5), EffectOn.Interval)\n    .Effect(new RemoveEffect(), EffectOn.Duration)\n    .Duration(5)\n    .Refresh(RefreshType.Duration);\n```\n\nCalling `Refresh()` without any arguments will make the last time (interval/duration) component refreshable.\n\n```csharp\nAdd(\"DamageOverTimeRefreshableDuration\")\n    .Interval(1)\n    .Effect(new DamageEffect(5), EffectOn.Interval)\n    .Effect(new RemoveEffect(), EffectOn.Duration)\n    .Duration(5).Refresh();\n```\n\n### Stack\n\nThen there's\n`Stack(WhenStackEffect whenStackEffect, int maxStacks, int everyXStacks, float singleStackTime, float independentStackTime)`.\nIt's used for tracking how many times the modifier has been re-added to the unit, or other stacking logic.\n\n`WhenStackEffect` tells the modifier when the stack action should be triggered: Always, OnMaxStacks, EveryXStacks,\netc.  \n`MaxStacks` limits on how many stacks the modifier can have.  \n`EveryXStacks` makes it possible to trigger the stack action every X stacks.  \n`SingleStackTime` adds a single stack timer, that will remove and revert all stacks after X seconds.\nUnless refreshed through a stack action.  \n`IndependentStackTime` adds a stack timer for each stack, that will remove and revert a single stack every X seconds.\nIndependent stacks are non-refreshable.\n\nMost stack effects also have a `StackEffectType` parameter, that tells the effect what to do when the stack action is\ntriggered. It can triggers the effect, add some kind of value to the effect, or other custom logic.\n\nIn this example we deal 5 damage every 1 second, but each time we add the modifier, we add 2 damage to the effect.\nResulting in 7 damage every 1 second with 1 stack. 9 with 2 stacks, etc.\n\n```csharp\nAdd(\"StackableDamage_DamageOverTime\")\n    .Interval(1)\n    .Effect(new DamageEffect(5, StackEffectType.Add, 2), EffectOn.Interval)\n    .Stack(WhenStackEffect.Always);\n```\n\n### OneTimeInit \u0026 Aura\n\n`OneTimeInit()` makes the modifier only trigger the init effect once, when it's added to the unit.\nAny subsequent adds will not trigger the init effects, but refresh and stack effects will still work as usual.\nThis is very useful for aura modifiers, where we don't want to stack the aura effect.\n\n\u003e After commit [d3cb4a6](https://github.com/Chillu1/ModiBuff/commit/d3cb4a6220ff0ea0260750b68f451bc06091332e)\n\u003e aura handling was refactored and improved.\n\nWe can tell the recipe that it will trigger effects on multiple units at once\nwith `Aura()`. We can also specify multiple aura Ids, which can be used as range dictionaries instead.\n\n```csharp\nAdd(\"InitAddDamageBuff\")\n    .OneTimeInit() // EffectState.IsRevertibleAndTogglable can be used instead of OneTimeInit\n    .Effect(new AddDamageEffect(5, EffectState.IsRevertible), EffectOn.Init)\n    .Remove(1.05f).Refresh();\nAdd(\"InitAddDamageBuff_Interval\")\n    .Aura()\n    .Interval(1)\n    .Effect(new ApplierEffect(\"InitAddDamageBuff\"), EffectOn.Interval);\n```\n\n### InstanceStackable\n\n`InstanceStackable()` makes the modifier instance stackable, meaning that we can have\nmultiple instances of the same modifier on the same unit.\n\nThis will impose a slight performance penalty, and will require to use unique `genId` for each instance.\n\nThis example deals 5 damage every 1 second, the remove timer can't be refreshed by normal means.\nAnd each new call to add the modifier will add a new instance of the modifier.\n\n```csharp\nAdd(\"InstanceStackableDoT\")\n    .InstanceStackable()\n    .Interval(1)\n    .Effect(new DamageEffect(5), EffectOn.Interval)\n    .Remove(5);\n```\n\n### Meta-Effect\n\nEffects can store meta effects, that will manipulate the effect values with optional conditions.\nMeta effects, just like normal effects, are user-generated. Note that meta-effect can't have **mutable** state.\nAny mutable state should be stored in the effect itself or on the unit.\nEffects support using many meta effects after each other. Allowing for very complex interactions.\n\nThis example scales our 5 damage value based on the source unit's health multiplied by it.\n\n```csharp\nAdd(\"InitDamageValueBasedOnHealthMeta\")\n    .Effect(new DamageEffect(5)\n        .SetMetaEffects(new StatPercentMetaEffect(StatType.Health, Targeting.SourceTarget)), EffectOn.Init);\n```\n\n### Post-Effect\n\nPost-effects can be chained with meta-effects.\n\nPost effects are effects that are applied after the main effect. And use the main effect as a source for their values.\nMost common example of this is lifesteal, where we deal damage, and then heal for a percentage of the damage dealt.\nPost effects also can't have any mutable state, and work fully in conjunction with meta effects.\n\nThis example deals 5 damage on init, and then heals for 50% of the damage dealt.\n\n```csharp\nAdd(\"InitDamageLifeStealPost\")\n    .Effect(new DamageEffect(5)\n        .SetPostEffects(new LifeStealPostEffect(0.5f, Targeting.SourceTarget)), EffectOn.Init);\n```\n\n### Apply \u0026 Effect Condition (checks)\n\nModifiers can have conditions, that will check if the modifier/target/source fulfills the condition before applying the\nmodifier.\n`ModiBuff.Units` has a few built-in conditions, and custom conditions are fully supported.\nThe common conditions are: cooldown, mana cost, chance, status effect, etc.\n\nThis example deals 5 damage on init apply, only if:\nthe source unit has at least 5 mana (uses up that mana), has at least 10% hp (uses up the hp),\npasses the 50% roll, is not on 1 second cooldown, source is able to act (attack, heal), and target is silenced.\n\n```csharp\nAdd(\"InitDamage_CostMana\")\n    .ApplyCost(CostType.Mana, 5)\n    .ApplyCostPercent(CostType.Health, 0.1f)\n    .ApplyChance(0.5f)\n    .ApplyCooldown(1f)\n    .ApplyCondition(LegalAction.Act)\n    .EffectCondition(LegalAction.Silence)\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\nThere's also `ChargeCooldown` that works like normal cooldown, but with charges.\n\n```csharp\nAdd(\"InitDamage_ChargesCooldown\")\n    .ApplyChargesCooldown(cooldown: 1, charges: 2)\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\n### Callback\n\n\u003e As of commit [a8ad4a6](https://github.com/Chillu1/ModiBuff/commit/a8ad4a6e962d21061b4beca4533f5581f6289106)\n\u003e (after V0.3.0), events were removed, and replaced entirely with callbacks. They're entirely the same.\n\nCallbacks are a way to call effects on certain unit events.\nExamples of some are: When Attacked, When Killed, On Attack, On Kill, On Cast, etc.\n\nSome common examples of this are: thorns, restore health on cast, add damage on kill.\n\nCallbacks are the most powerful tool to use together with game logic, they can use data sent through the event,\nand have mutable serializable state inside it.\n\nCallbacks are a way to add logic that can be triggered on any user/game-based action.\nThis is particularly useful for removing modifiers on certain non-standard cases.\n\nIn this example we add 5 damage to unit on Init, and the modifier can only be removed if the unit gets hit by a \"\nStrongHit\".\nEssentially a hit that deals more than half units health in damage (ex. game logic).\n\n\u003e Important: all versions before 0.4/latest master can only have one callback `CallbackUnit` per modifier.\n\n```csharp\nAdd(\"InitAddDamageRevertibleHalfHealthCallback\")\n    .Effect(new AddDamageEffect(5, EffectState.IsRevertible), EffectOn.Init)\n    .Remove(RemoveEffectOn.CallbackUnit)\n    .CallbackUnit(CallbackUnitType.StrongHit);\n```\n\nIt's possible to use any IEffect for callbacks,\nso we can for example heal the unit to full health every time they get hit by a \"StrongHit\".\n\n```csharp\nAdd(\"InitHealToFullWhenStrongHitCallback\")\n    .Effect(new HealEffect(0)\n        .SetMetaEffects(new AddValueBasedOnStatDiffMetaEffect(StatType.MaxHealth)), EffectOn.Callback)\n    .CallbackUnit(CallbackUnitType.StrongHit);\n```\n\nThere's another version of callbacks, based on delegates instead of IEffects.\nIt can be useful for simple one-off effects.\n\n```csharp\nAdd(\"InitHealToFullHalfHealthCallback\")\n    .Callback(CallbackType.StrongHit, (target, source) =\u003e\n    {\n        var damageable = (IDamagable\u003cfloat, float\u003e)target;\n        ((IHealable\u003cfloat, float\u003e)target).Heal(damageable.MaxHealth - damageable.Health, source);\n    });\n```\n\n\u003e Note that no effect or state can be defined or saved here, being mostly a downside.\n\n#### Callback with mutable state\n\nIt's possible for callbacks to have internal mutable state, but this state should be handled carefully.\nIt's important to reset the state when our condition is met first, before we trigger any effects.\n\nIn this example every time unit's health changes, we add that change to our total, and when we reach 10 damage taken,\nwe deal 5 damage to the unit.\n\nThe point of `CallbackStateContext` is to encapsulate state, and have read/write access to it for serialization\npurposes.\n\n```csharp\nAdd(\"InitTakeFiveDamageOnTenDamageTaken\")\n    .Callback(CallbackType.CurrentHealthChanged, () =\u003e\n    {\n        float totalDamageTaken = 0f;\n\n        return new CallbackStateContext\u003cfloat\u003e(new HealthChangedEvent(\n            (target, source, health, deltaHealth) =\u003e\n            {\n                if (deltaHealth \u003e 0)\n                    totalDamageTaken += deltaHealth;\n                if (totalDamageTaken \u003e= 10)\n                {\n                    totalDamageTaken = 0f;\n                    target.TakeDamage(5, source);\n                }\n            }), () =\u003e totalDamageTaken, value =\u003e totalDamageTaken = value);\n    });\n```\n\n#### Callback Effect\n\n`CallbackEffect` are special callbacks that trigger on `EffectOn.CallbackEffect` effects.\nThese callbacks get the effect fed as a parameter, this allows for condtional effect invoking, or custom effect use,\nlike manual stack trigger. Supports custom callback signatures.\n\n\u003e Important: all versions before 0.4/latest master can only have one callback `CallbackEffect` per modifier.\n\n```csharp\nAdd(\"SilenceSourceWhenSilenced\")\n    .Effect(new StatusEffectEffect(StatusEffectType.Silence, 2f), EffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e\n        new StatusEffectEvent((target, source, appliedStatusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (appliedStatusEffect.HasStatusEffect(StatusEffectType.Silence))\n                effect.Effect(source, target);\n        }));\n```\n\nIt's possible to have mutable state in this callback as well.\n\n```csharp\nAdd(\"StunnedFourTimesDispelAllStatusEffects\")\n    .Effect(new DispelStatusEffectEffect(StatusEffectType.All), EffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e\n    {\n        float totalTimesStunned = 0f;\n        return new CallbackStateContext\u003cfloat\u003e(\n            new StatusEffectEvent((target, source, statusEffect, oldLegalAction, newLegalAction) =\u003e\n            {\n                if (statusEffect.HasStatusEffect(StatusEffectType.Stun))\n                {\n                    totalTimesStunned++;\n                    if (totalTimesStunned \u003e= 4)\n                    {\n                        totalTimesStunned = 0f;\n                        effect.Effect(target, source);\n                    }\n                }\n            }), () =\u003e totalTimesStunned, value =\u003e totalTimesStunned = value);\n    })\n```\n\n### Multiple callbacks\n\nAs of commit [a8ad4a6](https://github.com/Chillu1/ModiBuff/commit/a8ad4a6e962d21061b4beca4533f5581f6289106)\n(after V0.3.0) the recipe system supports up to 4 different callbacks for each `EffectOn.Callback` type.\n\nThe signature becomes `EffectOn.Callback`, `EffectOn.Callback2`, etc.\nNote that the callback order matters, callbacks are sequential, first registered callback will be marked as first.\nSame callback type can be registered multiple times, with different event signatures.\n\nCallbackEffect example:\n\nDamage whenever target get's stunned, and heal whenever a status effect expires/gets removed that's not a stun.\n\n```csharp\nAdd(\"DamageOnStun_HealOnAnyNotStunStatusEffectRemoved\")\n    .Effect(new DamageEffect(5), EffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e\n        new StatusEffectEvent((target, source, appliedStatusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (appliedStatusEffect.HasStatusEffect(StatusEffectType.Stun))\n                ((ICallbackEffect)effect).CallbackEffect(target, source);\n        }))\n    .Effect(new HealEffect(5), EffectOn.CallbackEffect2)\n    .CallbackEffect(CallbackType.StatusEffectRemoved, effect =\u003e\n        new StatusEffectEvent((target, source, appliedStatusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (!appliedStatusEffect.HasStatusEffect(StatusEffectType.Stun))\n                ((ICallbackEffect)effect).CallbackEffect(target, source);\n        }));\n```\n\n### Modifier Actions\n\nSometimes we need extra control of what happens inside the modifier, with game logic.\nThis can be achieved with modifier actions, currently there's three: Refresh, ResetStacks and CustomStack.\n\nHere we have a delayed add damage, that triggers after 2 seconds.\nBut if a unit takes a \"StrongHit\", it will reset the timer.\n\n```csharp\nAdd(\"DurationAddDamageStrongHitRefresh\")\n    .Effect(new AddDamageEffect(5), EffectOn.Duration)\n    .ModifierAction(ModifierAction.Refresh, EffectOn.Callback)\n    .Callback(CallbackType.StrongHit)\n    .Duration(2).Refresh();\n```\n\nHere we add damage every 5 stacks of the modifier.\nBut if a unit takes a \"StrongHit\", it will reset the stacks count to 0.\n\n```csharp\nAdd(\"StackAddDamageStrongHitResetStacks\")\n    .Effect(new AddDamageEffect(5), EffectOn.Stack)\n    .ModifierAction(ModifierAction.ResetStacks, EffectOn.Callback)\n    .Callback(CallbackType.StrongHit)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 5);\n```\n\nCustom stack is an experimental modifier action, that might be removed in the future.\nIt allows to trigger the stack action as an modifier action.\n\nEx. every 4th stun, dispel all status effects.\n\n```csharp\nAdd(\"StunnedFourTimesDispelAllStatusEffects\")\n    .Tag(TagType.CustomStack)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 4)\n    .Effect(new DispelStatusEffectEffect(StatusEffectType.All), EffectOn.Stack)\n    .ModifierAction(ModifierAction.Stack, EffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e\n        new StatusEffectEvent((target, source, statusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (statusEffect.HasStatusEffect(StatusEffectType.Stun))\n                effect.Effect(target, source);\n        }));\n```\n\n### Remove Applier\n\nIt's possible to remove added appliers through recipes as well.\nWe just need to specify applier type, and if it has any apply checks (ex. like chance, cooldown, etc.).\n\n```csharp\nAdd(\"AddApplier_Effect\")\n    .Effect(new ApplierEffect(\"InitDamage\"), EffectOn.Init)\n    .RemoveApplier(5, ApplierType.Cast, false);\n```\n\n### Dispel\n\nModiBuff currently contains an internal dispel system, that doesn't yet allow for custom dispel logic through it.\n\n```csharp\nAdd(\"BasicDispellable\")\n    .Dispel(DispelType.Basic)\n    .Effect(new DamageEffect(5), EffectOn.Init);\n\nIModifierOwner.Dispel(DispelType dispelType, IUnit source)\n```\n\nIt's recommended to make your own dispel system inside the unit for better control, if needed.\nEx. through tags and callbacks.\n\n```csharp\nAdd(\"InitStatusEffectSleep_RemoveOnDispel\")\n    .Tag(TagType.BasicDispel)\n    .Effect(new StatusEffectEffect(StatusEffectType.Sleep, 5f, true), EffectOn.Init)\n    .Remove(RemoveEffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.Dispel, removeEffect =\u003e\n        new DispelEvent((target, source, eventTag) =\u003e\n        {\n            if ((TagType.BasicDispel \u0026 eventTag) != 0)\n                removeEffect.Effect(target, source);\n        }))\n```\n\n### Tags\n\nTags are a way to mark modifiers, there are a few internal tags that tell the `ModifierController` how the modifiers\nshould be handled. Tags are stored inside `ModifierRecipes` and can only be set on modifier generator creation.\n\nThen there's a few other internal tags that are there for the user:\n`IsInstanceStackable`, `IntervalIgnoresStatusResistance` and `DurationIgnoresStatusResistance`.\nThere's also automatic tagging for manual modifier generation of: `IsInit`, `IsStack`, and `IsRefresh`.\n\nThere's also a `TagType.Default` which we can use in the beginning to define our default tags in the config.\n`Config.DefaultTag = /*Your*/TagType.Default;`\n\nBut tagging also supports user-defined tags, that can be used for any purpose.\n\n`ModiBuff.Units` uses it to implement legal targeting for modifiers.\nWe need to respect the last reserved tag in `ModiBuff.Core.TagType`.\n\n```csharp\n[Flags]\npublic enum TagType : ulong\n{\n    Default = Core.TagType.Default | LegalTargetAll,\n\n    LastReserved = Core.TagType.LastReserved,\n\n    LegalTargetSelf = 1ul \u003c\u003c 17,\n    LegalTargetAlly = 1ul \u003c\u003c 18,\n    LegalTargetEnemy = 1ul \u003c\u003c 19,\n\n    LegalTargetAll = LegalTargetSelf | LegalTargetAlly | LegalTargetEnemy,\n    //UserTag5 = 1ul \u003c\u003c 21\n}\n```\n\nThen we can define some helper extension methods for checking if a unit is a legal target of the modifier.\n\n```csharp\npublic static bool IsLegalTarget(this int modifierId, IUnitEntity target, IUnitEntity source)\n{\n    var tag = (TagType)ModifierRecipes.GetTag(modifierId);\n    if (tag.HasTag(TagType.LegalTargetSelf) \u0026\u0026 target == source)\n        return true;\n\n    return tag.IsLegalTarget(target.UnitType, source.UnitType);\n}\n```\n\n```csharp\npublic static bool IsLegalTarget(this TagType tag, UnitType target, UnitType source)\n{\n    if (tag.HasTag(TagType.LegalTargetAlly) \u0026\u0026 target == source)\n        return true;\n    if (tag.HasTag(TagType.LegalTargetEnemy) \u0026\u0026 target != source)\n        return true;\n    if (tag.HasTag(TagType.LegalTargetAll))\n        return true;\n\n#if DEBUG\n    Logger.Log($\"Tag {tag} is not a legal target for UnitType.{target} from UnitType.{source}\");\n#endif\n    return false;\n}\n```\n\nSo we can finally setup our own tag for modifiers.\n\n\u003e Note that we're also using a default tag that sets all modifiers to be legal to target all units.\n\u003e So we need to remove that tag first.\n\n```csharp\nAdd(\"InitDamageEnemyOnly\")\n    .RemoveTag((Core.TagType)LegalTarget.All)\n    .Tag((Core.TagType)TagType.LegalTargetEnemy)\n    .Effect(new DamageEffect(5f), EffectOn.Init);\n```\n\nThen we can make a extension method for our logic, so we don't use the previous verbose `Tag` approach.\n\n```csharp\npublic static ModifierRecipe LegalTarget(this ModifierRecipe recipe, LegalTarget target)\n{\n    recipe.RemoveTag((Core.TagType)TagType.LegalTargetAll);\n    return recipe.Tag(target.ToTagType());\n}\n```\n\n```csharp\nAdd(\"InitDamageEnemyOnly\")\n    .LegalTarget(LegalTarget.Enemy)\n    .Effect(new DamageEffect(5f), EffectOn.Init);\n```\n\n### Custom Data\n\nSometimes the tag system is too limited for our needs, and we want to store custom modifier identification with data.\nThat's why every recipe stores a custom object, that can be accessed from anywhere like the tag.\nIt is mostly designed to store basic information about the modifier, one example of this is adding a modifier to every\nunit of type X (ex. Goblin).\nInstead of storing that information on the goblin unit data itself, we delegate it to the modifiers, also it makes it so\nwe don't need arbitrary naming conventions for our modifiers.\n\n```csharp\npublic enum EnemyUnitType\n{\n    Slime,\n    Goblin,\n    Orc,\n}\n\npublic enum ModifierAddType\n{\n    Self = 1,\n    Applier,\n}\n\npublic record AddModifierCommonData\u003cTUnit\u003e(ModifierAddType ModifierType, TUnit UnitType);\n\nAdd(\"Damage\")\n    .Data(new AddModifierCommonData\u003cEnemyUnitType\u003e(ModifierAddType.Self, EnemyUnitType.Goblin))\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\nIt also allows for a more standardized recipe creation, by unit types, modifier types, etc, reducing code duplication.\nAnd allowing for a set of standards, making the modifier creation less prone to errors.\n\n```csharp\nAddEnemySelfBuff(\"Damage\", EnemyUnitType.Goblin)\n    .Effect(new DamageEffect(5), EffectOn.Init);\n\nModifierRecipe AddEnemySelfBuff(string name, EnemyUnitType enemyUnitType, string displayName = \"\", string description = \"\") =\u003e\n    Add(name + enemyUnitType, displayName, description)\n        .Data(new AddModifierCommonData\u003cEnemyUnitType\u003e(ModifierAddType.Self, enemyUnitType));\n```\n\nThen to apply all the modifiers to the units, we filter through them.\n\n```csharp\nvar goblinSelfModifiers = new List\u003cint\u003e();\nforeach ((int id, var data) in ModifierRecipes.GetModifierData\u003cAddModifierCommonData\u003cEnemyUnitType\u003e\u003e())\n    if (data.UnitType == EnemyType.Goblin \u0026\u0026 data.ModifierType == ModifierAddType.Self)\n        goblinSelfModifiers.Add(id);\n```\n\nIt's also possible to create `ModifierRecipe` extensions instead, for ease of use.\nThis is recommended if you have multiple different entity type combinations, and actions.\n\n```csharp\npublic static ModifierRecipe Data(this ModifierRecipe recipe, ModifierAddType modifierAddType, EnemyUnitType enemyUnitType) =\u003e \n    recipe.Data(new AddModifierCommonData\u003cEnemyUnitType\u003e(modifierAddType, enemyUnitType));\n\nModifierRecipe AddEnemySelfBuffExtension(string name, EnemyUnitType enemyUnitType, string displayName = \"\", string description = \"\") =\u003e\n    AddRecipe(name + enemyUnitType, displayName, description)\n        .Data(ModifierAddType.Self, enemyUnitType);\n```\n\n\u003e Note: That modifier appliers should still be two separate modifiers, the applier and the applied modifier.\n\u003e Then we just add the appliers based on what we get from `ModifierRecipes.GetModifierData`, and apply them on\n\u003e actions/events.\n\n#### Advanced Custom Data\n\nIt's possible to use custom action types as well, possibly for non-generic actions tied to a unit type.\nEx. goblins having a special event to surrender, and us wanting to trigger some modifiers on certain goblin types.\n\n```csharp\npublic record AddModifierCommonData\u003cTModifier, TUnit\u003e(TModifier ModifierType, TUnit UnitType);\n\nAdd(\"RemoveDamageOnSurrender\")\n    .Data(new AddModifierCommonData\u003cGoblinModifierActionType, EnemyUnitType\u003e(GoblinModifierActionType.OnSurrender, EnemyUnitType.Goblin)\n    .Effect(new DamageEffect(-5), EffectOn.Init);\n```\n\n### Custom Stack\n\nStack is always triggered when we try to add the same type of modifier again.\nThis behaviour can be changed by using CustomStack logic. Modifier stack action might be removed/refactored in\na future release.\n\nThe most usual usage of this is to trigger the stack action on a custom case.\nIt's a way to glue callback logic to stacking behaviour.\n\nHere we dispel all status effects if the unit has been stunned 4 times.\n\n```csharp\nAdd(\"StunnedFourTimesDispelAllStatusEffects\")\n    .Tag(TagType.CustomStack)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 4)\n    .Effect(new DispelStatusEffectEffect(StatusEffectType.All), EffectOn.Stack)\n    .ModifierAction(ModifierAction.Stack, EffectOn.CallbackEffect)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e\n        new StatusEffectEvent((target, source, statusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (statusEffect.HasStatusEffect(StatusEffectType.Stun))\n                effect.Effect(target, source);\n        }));\n```\n\nHere we have a stacking heal that stacks based on the amount of times the unit has been stunned.\nThat stacking number gets reset every 10 seconds (interval), but the interval timer gets refreshed every time the unit\ngets stunned. Also the heal itself isn't revertible, but the value we stack is.\n\n```csharp\nAdd(\"StunHealStackReset\")\n    .Tag(Core.TagType.CustomStack)\n    .Stack(WhenStackEffect.Always)\n    .Effect(new HealEffect(0, HealEffect.EffectState.ValueIsRevertible,\n        StackEffectType.Effect | StackEffectType.Add, 5), EffectOn.Stack)\n    .CallbackEffect(CallbackType.StatusEffectAdded, effect =\u003e\n        new StatusEffectEvent((target, source, appliedStatusEffect, oldLegalAction, newLegalAction) =\u003e\n        {\n            if (appliedStatusEffect.HasStatusEffect(StatusEffectType.Stun))\n                effect.Effect(target, source);\n        }))\n    .ModifierAction(ModifierAction.ResetStacks, EffectOn.Interval)\n    .ModifierAction(ModifierAction.Refresh | ModifierAction.Stack, EffectOn.CallbackEffect)\n    .Interval(10).Refresh();\n```\n\n### Order\n\nFunctions should be used in the operation order, for clarity.\nThis is optional, except for parameterless refresh functions, which should be called right after interval/duration.\n\n```csharp\nAdd(\"Full\")\n    .OneTimeInit()\n    .ApplyCondition(ConditionType.HealthIsFull)\n    .ApplyCooldown(1)\n    .ApplyCost(CostType.Mana, 5)\n    .ApplyChance(0.5f)\n    .EffectCondition(ConditionType.HealthIsFull)\n    .EffectCooldown(1)\n    .EffectCost(CostType.Mana, 5)\n    .EffectChance(0.5f)\n    .Effect(new DamageEffect(5), EffectOn.Init)\n    .Effect(new DamageEffect(5), EffectOn.Stack)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 2)\n    .Interval(1)\n    .Effect(new DamageEffect(2), EffectOn.Interval)\n    .Remove(5).Refresh()\n    .Effect(new DamageEffect(8), EffectOn.Duration);\n```\n\nEach modifier should have at least one effect, unless it's used as a flag.\n\n### Encapsulating same code in method extensions\n\nWhen a game has many modifiers that share the same core logic, it's recommended to encapsulate the logic in method\nthrough method extensions.\nAn example of this from Chillu's test game, where whenever an enemy drops a resource their carrying, they lose that\nresources buff/debuff.\n\n```csharp\npublic static ModifierRecipe RemoveOnResourceDrop(this ModifierRecipe recipe, ResourceType resourceType) =\u003e\n    recipe.Remove(RemoveEffectOn.CallbackEffect)\n        .CallbackEffect(CallbackType.EnemyDropResource, effect =\u003e\n            new ResourceDroppedEvent((target, resource) =\u003e\n            {\n                if (effect is RemoveEffect removeEffect \u0026\u0026 resource.IsPrimary(resourceType))\n                    removeEffect.Effect(target, null);\n            }));\n\nAddResourceModifier(ResourceType.Red, ResourceModifierAction.OnDigested, \"Curse\", \"Resource eating curse\",\n        \"Deal damage with every resource eaten\")\n    .Effect(new DamageEffect(new MutableDamageStatData(10)), EffectOn.CallbackUnit)\n    .CallbackUnit(CallbackType.EnemyEatResource)\n    .Remove(15).Refresh()\n    .RemoveOnResourceDrop(ResourceType.Red); //Then we just call the method extension each time we we need to add the modifier of this type\n```\n\nThis allows for lower cognitive load, since it's easier to understand what \"RemoveOnResourceDrop(ResourceType.Red)\"\ndoes.\n\n## Recipe Limitations\n\n\u003e Note that these limitations don't matter for 95% of the use cases.\n\n* One Interval Component\n* One Duration Component\n* One Modifier Check for all effects\n* Same Checks (cost, chance, cooldown) for all effects\n\n## Adding Modifiers To Units\n\nFor units to be able to use and own modifiers, they need to implement `IModifierOwner` interface.\nA `ModifierControllerPool` should be used to avoid runtime heap allocations. The pool needs to be initialized manually.\nThen it can be rented inside the unit like so: `ModifierController = ModifierControllerPool.Instance.Rent();`.\n\nFor units to be able to use and own modifier appliers, they need to implement `IModifierApplierOwner` interface.\nIt also has a pool, and works the same way as `ModifierController`.\n\nThere's multiple ways to add modifiers to a unit.\n\nFor normal modifiers, the best approach is to use `IModifierOwner.AddModifier(int id, IUnit source)`.\nBy feeding the modifier ID, and the source unit. These modifiers are stored and managed by `ModifierController`.\n\nFor applier (attack, cast, etc) modifiers,\n`IModifierApplierOwner.ModifierApplierController.TryAddApplier(int id, bool hasApplyChecks, ApplierType applierType)`\nshould be used.\n\nFor aura modifiers, units need to store the aura collection of targets themselves.\nThen they need to implement `IAuraOwner` so the modifiers can access these targets.\n\nThis is also the case for unit callbacks, like `OnKill`, `OnAttack`, `WhenDeath`, etc.\nThrough `ICallbackUnitRegistrable.RegisterCallbacks(TCallbackUnit, IEffect[])`.\n\n## Effect\n\n[Skip effect creation](#conditions-in-effects)\n\n### Making New Effects\n\nThe library allows for easy creation of new effects.\nWhich are needed for using custom game-based logic.\n\nEffects have to implement `IEffect`.  \nThey can also implement `IStackEffect` for stacking functionality, `IStateEffect` for resetting runtime state.\n\nFor fully featured effect implementation, look at\n[DamageEffect](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/Effects/DamageEffect.cs)\n\n#### In-depth Effect Creation\n\nWe start by creating a new class that implements `IEffect`.  \n`IEffect` has a method `void Effect(IUnit target, IUnit source);` that gets fed the target and source of the\ncast/attack/apply.\n\nThe next important thing is to identify if our effect will have mutable state.\nIt will have state if we plan on reverting it, adding more value to it, or changing internal effect state.\nIn that case, we need to implement `IStateEffect`, which has a method `void ResetState();` that gets called when the\nmodifier is sent back to pool, and also a clone `IEffect ShallowClone()` method. For cloning the effect so the state is\nnot shared between modifiers.\n\nIf we want to use stack logic, the effect needs to implement `IStackEffect`.\n\nThe effects hould have two constructors, the first main one should be used for recipes,\nand only expose the non-recipe setting variables.\nThe other constructor should be in a factory pattern style, and expose all the variables.\n\n##### Designing an Effect from scratch\n\nLet's design a damage effect from scratch. It first needs to implement `IEffect`.\n\n```csharp\npublic class DamageEffect : IEffect\n{\n    private readonly float _damage;\n\n    public DamageEffect(float damage)\n    {\n        _damage = damage;\n    }\n\n    public void Effect(IUnit target, IUnit source)\n    {\n        ((IDamagable)target).TakeDamage(_damage, source);\n    }\n}\n```\n\nThe changed parts are the only ones showed in new implementations.\n\nOkay, but what if we want to be able to use the damage effect as thorns? As in the source should be the one damaged.\nNot the target. We can do this by using `Targeting`.\n\n```csharp\npublic class DamageEffect : IEffect\n{\n    private Targeting _targeting;\n\n    public DamageEffect(float damage, Targeting targeting = Targeting.TargetSource)\n    {\n        _damage = damage;\n        _targeting = targeting;\n    }\n\n    public void Effect(IUnit target, IUnit source)\n    {\n        _targeting.UpdateTargetSource(ref target, ref source);\n        ...\n    }\n}\n```\n\n`_targeting.UpdateTargetSource(ref target, ref source);` just changes around who's the target and who's the source,\n\nThis way we can tell the effect who's the target (of the effect), and who's the source.\nThe original source can be both the target and source if desired (`Targeting.SourceSource`).\n\nOkay, but what about adding state to the effect? Ex. extra damage on stack.\nBefore we implement this, we need to understand that stateful effects need special handling.\n\nAll stateful effects need to implement `IStateEffect`. `IStateEffect` needs three methods:  \n`void ResetState();` resets the mutable state of the effect to it's default values.  \n`IEffect ShallowClone();` clones the effect, so the state is not shared between modifiers.  \n`object IShallowClone.ShallowClone();` is a object wrapper for the `ShallowClone()` method.\n\n```csharp\npublic class DamageEffect : IEffect, IStateEffect\n{\n    private float _extraDamage;\n\n    ...\n        \n    public void Effect(IUnit target, IUnit source)\n    {\n        ...\n        ((IDamagable)target).TakeDamage(_damage + _extraDamage, source);\n    }\n    \n    public void ResetState() =\u003e _extraDamage = 0;\n\n    public IEffect ShallowClone() =\u003e new DamageEffect(_damage, _targeting);\n    object IShallowClone.ShallowClone() =\u003e ShallowClone();\n}\n```\n\nNow that we have modifier state, we can add stack logic to the effect.\nThis can be added through `IStackEffect`.\n\n```csharp\npublic class DamageEffect : IEffect, IStateEffect, IStackEffect\n{\n    private readonly StackEffectType _stackEffect;\n    private readonly float _stackValue;\n\n    public DamageEffect(float damage, StackEffectType stackEffect, float stackValue, Targeting targeting)\n    {\n        _damage = damage;\n        _stackEffect = stackEffect;\n        _stackValue = stackValue;\n        _targeting = targeting;\n    }\n\n    ...\n\n    public void StackEffect(int stacks, IUnit target, IUnit source)\n    {\n        if ((_stackEffect \u0026 StackEffectType.Add) != 0)\n            _extraDamage += _stackValue;\n\n        if ((_stackEffect \u0026 StackEffectType.AddStacksBased) != 0)\n            _extraDamage += _stackValue * stacks;\n\n        if ((_stackEffect \u0026 StackEffectType.Effect) != 0)\n            Effect(target, source);\n    }\n\n    ...\n}\n```\n\n`StackEffectType` is a generic flag enum, that works with most stack logic, but not all, so feel free to make your own.\n\nAll right, but what about reverting the effect? We need to introduce a new variable to store how much the total value\nchanged.\nWe also need to implement `IRevertEffect`.\n\n```csharp\npublic class DamageEffect : IEffect, IStateEffect, IStackEffect, IRevertEffect\n{\n    public bool IsRevertible { get; }\n\n    private float _totalDamage;\n\n    public DamageEffect(float damage, StackEffectType stackEffect,\n        float stackValue, bool revertible, Targeting targeting)\n    {\n        _damage = damage;\n        _stackEffect = stackEffect;\n        _stackValue = stackValue;\n        IsRevertible = revertible;\n        _targeting = targeting;\n    }\n\n    public void Effect(IUnit target, IUnit source)\n    {\n        if (IsRevertible)\n            _totalDamage += _damage + _extraDamage;\n        \n        ...\n    }\n\n    ...\n\n    public void RevertEffect(IUnit target, IUnit source)\n    {\n        //Might be smart to have it's own RevertDamage method here, so we don't trigger damage based callbacks.\n        //Or heal callbacks\n        ((IDamagable)target).RevertDamage(_totalDamage, source);\n        //((IDamagable)target).TakeDamage(-_totalDamage, source);\n        _totalDamage = 0;\n    }\n\n    public void ResetState() \n    {\n        _extraDamage = 0;\n        _totalDamage = 0;\n    }\n\n    ...\n}\n```\n\nNow let's look at post and meta effects.\n`IMetaEffectOwner\u003cTEffect, TInValue, TOutValue\u003e` is a helper interface for adding meta effects to effects.\n`IPostEffectOwner\u003cTEffect, TInValue\u003e` is a helper interface for adding post effects to effects.\n\n```csharp\npublic class DamageEffect : IEffect, IStateEffect, IStackEffect, IRevertEffect,\n    IMetaEffectOwner\u003cDamageEffect, float, float\u003e, IPostEffectOwner\u003cDamageEffect, float\u003e\n{\n    private IMetaEffect\u003cfloat, float\u003e[] _metaEffects;\n    private IPostEffect\u003cfloat\u003e[] _postEffects;\n\n    ...\n\n    private DamageEffect(... , IMetaEffect\u003cfloat, float\u003e[] metaEffects, IPostEffect\u003cfloat\u003e[] postEffects)\n    {\n        ...\n        _metaEffects = metaEffects;\n        _postEffects = postEffects;\n    }\n\n    public DamageEffect SetMetaEffects(params IMetaEffect\u003cfloat, float\u003e[] metaEffects)\n    {\n        _metaEffects = metaEffects;\n        return this;\n    }\n\n    public DamageEffect SetPostEffects(params IPostEffect\u003cfloat\u003e[] postEffects)\n    {\n        _postEffects = postEffects;\n        return this;\n    }\n\n    public void Effect(IUnit target, IUnit source)\n    {\n        _targeting.UpdateTargetSource(ref target, ref source);\n\n        float damage = _damage;\n\n        if (_metaEffects != null)\n            foreach (var metaEffect in _metaEffects)\n                damage = metaEffect.Effect(damage, target, source);\n\n        damage += _extraDamage;\n\n        ...\n\n        float returnDamageInfo = ((IDamagable)target).TakeDamage(damage, source);\n\n        if (_postEffects != null)\n            foreach (var postEffect in _postEffects)\n                postEffect.Effect(returnDamageInfo, target, source);\n    }\n\n    ...\n\n    public IEffect ShallowClone() =\u003e\n            new DamageEffect(_baseDamage, _stackEffect, _stackValue, _targeting, _metaEffects, _postEffects);\n\n    ...\n```\n\nWe're not always using or changing mutable state in our effects, in these cases we can clone only when needed.\n`IMutableStateEffect` needs to be implemented, and it's property `UsesMutableState` needs to say if the effect is using\nmutable state. `IMutableStateEffect` is already part of `IStateEffect`.\n\n```csharp\npublic class DamageEffect : IEffect, IStateEffect, IStackEffect, IRevertEffect,\n    IMetaEffectOwner\u003cDamageEffect, float, float\u003e, IPostEffectOwner\u003cDamageEffect, float\u003e,\n    IMutableStateEffect\n{\n    public bool UsesMutableState =\u003e IsRevertible || _stackEffect.UsesMutableState();\n\n    ...\n}\n```\n\nSince we have mutable state in our effect, we probably want to serialize it.\nTo do that we need to implement `ISavableEffect\u003cTEffect.SaveData\u003e`.\nThis interface forces us to return and load an object that will holds the effects mutable state.\nThis object should be a readonly struct, and should be called `SaveData`.\n\n```csharp\npublic class DamageEffect : IEffect, IStateEffect, IStackEffect, IRevertEffect,\n    IMetaEffectOwner\u003cDamageEffect, float, float\u003e, IPostEffectOwner\u003cDamageEffect, float\u003e,\n    IMutableStateEffect, ISavableEffect\u003cDamageEffect.SaveData\u003e\n{\n    ...\n    \n    public object SaveState() =\u003e new SaveData(_extraDamage);\n    public void LoadState(object saveData) =\u003e _extraDamage = ((SaveData)saveData).ExtraDamage;\n\n    public readonly struct SaveData\n    {\n        public readonly float ExtraDamage;\n\n        public SaveData(float extraDamage) =\u003e ExtraDamage = extraDamage;\n    }\n```\n\n### Conditions in Effects\n\nCurrently `ModiBuff.Core` doesn't have any idea of effect conditions, these are instead implemented directly in\nuser-made effects.\n\nThese conditions currently need to be state-free, unless you handle the reset of state itself, and cloning them, rather\nthan the references.\n\nExamples of this can be found in\n[HealEffect](https://github.com/Chillu1/ModiBuff/blob/299760c23d8070e602a496c893c6aa98cb6ea684/ModiBuff/ModiBuff.Units/Effects/HealEffect.cs#L70)\nand\n[Condition effect tests](https://github.com/Chillu1/ModiBuff/blob/299760c23d8070e602a496c893c6aa98cb6ea684/ModiBuff/ModiBuff.Tests/MetaEffectTests.cs#L242)\n\nMeta and post effects support conditions in the same exact way as effects.\nSo condition logic can reused across all of them. This can be seen in the link above.\n\nAn example of a recipe that uses multiple conditions in both effects and meta effects:\n\n```csharp\nvar metaEffects = new IMetaEffect\u003cfloat, float\u003e[]\n{\n    new AddValueMetaEffect(5).Condition(new StatusEffectCond(StatusEffectType.Root)),\n    new MultiplyValueMetaEffect(2).Condition(new StatusEffectCond(StatusEffectType.Silence))\n};\nAdd(\"AddFlatOnRooted_MultiplyOnSilenced_HealOnDisarmed\")\n    .Effect(new DamageEffect(5)\n        .Condition(new StatusEffectCond(StatusEffectType.Disarm, true)) \n        .SetMetaEffects(metaEffects), EffectOn.Init)\n    .Effect(new HealEffect(5)\n        .Condition(new StatusEffectCond(StatusEffectType.Disarm)) \n        .SetMetaEffects(metaEffects), EffectOn.Init);\n```\n\nTo use conditions effects, copy over or take a look at\n[IConditionEffect](https://github.com/Chillu1/ModiBuff/blob/299760c23d8070e602a496c893c6aa98cb6ea684/ModiBuff/ModiBuff.Units/Effects/IConditionEffect.cs#L6)\nand\n[Condition](https://github.com/Chillu1/ModiBuff/blob/299760c23d8070e602a496c893c6aa98cb6ea684/ModiBuff/ModiBuff.Units/Effects/Condition.cs#L18)\n\nCurrently these conditions can't use \"consumption\" logic (like mana), this can be done by separating the process into\ntwo parts. Once all checks have passed, then we can consume the resources in the check.\n\n### Unit logic implementation check\n\nTarget unit might not have the logic implemented that's needed for the effect to work.\n`ModiBuff.Units` effects have a check for that, and if it doesn't it can log an error if desired.\n\nEx. if a unit doesn't have health/is damagable, we can either ignore the effect (by default), or log an error that\nthe effect was supposed to work (since either all our units should be damagable, or we have some other validation system\non top). These checks can be enabled with a preprocessor directive define `MODIBUFF_EFFECT_CHECK`.\n\n```csharp\nif (target is IAttackable\u003cfloat, float\u003e damagableTarget)\n    //Effect logic\n#if MODIBUFF_EFFECT_CHECK\nelse\n    EffectHelper.LogImplError(effectTarget, nameof(IAttackable\u003cfloat, float\u003e));\n#endif\n```\n\n### Applier Effect\n\nHands down, the most powerful effect is the ApplierEffect.  \nIt's used to apply other modifiers to units. While being able to use modifier logic, like stacks.  \nThis can create some very sophisticated modifiers:\n\n```csharp\n//WhenAttacked ApplyModifier. Every5Stacks this modifier adds a new ^ rupture modifier\nAdd(\"ComplexApplier_OnHit_Event\")\n    .Effect(new ApplierEffect(\"ComplexApplier_Rupture\", Targeting.SourceTarget), EffectOn.CallbackUnit)\n    .CallbackUnit(CallbackUnitType.WhenAttacked);\n\n//rupture modifier, that does DoT. When this gets to 5 stacks, apply the disarm effect.\nAdd(\"ComplexApplier_Rupture\")\n    .Effect(new DamageEffect(5), EffectOn.Interval)\n    .Effect(new ApplierEffect(\"ComplexApplier_Disarm\"), EffectOn.Stack)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 5);\n\n//Disarm the target for 5 seconds. On 2 stacks, removable in 10 seconds, refreshable.\nAdd(\"ComplexApplier_Disarm\")\n    .Effect(new StatusEffectEffect(StatusEffectType.Disarm, 5, false, StackEffectType.Effect), EffectOn.Stack)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 2)\n    .Remove(10)\n    .Refresh();\n```\n\nThe next one is very complex, but it shows the power of the ApplierEffect.  \nObviously the effects can be whatever we want. Damage, Stun, etc.\n\nAdd damage on 4 stacks buff, that you give someone when they heal you 5 times, for 60 seconds.  \nTo clarify:\n\n* Player heals ally 5 times, gets buff\n* Player attacks an enemy 4 times, gets damage buff\n* Player buff gets removed after 60 seconds\n\n```csharp            \n//Apply the modifier to source (healer) WhenHealed                                   \nAdd(\"ComplexApplier2_WhenHealed_Event\")               \n    .Effect(new ApplierEffect(\"ComplexApplier2_WhenHealed\", Targeting.SourceTarget), EffectOn.CallbackUnit)\n    .CallbackUnit(CallbackUnitType.WhenHealed);\n\n//On 5 stacks, apply the modifier to self.\nAdd(\"ComplexApplier2_WhenHealed\")                                                    \n    .Effect(new ApplierEffect(\"ComplexApplier2_OnAttack_Event\"), EffectOn.Stack)     \n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 5)\n    .Remove(5).Refresh();\n\n//Long main buff. Apply the modifier OnAttack.\nAdd(\"ComplexApplier2_OnAttack_Event\")\n    .Effect(new ApplierEffect(\"ComplexApplier2_WhenAttacked_Event\"), EffectOn.CallbackUnit)\n    .CallbackUnit(CallbackUnitType.OnAttack)\n    .Remove(60).Refresh();\n\nAdd(\"ComplexApplier2_WhenAttacked_Event\")\n    .Effect(new ApplierEffect(\"ComplexApplier2_AddDamageAdd\", Targeting.SourceTarget), EffectOn.CallbackUnit)\n    .CallbackUnit(CallbackUnitType.WhenAttacked)\n    .Remove(5).Refresh();\n\n//On 4 stacks, Add Damage to Unit source (attacker).\nAdd(\"ComplexApplier2_AddDamageAdd\")\n    .Effect(new ApplierEffect(\"ComplexApplier2_AddDamage\"), EffectOn.Stack)\n    .Stack(WhenStackEffect.EveryXStacks, everyXStacks: 4)\n    .Remove(5).Refresh();\n\n//AddDamage 5, one time init, remove in 10 seconds, refreshable.\nAdd(\"ComplexApplier2_AddDamage\")\n    .OneTimeInit()\n    .Effect(new AddDamageEffect(5, EffectState.IsRevertible), EffectOn.Init)\n    .Remove(10).Refresh();\n```\n\n## Modifier\n\nModifiers are the core backend part of the library, they are the things that are applied to entities with effects on\ncertain actions. Ex. Init, Interval, Duration, Stack.  \nYou **CAN'T** use the Modifier class directly, use the recipe system.\nRecipe system fixes a lot of internal complexity of setting up modifiers for you.\n\nIt's possible to make modifier directly by using `ManualModifierGenerator` class,\nspecifically `ModifierRecipes.Add(string, string, string, ModifierGeneratorFunc, Tag)` method.\nBut only do so if you really know what you're doing, and need that extra functionality like multiple interval/duration\ncomponents, separate condition checks, etc.\n\n### Manual Modifier Generation\n\nManual modifier generation is not a \"first-class citizen\" in the library,\nbut it does support all the bleeding edge features that aren't supported in recipes yet.\n\nHere's a basic implementation of the classic 5 damage on init modifier. First as recipe, then as manual modifier.\n\n```csharp\nAdd(\"InitDamage\")\n    .Effect(new DamageEffect(5), EffectOn.Init);\n```\n\nWe need to always supply a `TargetComponent`, and id, genId and name need to fed to the modifier.\n\n```csharp\nAdd(\"InitDamageManual\", \"\", \"\", (id, genId, name, tag) =\u003e\n{\n    var initComponent = new InitComponent(false, new IEffect[] { new DamageEffect(5) }, null);\n\n    return new Modifier(id, genId, name, initComponent, null, null, null,\n        new SingleTargetComponent(), null);\n}/*\u003eSupply possible tags here\u003c*/);\n```\n\nAs we can see there's a lot of manual setup, but this gives us more control over the modifier.\nWe can have multiple interval and duration components, with different unique effects.\n\nWe don't need to tag if our effect is init, stack or refresh.\nSince it's done automatically by the library through reflection.\n\nSome effects have special constructors for manual modifier generation,\nwhere you can supply the needed parameters directly.\nFor example effects that hold instance info.\n\n```csharp\nvar effect = StatusEffectEffect.Create(id, genId, StatusEffectType.Sleep, 5f, true);\n```\n\n## Serialization\n\nTo correctly serialize data, two things need to be serialized: The identification, and the mutable state.\n\nMutable state is fully opened up for serialization through nested structs called `SaveData` in every object that has\nmutable state.\n\nThis allows for easy serialization of objects through generic means, because the data is fully exposed.\nWithout extra need custom serialization logic inside the objects.\n\nThe identifiers might have also changed Id order, or new ones might have been added to the game, through things like dlc\nor mods, so it's important to handle that correctly. Like mapping old Ids to new ones.\nThis is achieved by calling `LoadState(SaveData)` on controllers/managers,\n[ModiBuff.Units.GameState](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/GameState.cs)\nis a good example of this.\n\nAny effect (mostly callback effects) that inherits from `IRegisterEffect` will be loaded automatically\non load (through init). This is done through `ModifierController.LoadState(SaveData, IUnit)`.\n\n### JSON\n\nModiBuff currently supports JSON serialization through `System.Text.Json`.\nTo enable it in the library, you need to add a preprocessor directive define `MODIBUFF_SYSTEM_TEXT_JSON`.\n\nThis enables the constructor attributes on objects, that allow for deserialization.\n\nAn example `SaveController` is shown in `ModiBuff.Extensions.Serialization.Json`,\nbut it's essentially not needed at all.\nBecause the only things that's needed is to serialize the object with `JsonSerializer.Serialize(obj, _options);`\nand have options have `IncludeFields = True`.\n\n## Centralized Effects\n\nSometimes we want to have the effects be centralized, so they're affected and controlled in one place.\nCommon examples of this are singular effects of: Poison, Bleed, Wet, etc.\nWhile ModiBuff wasn't designed for this, it's still possible to do, but at extra complexity cost.\n\n\u003e Note that this is possible to do in ModiBuff, but also harder than normal.\n\u003e While it's possible to do manage this, it might be better to create your own system for this.\n\u003e Unless you want some of the other features of ModiBuff.\n\nThe issue is that we need to store the state in the effect itself or the unit.\nIf we chose the unit, we're coupling together effect code with unit implementation (not good).\nSo let's settle with effect, an example of this is\n[PoisonDamageEffect](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/Effects/PoisonDamageEffect.cs).\nWe store all the poison stacks and their owners inside a dictionary that's inside the effect.\nWe also need a custom `StackEffect` implementation for saving those stacks,\nand a custom `Effect` function for when we should trigger applying the poison stacks.\n\nThere are some\n[tests](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Tests/CentralizedCustomLogicTests.cs)\nthat show how one can use the centralized effects.\n\nWhen using centralized effects, it often happens that we have custom state inside them.\nAnd if we also want to serialize that data, we might need to implement a parser for it.\nFor example a dictionary of `int unitId` and `int stacks`:\n\n```csharp\nSerializationExtensions.AddCustomValueType\u003cIReadOnlyDictionary\u003cint, int\u003e\u003e(element =\u003e\n{\n    var dictionary = new Dictionary\u003cint, int\u003e();\n    foreach (var kvp in element.EnumerateObject())\n        dictionary.Add(int.Parse(kvp.Name), kvp.Value.GetInt32());\n    return dictionary;\n});\n```\n\nCustom stack logic can also help us with implementing custom logic.\nHere we use stacks to determine the amount of poison stacks.\n\n```csharp\nAdd(\"HealPerPoisonStack\")\n    .Tag(Core.TagType.CustomStack)\n    .Stack(WhenStackEffect.Always)\n    .Effect(new HealEffect(0, HealEffect.EffectState.None,\n        StackEffectType.Effect | StackEffectType.SetStacksBased, 1), EffectOn.Stack)\n    .CallbackEffect(CallbackType.PoisonDamage, effect =\u003e\n        new PoisonEvent((target, source, stacks, totalStacks, damage) =\u003e\n        {\n            effect.Effect(target, source);\n        }))\n    .ModifierAction(ModifierAction.Stack, EffectOn.CallbackEffect);\n```\n\nAlso checkout [applier branch](https://github.com/Chillu1/ModiBuff/compare/master...feature/duration-add-modifier)\nfor applying extra different durations (undecided feature).\n\n## Modifierless-Effects\n\nSometimes we don't want to need to use the entire functionality of modifiers, and only add a simple effect to a unit.\nThis can be done by registering an effect with a name. Modifierless-effects are mostly used for simple init effects.\n\n\u003e Note: Modifierless-effects **CANNOT** use mutable state. Because their not cloned or kept by the target/source unit.\n\u003e If we try to use mutable state that state will be shared between all units that the effect applied to.\n\nModifierless-effects work similarly to modifiers in both access and registering.\n\nA simple 5 damage effect can be registered like this:\n\n```csharp\nAddEffect(\"5Damage\", new DamageEffect(5f));\n```\n\nEffects can also be added as \"castable\" to the unit, and cast, like this:\n\n```csharp\nunit.ModifierApplierController.TryAddEffectApplier(effectId);\n\nunit.TryCastEffect(effectId, target);\n```\n\nModifiers that only use `init` and have no mutable state can almost always be transformed into modifierless-effects.\n\nA common way to use modifierless-effects is to add one time effects as appliers.\n\n```csharp\nAdd(\"AddApplier_Effect\")\n    .Effect(new ApplierEffect(\"InitDamage\"), EffectOn.Init)\n    .RemoveApplier(5, ApplierType.Cast, false);\n\nAddEffect(\"AddApplier_ApplierEffect\", new ApplierEffect(\"AddApplier_Effect\", ApplierType.Cast, false));\n```\n\nOr combine two different effects together, so it's possible to trigger both on same cast, with different modifier\ntargets/owners.\n\nThis example uses centralized effects, where we apply poison and heal from the poison stacks. In one cast.\n\n```csharp\nAdd(PoisonRecipe);\nAdd(\"PoisonHealHeal\")\n    .Stack(WhenStackEffect.Always)\n    .Effect(new HealEffect(0, HealEffect.EffectState.None,\n            StackEffectType.Effect | StackEffectType.SetStacksBased, 1)\n        .SetMetaEffects(new AddValueBasedOnPoisonStacksMetaEffect(1f)), EffectOn.Stack);\n\nAddEffect(\"PoisonHeal\",\n    new ApplierEffect(\"Poison\"),\n    new ApplierEffect(\"PoisonHealHeal\", targeting: Targeting.SourceTarget));\n```\n\n# FAQ\n\nQ: ModiBuff.Units seems to use excessive casting in effects. Why not have a master unit interface?  \nA: This was a tough solution to make custom user effects work with their own unit implementations.\nAnd not force users to implement all methods for functionality, where it's not used.\n\nQ: How do I make \"insert mechanic from a game\" in ModiBuff?  \nA: First check [ModifierExamples.md](ModifierExamples.md). Then if it isn't there, ask about how to make it in issues,\nwill make a better\nplatform for discussion if needed.\n\nQ: It's 100% not possible to make \"mechanic from a game\" in ModiBuff.  \nA: If the mechanic is lacking internal ModiBuff functionality to work, and isn't an effect implementation problem, make\nan issue about it. The goal of ModiBuff is to support as many unique mechanics as possible,\nthat don't rely on game logic.\n\nQ: Why can't effects hold some kind of state? How else can I achieve this?  \nA: Feeding mutable (non-stack) state to effects would introduce way too much complexity.\nInstead we achieve this by using meta effects, and post effects on a secondary entity.\nAn example of this is a\n[projectile](https://github.com/Chillu1/ModiBuff/blob/d0ba95f8f0696572b9cfb4f3e1374c2fc5f57726/ModiBuff/ModiBuff.Tests/StateTests.cs#L270-L277).\nIt is possible to have (non-stack) mutable state in effects, but it's almost always a bad idea, unless you're working\nwith centralized effects.\n[Poison example](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Units/Effects/PoisonDamageEffect.cs).\n\nQ: How to handle UI?  \nA: There's two main ways of handling UI. The first general info is Modifier Name and Modifier Description,\nthrough `ModifierRecipes.GetModifierInfo()`. There's also `ModifierApplierController.GetApplier*()` methods for appliers\ninfo.\nAnd `ModifierController.GetModifierReferences()` for normal modifiers. Basic usage is shown in the\n[BasicConsole sample](https://github.com/Chillu1/ModiBuff/blob/905fff885dc45c4e31df03d8b995a82d40f24042/ModiBuff/ModiBuff.Examples/BasicConsole/UIExtensions.cs).\n\nQ: I have a lot of modifiers, and care about memory usage (ex. mobile). What can I do?  \nA: With huge amounts of modifier generators 500+ (recipes), and 10000+ units,\nthe memory usage of `ModifierController`s will be around 20MB. It can be lowered to 1MB±\nby using `Config.UseDictionaryIndexes`, at a small performance cost.\n\nQ: There's too much code duplication in `ModiBuff.Units` Effects, why not use inheritance or something else?  \nA: Not all effects use stack or targeting logic, and post/meta effects can have different signatures.\nSo it's better to have a bit of code duplication that force every effect into the same basket.\n\nQ: What about multi-threading?  \nA: ModiBuff isn't thread safe, and currently has no concurrency support. This might be a subject of change after\nthe 1.0 release.\n\nQ: My stack effect is not working, what's wrong?  \nA: StackEffectType needs to be set in all: `IEffect` (ex. DamageEffect), `Recipe.Effect.EffectOn.Stack`\nand `Recipe.Stack()`  \nEx:\n\n```csharp\nAdd(\"StackDamage\")\n    .Effect(new DamageEffect(5, StackEffectType.Effect), EffectOn.Stack)\n    .Stack(WhenStackEffect.Always);\n```\n\n# Examples\n\n## Modifier/Effect/Recipe\n\nFor a big list of implementation examples, see [ModifierExamples.md](ModifierExamples.md)\n\n## Full\n\n\u003e Note: The current examples are very very bare bones, a proper implementation with custom game logic will be added\n\u003e soon.\n\n[All samples](https://github.com/Chillu1/ModiBuff/tree/master/ModiBuff/ModiBuff.Examples)\n\n[Basic console](https://github.com/Chillu1/ModiBuff/tree/master/ModiBuff/ModiBuff.Examples/BasicConsole)\nsample, of player unit fighting a single enemy unit at a time.\n\n# Internals\n\n## Modifier lifetime process\n\nRecipes are only a initial intermediate step, to tell the system how to create modifiers.\nRecipes can also be fully skipped by using creating modifiers manually with the `ManualModifierGenerator` class.\nRecipes and generators are only created once in the initialization process.\n\nWhen the generators have been created, they're fed to the pool.\nThat supplies it's internal stacks with modifier instances.\nThere's a singleton pool that is used by every ModifierController.\n\n![InternalsDiagram](Docs/ModiBuffSystemsDiagram.png)\n\n# Differences to ModiBuffEcs and Old\n\n## [ModiBuffEcs](https://github.com/Chillu1/ModiBuffEcs)\n\nModiBuff has:\n\n* No GC/allocations\n* No ECS framework needed\n* Worse iteration speed, 25_000 interval modifiers compared to 100_000 modifiers, 5ms update, average complexity\n  modifiers\n* Many more features\n\n## [Old Modifier Library](https://github.com/Chillu1/ModifierLibrary)\n\nModiBuff has:\n\n* **Much** better backend and design decisions\n* Lightweight\n* Smaller Codebase\n* No GC/allocations\n* Redesigned Improved API\n\t* [Recipes](#recipe)\n\t  vs [Properties](https://github.com/Chillu1/ModifierLibrary/blob/master/ModifierLibrary/Assets/Scripts/ModifierLibrary/ModifierPrototypes.cs#L126)\n* Better iteration speed, 25_000 interval modifiers (from 500), 5ms update, average complexity modifiers\n* Better memory management (1MB for 5_000 modifiers)\n* No arbitrary name constraints\n\n---\n\n* Missing features:\n\t* Two status effects: taunt, confuse","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchillu1%2Fmodibuff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchillu1%2Fmodibuff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchillu1%2Fmodibuff/lists"}