SkillAgentSearch skills...

ModiBuff

Buff/Debuff/Modifier library focused on feature set and performance, while maintaining 0 GC. Fully pooled

Install / Use

/learn @Chillu1/ModiBuff
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"> <img src="Docs/LogoTest.png" width="500"/> <img src="Docs/ModiBuff.png" width="500"/> </p> <h1></h1>

Godot Nuget Coverage

What is this?

This zero dependency, engine-agnostic library was made to make a standardized powerful system that allows for manipulation of effects on entities.

It focuses on Feature Set, Performance and Ease of use, in that order.

Library goals:

  • Capable of creating very sophisticated modifiers
  • Modifiers being fast
  • Creating them easily

The library is split into two core parts:

ModiBuff is the core backend part that handles all the modifier logic and is mostly unopinionated when it comes to the game logic.

Meanwhile ModiBuff.Units is a fully featured implementation of the library, that showcases how to tie the library into a game.

Note: The library is currently in development, and it will most likely encounter breaking API changes.

Why do I need this?

The vast majority of games make their own buff/debuff systems, to fit their game logic. Examples of this are: Dota 2, League of Legends, Path of Exile, Diablo, World of Warcraft, etc.

While this is a great approach, it's also very time consuming, and requires a fair amount of research to design well.

This library solves that, but also allows for more complex and deeper modifiers than the aforementioned games.

