Skip to content

Instantly share code, notes, and snippets.

@KevinRoussel
Created March 18, 2026 17:51
Show Gist options
  • Select an option

  • Save KevinRoussel/b682d107ed71e08742c80579dbd27eb4 to your computer and use it in GitHub Desktop.

Select an option

Save KevinRoussel/b682d107ed71e08742c80579dbd27eb4 to your computer and use it in GitHub Desktop.
Petit helper pour la mise en place d'alteration à une stat / Vector3 etc.
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Opaque identifier returned when registering an alteration.
/// It can later be used to remove that specific alteration.
/// </summary>
public interface IAlterationID { }
/// <summary>
/// Stores a base value and applies a layered set of transformations to produce a final altered value.
/// </summary>
/// <typeparam name="T">Type of value transformed by the registered alterations.</typeparam>
public class Alterable<T>
{
sealed class AlterationID : IAlterationID { }
readonly struct AlterationEntry
{
public readonly IAlterationID Id;
public readonly Func<T, T> Alteration;
public readonly int Layer;
public readonly string Name;
public AlterationEntry(IAlterationID id, Func<T, T> alteration, int layer, string name)
{
Id = id;
Alteration = alteration;
Layer = layer;
Name = name;
}
}
readonly T _defaultValue;
readonly List<AlterationEntry> _effects;
/// <summary>
/// Invoked after a new alteration has been added.
/// </summary>
public event Action<IAlterationID> OnNewAlteration;
/// <summary>
/// Invoked just before an existing alteration is removed.
/// </summary>
public event Action<IAlterationID> OnBeforeRemovedAlteration;
/// <summary>
/// Invoked after an alteration has been removed.
/// </summary>
public event Action<IAlterationID> OnRemovedAlteration;
/// <summary>
/// Invoked after all registered alterations have been cleared.
/// </summary>
public event Action OnAllAlterationsRemoved;
/// <summary>
/// Returns a lightweight debug view of the currently registered alterations.
/// </summary>
public IEnumerable<string> DebugStrings => _effects.Select(i => $"{i.Name} Layer: {i.Layer}");
/// <summary>
/// Creates a new alterable value container.
/// </summary>
/// <param name="defaultValue">Base value used when calling <see cref="Value()"/> or <see cref="AllAlterations()"/>.</param>
public Alterable(T defaultValue)
{
_defaultValue = defaultValue;
_effects = new List<AlterationEntry>();
}
/// <summary>
/// Computes the current value by applying all registered alterations to the default value.
/// </summary>
public T Value() => Value(_defaultValue);
/// <summary>
/// Computes the current value by applying all registered alterations to the provided reference value.
/// </summary>
/// <param name="referenceValue">Starting value used before applying the alterations.</param>
/// <returns>The final value after all alterations have been applied in layer order.</returns>
public T Value(T referenceValue)
{
T tmpValue = referenceValue;
for (var i = 0; i < _effects.Count; i++)
{
tmpValue = _effects[i].Alteration(tmpValue);
}
return tmpValue;
}
/// <summary>
/// Enumerates the intermediate values produced when applying each alteration to the default value.
/// The first yielded value is the unmodified default value.
/// </summary>
public IEnumerable<T> AllAlterations() => AllAlterations(_defaultValue);
/// <summary>
/// Enumerates the intermediate values produced when applying each alteration to the provided reference value.
/// The first yielded value is the unmodified reference value.
/// </summary>
/// <param name="referenceValue">Starting value used before applying the alterations.</param>
public IEnumerable<T> AllAlterations(T referenceValue)
{
yield return referenceValue;
T tmpValue = referenceValue;
for (var i = 0; i < _effects.Count; i++)
{
tmpValue = _effects[i].Alteration(tmpValue);
yield return tmpValue;
}
}
/// <summary>
/// Adds a custom alteration to the collection.
/// Alterations are applied in ascending layer order.
/// </summary>
/// <param name="alteration">Transformation applied to the current value.</param>
/// <param name="layer">Execution order of the alteration. Lower layers are applied first.</param>
/// <param name="alterationName">Optional debug name used by <see cref="DebugStrings"/>.</param>
/// <returns>An identifier that can later be used to remove this alteration.</returns>
public IAlterationID AddCustomAlteration(Func<T, T> alteration, int layer = 1, string alterationName = "")
{
if (alteration == null) throw new ArgumentNullException(nameof(alteration));
IAlterationID id = new AlterationID();
var insertIndex = _effects.FindIndex(effect => layer < effect.Layer);
if (insertIndex >= 0)
{
_effects.Insert(insertIndex, new AlterationEntry(id, alteration, layer, alterationName));
}
else
{
_effects.Add(new AlterationEntry(id, alteration, layer, alterationName));
}
OnNewAlteration?.Invoke(id);
return id;
}
/// <summary>
/// Removes a previously registered alteration.
/// </summary>
/// <param name="alteration">Identifier of the alteration to remove.</param>
/// <returns><c>true</c> if the alteration was found and removed; otherwise <c>false</c>.</returns>
public bool RemoveAlteration(IAlterationID alteration)
{
for (var i = 0; i < _effects.Count; i++)
{
if (_effects[i].Id != alteration) continue;
OnBeforeRemovedAlteration?.Invoke(alteration);
var removedAlteration = alteration;
_effects.RemoveAt(i);
OnRemovedAlteration?.Invoke(removedAlteration);
return true;
}
return false;
}
/// <summary>
/// Removes a previously registered alteration and clears the provided identifier reference when successful.
/// </summary>
/// <param name="alteration">Identifier of the alteration to remove. Set to <c>null</c> when removal succeeds.</param>
/// <returns><c>true</c> if the alteration was found and removed; otherwise <c>false</c>.</returns>
public bool TryRemoveAndClear(ref IAlterationID alteration)
{
if (!RemoveAlteration(alteration)) return false;
alteration = null;
return true;
}
/// <summary>
/// Removes every registered alteration.
/// </summary>
public void RemoveAllAlterations()
{
_effects.Clear();
OnAllAlterationsRemoved?.Invoke();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment