Files
trail-into-darkness/Assets/Code/GameState/Entities/ModifiersResolver.cs
2026-04-06 01:05:20 +02:00

161 lines
6.2 KiB
C#

using System;
using System.Collections.Generic;
using ZLinq;
namespace Nox.Game {
public interface IModifierResolver {
int Resolve(int baseValue, IEnumerable<IModifier> modifiers, IEntityDefinition entity = null);
IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, StatType statType);
IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, AttributeType attributeType);
IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, CombatScoreType combatScoreType);
}
/// <summary>
/// Collects modifiers from an entity's direct modifiers and perk-granted modifiers,
/// then resolves them against a base value.
///
/// Resolution order:
/// 1. Flat — sum of all flat values replaces the base
/// 2. Addition — summed and added to the running total
/// 3. Percentage — summed into a single multiplier applied to the post-addition total
/// 4. Multiplication — each factor applied sequentially to the running total
///
/// If a modifier has a ScalingSource set, its Value is multiplied by the entity's
/// current value for that source before being applied. For example, a modifier with
/// Target=Health, ScalingSource=Might, Operation=Addition, Value=2 means "+2 Health
/// per point of Might".
/// </summary>
public sealed class ModifierResolver : IModifierResolver {
public int Resolve(int baseValue, IEnumerable<IModifier> modifiers, IEntityDefinition entity = null) {
if(modifiers == null) {
return baseValue;
}
float flatSum = 0f;
float addSum = 0f;
float pctSum = 0f;
var mulValues = new List<float>();
var hasFlat = false;
foreach(var m in modifiers) {
if(m == null || m.Operation == ModifierOperation.None) {
continue;
}
var effectiveValue = ResolveScaling(m, entity);
switch(m.Operation) {
case ModifierOperation.Flat:
flatSum += effectiveValue;
hasFlat = true;
break;
case ModifierOperation.Addition:
addSum += effectiveValue;
break;
case ModifierOperation.Percentage:
pctSum += effectiveValue;
break;
case ModifierOperation.Multiplication:
mulValues.Add(effectiveValue);
break;
}
}
float result = hasFlat ? flatSum : baseValue;
result += addSum;
result *= 1f + (pctSum / 100f);
foreach(var mul in mulValues) {
result *= mul;
}
return (int)Math.Round(result);
}
private static float ResolveScaling(IModifier modifier, IEntityDefinition entity) {
var value = modifier.Value;
if(entity == null || modifier.ScalingSource == null || !modifier.ScalingSource.IsSet) {
return value;
}
var source = modifier.ScalingSource;
var sourceValue = source.Type switch {
ModifierTargetType.Attribute when entity.Attributes?.attributes != null =>
entity.Attributes.GetValue(source.AttributeType),
ModifierTargetType.Stat when entity.Stats?.stats != null =>
entity.Stats.GetValue(source.StatType),
_ => 0
};
return value * sourceValue;
}
public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, StatType statType) {
if(entity == null) {
return Array.Empty<IModifier>();
}
var result = new List<IModifier>();
CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(statType));
return result;
}
public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, AttributeType attributeType) {
if(entity == null) {
return Array.Empty<IModifier>();
}
var result = new List<IModifier>();
CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(attributeType));
return result;
}
public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, CombatScoreType combatScoreType) {
if(entity == null) {
return Array.Empty<IModifier>();
}
var result = new List<IModifier>();
CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(combatScoreType));
return result;
}
private static bool MeetsRequirements(IModifier modifier, IEntityDefinition entity) {
var requirements = modifier.Requirements;
if(requirements == null || requirements.Count == 0) {
return true;
}
for(int i = 0; i < requirements.Count; i++) {
if(!requirements[i].IsMet(entity.Attributes)) {
return false;
}
}
return true;
}
private static void CollectFromEntity(IEntityDefinition entity, List<IModifier> result, Func<IModifier, bool> predicate) {
if(entity.Modifiers?.modifiers != null) {
foreach(var m in entity.Modifiers.modifiers) {
if(m != null && predicate(m) && MeetsRequirements(m, entity)) {
result.Add(m);
}
}
}
if(entity.Perks?.perks != null) {
foreach(var p in entity.Perks.perks) {
if(p?.Modifiers?.modifiers == null) {
continue;
}
foreach(var m in p.Modifiers.modifiers) {
if(m != null && predicate(m)) {
result.Add(m);
}
}
}
}
}
}
}