Features

  • No GC/runtime heap allocations (fully pooled with state reset)
  • Low memory usage (2-5 MB for 10_000 modifiers)
  • Fast effects 10_000 damage modifiers in 0.24ms
  • Fast iteration 10_000 interval modifiers & 10_000 units in 1.37ms
  • Easy high level API recipes
  • Instance Stackable modifiers (multiple instances of the same modifier type)
  • Effects on actions
    • Init
    • Interval
    • Duration
    • Stack
    • Callbacks (any user logic, support mutable and serializable state inside)
      • Unit callbacks/events (ex. When Attacked/Cast/Killed/Healed/Stunned/Silenced, On Attack/Cast/Kill/Heal)
      • Effect callbacks
      • Custom signature callbacks
  • Effect implementation examples
    • Damage (& self damage)
    • Heal
    • Status effects (stun, silence, disarm, etc.) & immunity
    • Multi instance Status effects impl (2 same stuns, 2 different sources, unique timers)
    • Dispel status effect(s)
    • Add stat (Damage, Heal)
    • Actions (Attack, Heal, Cast, etc.)
    • Centralized Effect
    • And more, see the rest
  • Internal Effects
    • Special Applier (another Modifier)
      • Applying Applier Modifiers as Applier Modifiers
    • Modifier Action (refresh, reset stacks, custom stack)
    • Remove (remove modifier)
    • Revert Action
  • Modifierless-Effects
  • Meta & Post effect manipulation (ex. lifesteal)
  • Stack Logic
    • Custom (callback/interval/duration triggers)
    • Stack timer
    • Independent stack timers
    • Revertable independent stacks
  • Condition implementations (checks)
    • Chance 0-100%
    • Cooldown
      • Charges (multiple use with cooldown)
    • Health/Mana cost, flat & percent
    • General:
      • Stat (health/mana/damage) >/=/< than X
      • Stat is full/empty
      • Has LegalAction (can attack, cast spell, move, etc.)
      • Has StatusEffect (stunned, silenced, disarmed, etc.)
      • Has Modifier
  • Applier Modifiers
    • OnAttack
    • Cast
  • Fully revertible effects
  • Manual modifier generation (for full control)
  • Open generic serialization (of all mutable state and id's)
    • System.Text.Json

Short showcase

We'll start off super basic, this is a modifier that deals 5 damage on target unit when added. Recipe explanations are down in the Usage section.

Add("InitDamage")
    .Effect(new DamageEffect(5), EffectOn.Init);

Let's bring out the heavy stuff. These will be very complex, but will show the capabilities of the library. So don't be scared off by the code complexity, this is (almost) as complex as it gets. Only limited by imagination.

Add("DamageOnStun_HealOnAnyNotStunStatusEffectRemoved_StackDamageWhenLongStunned")
    .Tag(TagType.CustomStack | TagType.CustomRefresh)
    .Interval(2f).Refresh()
    .ModifierAction(ModifierAction.ResetStacks, EffectOn.Interval)
    .Stack(WhenStackEffect.Always)
    .ModifierAction(ModifierAction.Stack | ModifierAction.Refresh, EffectOn.CallbackEffect)
    .CallbackEffect(CallbackType.DamageChanged, effect => new DamageChangedEvent(
        (source, newDamage, deltaDamage) =>
        {
            if (deltaDamage >= 1f)
                effect.Effect(source, source);
        }))
    .Effect(new DamageEffect(5, true, StackEffectType.Add, 2), EffectOn.Stack | EffectOn.CallbackEffect2)
    .CallbackEffect(CallbackType.StatusEffectAdded, effect => new AddStatusEffectEvent(
        (target, source, duration, statusEffect, oldLegalAction, newLegalAction) =>
        {
            if (statusEffect.HasStatusEffect(StatusEffectType.Stun))
                effect.Effect(target, source);
        }))
    .Effect(new HealEffect(5, HealEffect.EffectState.ValueIsRevertible, StackEffectType.Add, 2),
        EffectOn.Stack | EffectOn.CallbackEffect3)
    .CallbackEffect(CallbackType.StatusEffectRemoved, effect => new RemoveStatusEffectEvent(
        (target, source, statusEffect, oldLegalAction, newLegalAction) =>
        {
            if (!statusEffect.HasStatusEffect(StatusEffectType.Stun))
                effect.Effect(target, source);
        }));

It's a mouthful, but if we split up the effects it's easy to understand.

  1. Every time a stun status effect gets added, we deal 5 damage to the unit.
  2. Every time a non-stun status effect (ex. freeze, root) gets removed (either by dispel, or duration), we heal the unit for 5.
  3. Every time the source unit changes its damage value, we add 2 damage and heal value stacks to effects above, each time.
  4. Every 2 seconds, we reset the stacks on above damage and heal effects
  5. If the unit gets extra damage, it refreshes the 2 seconds stack timer (so the stacks don't get removed)

RoadMap

| V0.4.0-V0.?.0 | V1.0.0 | |-------------------------------------------------|----------------------------------------------------------------| | GenId based<br/>modifier stacking | Fully released open source<br/>game using ModiBuff at its core | | Proper game samples<br/>(Godot and maybe Unity) | 98% of game mechanics<br/>implementable | | Improved Appliers API? | ... |

Benchmarks

TL;DR: It's fast. Takes 1ms± to add & update 10000 modifiers every frame.

BenchmarkDotNet v0.13.6, EndeavourOS
Intel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET SDK 6.0.120
.NET 6.0.20 (6.0.2023.36801), X64 RyuJIT AVX2

N: 10_000
Delta: 0.0167 * N

Add/Apply/Update Modifier table

Pre-allocated Pools

| Library | NoOp*<br/>(1 unit) | Apply<br/>InitDmg<br/>(1 unit) | Apply<br/>InitStackDmg<br/>(1 unit) | Apply Multi<br/>instance DoT | |-------------------------------------------------------|--------------------|--------------------------------|-------------------------------------|------------------------------| | ModiBuff (this) | 0.18ms, 0 B | 0.26ms, 0 B | 0.44ms, 0 B | 1.01ms, 0 B | | ModiBuffEcs | ? | 1.02ms, 0 GC | ? | X | | Old | ? | 21.4ms, 24 GC | ? | X |

| Library | Update DoT**<br/>(10_000 units, N:1) | Update Instance<br/>Stackable DoT | |-------------------------------------------------------|--------------------------------------|-----------------------------------| | ModiBuff (this) | 1.96ms, 0 B | 0.13ms, 0 B | | ModiBuffEcs | 0.44ms, 0 B | X | | Old | ? | X |

New Modifier/Pool table

| Library | DoT pool rent | DoT pool<br/>reset return | |-------------------------------------------------------|---------------|---------------------------| | ModiBuff (this) | 0.04ms, 0 B | 0.18ms, 0 B | | ModiBuffEcs

Related Skills

View on GitHub
GitHub Stars196
CategoryDevelopment
Updated4d ago
Forks7

Languages

C#

Security Score

100/100

Audited on Apr 4, 2026

No findings