using System; using System.Collections.Generic; using ZLinq; namespace Nox.Game { public interface IModifierResolver { int Resolve(int baseValue, IEnumerable modifiers, IEntityDefinition entity = null); IEnumerable CollectModifiers(IEntityDefinition entity, StatType statType); IEnumerable CollectModifiers(IEntityDefinition entity, AttributeType attributeType); IEnumerable CollectModifiers(IEntityDefinition entity, CombatScoreType combatScoreType); } /// /// 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". /// public sealed class ModifierResolver : IModifierResolver { public int Resolve(int baseValue, IEnumerable modifiers, IEntityDefinition entity = null) { if(modifiers == null) { return baseValue; } float flatSum = 0f; float addSum = 0f; float pctSum = 0f; var mulValues = new List(); 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 CollectModifiers(IEntityDefinition entity, StatType statType) { if(entity == null) { return Array.Empty(); } var result = new List(); CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(statType)); return result; } public IEnumerable CollectModifiers(IEntityDefinition entity, AttributeType attributeType) { if(entity == null) { return Array.Empty(); } var result = new List(); CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(attributeType)); return result; } public IEnumerable CollectModifiers(IEntityDefinition entity, CombatScoreType combatScoreType) { if(entity == null) { return Array.Empty(); } var result = new List(); 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 result, Func 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); } } } } } } }