forked from Shardstone/trail-into-darkness
Added resolver
This commit is contained in:
@@ -3,22 +3,31 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
public interface ICharacterAttributesFactory {
|
public interface ICharacterAttributesFactory {
|
||||||
EntityAttributes Create(EntityAttributes entityAttributes);
|
EntityAttributes Create(IEntityDefinition entityDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterAttributesFactory : ICharacterAttributesFactory {
|
public sealed class CharacterAttributesFactory : ICharacterAttributesFactory {
|
||||||
private readonly CharacterRegistry characterRegistry;
|
private readonly IModifierResolver modifierResolver;
|
||||||
|
|
||||||
public CharacterAttributesFactory(CharacterRegistry characterRegistry) {
|
public CharacterAttributesFactory(IModifierResolver modifierResolver) {
|
||||||
this.characterRegistry = characterRegistry;
|
this.modifierResolver = modifierResolver ?? throw new ArgumentNullException(nameof(modifierResolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityAttributes Create(EntityAttributes entityAttributes) {
|
public EntityAttributes Create(IEntityDefinition entityDefinition) {
|
||||||
if(entityAttributes.attributes.Any(a => a.value <= 0)) {
|
var attributes = entityDefinition.Attributes;
|
||||||
|
|
||||||
|
if(attributes.attributes.Any(a => a.value <= 0)) {
|
||||||
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
||||||
}
|
}
|
||||||
//TODO: Handle attributes modifiers and perks
|
|
||||||
return entityAttributes;
|
return new EntityAttributes {
|
||||||
|
attributes = attributes.attributes
|
||||||
|
.Select(a => {
|
||||||
|
var modifiers = modifierResolver.CollectModifiers(entityDefinition, a.attribute);
|
||||||
|
return new Attribute(a.attribute, modifierResolver.Resolve(a.value, modifiers));
|
||||||
|
})
|
||||||
|
.ToArray()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Nox.Game {
|
|||||||
public CharacterClass Class { get; set; } = (CharacterClass)GetRandomInt(1, Enum.GetValues(typeof(CharacterClass)).Length-1);
|
public CharacterClass Class { get; set; } = (CharacterClass)GetRandomInt(1, Enum.GetValues(typeof(CharacterClass)).Length-1);
|
||||||
public CharacterRole Role { get; set; } = CharacterRole.Companion;
|
public CharacterRole Role { get; set; } = CharacterRole.Companion;
|
||||||
public EntityAttributes Attributes { get; set; } = GetDefaultAttributes();
|
public EntityAttributes Attributes { get; set; } = GetDefaultAttributes();
|
||||||
public EntityStats Stats { get; set; } = new();
|
public EntityStats Stats { get; set; } = new() { stats = Array.Empty<Stat>() };
|
||||||
public PerksData Perks { get; set; } = new();
|
public PerksData Perks { get; set; } = new();
|
||||||
public ModifiersData Modifiers { get; set; } = new();
|
public ModifiersData Modifiers { get; set; } = new();
|
||||||
|
|
||||||
@@ -36,15 +36,15 @@ namespace Nox.Game {
|
|||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class CustomCharacterCreationRequest : IEntityDefinition {
|
public sealed class CustomCharacterCreationRequest : IEntityDefinition {
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public CharacterRace Race { get; set; }
|
public CharacterRace Race { get; set; }
|
||||||
public CharacterClass Class { get; set; }
|
public CharacterClass Class { get; set; }
|
||||||
public CharacterRole Role { get; set; }
|
public CharacterRole Role { get; set; } = CharacterRole.Protagonist;
|
||||||
public EntityAttributes Attributes { get; set; }
|
public EntityAttributes Attributes { get; set; }
|
||||||
public EntityStats Stats { get; set; }
|
public EntityStats Stats { get; set; } = new() { stats = Array.Empty<Stat>() };
|
||||||
public PerksData Perks { get; set; }
|
public PerksData Perks { get; set; } = new();
|
||||||
public ModifiersData Modifiers { get; set; }
|
public ModifiersData Modifiers { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterFactory : ICharacterFactory {
|
public sealed class CharacterFactory : ICharacterFactory {
|
||||||
@@ -69,7 +69,7 @@ namespace Nox.Game {
|
|||||||
throw new ArgumentNullException(nameof(request));
|
throw new ArgumentNullException(nameof(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributes = attributesFactory.Create(request.Attributes);
|
var attributes = attributesFactory.Create(request);
|
||||||
var stats = statsFactory.Create(request);
|
var stats = statsFactory.Create(request);
|
||||||
|
|
||||||
var character = new CharacterDefinition {
|
var character = new CharacterDefinition {
|
||||||
@@ -79,6 +79,7 @@ namespace Nox.Game {
|
|||||||
Class = request.Class,
|
Class = request.Class,
|
||||||
Role = CharacterRole.Protagonist,
|
Role = CharacterRole.Protagonist,
|
||||||
Attributes = attributes,
|
Attributes = attributes,
|
||||||
|
Stats = stats,
|
||||||
Perks = request.Perks ?? new PerksData(),
|
Perks = request.Perks ?? new PerksData(),
|
||||||
Modifiers = request.Modifiers ?? new ModifiersData()
|
Modifiers = request.Modifiers ?? new ModifiersData()
|
||||||
};
|
};
|
||||||
@@ -101,7 +102,7 @@ namespace Nox.Game {
|
|||||||
Race = template.Race,
|
Race = template.Race,
|
||||||
Class = template.Class,
|
Class = template.Class,
|
||||||
Role = role,
|
Role = role,
|
||||||
Attributes = attributesFactory.Create(template.Attributes),
|
Attributes = attributesFactory.Create(template),
|
||||||
Stats = statsFactory.Create(template),
|
Stats = statsFactory.Create(template),
|
||||||
Perks = template.Perks,
|
Perks = template.Perks,
|
||||||
Modifiers = template.Modifiers
|
Modifiers = template.Modifiers
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
@@ -8,33 +7,34 @@ namespace Nox.Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterStatsFactory : ICharacterStatsFactory {
|
public sealed class CharacterStatsFactory : ICharacterStatsFactory {
|
||||||
private readonly CharacterRegistry characterRegistry;
|
private readonly StarterCharacterSettings baseSettings;
|
||||||
|
private readonly IModifierResolver modifierResolver;
|
||||||
|
|
||||||
public CharacterStatsFactory(CharacterRegistry characterRegistry) {
|
public CharacterStatsFactory(StarterCharacterSettings baseSettings, IModifierResolver modifierResolver) {
|
||||||
this.characterRegistry = characterRegistry;
|
this.baseSettings = baseSettings;
|
||||||
|
this.modifierResolver = modifierResolver ?? throw new ArgumentNullException(nameof(modifierResolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityStats Create(IEntityDefinition entityDefinition) {
|
public EntityStats Create(IEntityDefinition entityDefinition) {
|
||||||
var attributes = entityDefinition.Attributes;
|
var attributes = entityDefinition.Attributes;
|
||||||
var stats = entityDefinition.Stats;
|
|
||||||
var perks = entityDefinition.Perks;
|
|
||||||
var modifiers = entityDefinition.Modifiers;
|
|
||||||
|
|
||||||
if(attributes.attributes.Any(a => a.value <= 0)) {
|
if(attributes.attributes.Any(a => a.value <= 0)) {
|
||||||
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
||||||
}
|
}
|
||||||
|
|
||||||
var healthModifiers = modifiers.modifiers.Where(m => m.StatType == StatType.Health);
|
var healthModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Health);
|
||||||
var health = stats.GetValue(StatType.Health) + attributes.GetValue(AttributeType.Might) * ResolveModifiersValue(healthModifiers, attributes);
|
var staminaModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Stamina);
|
||||||
|
var levelModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Level);
|
||||||
|
var experienceModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Experience);
|
||||||
|
|
||||||
return new EntityStats {
|
return new EntityStats {
|
||||||
stats = new[] {
|
stats = new[] {
|
||||||
new Stat(StatType.Health, health)
|
new Stat(StatType.Health, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Health), healthModifiers)),
|
||||||
|
new Stat(StatType.Stamina, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Stamina), staminaModifiers)),
|
||||||
|
new Stat(StatType.Level, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Level), levelModifiers)),
|
||||||
|
new Stat(StatType.Experience, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Experience), experienceModifiers))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
private int ResolveModifiersValue(IEnumerable<ModifierDefinition> healthModifiers, EntityAttributes attributes) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ namespace Nox.Game {
|
|||||||
public interface ICharacterSystems {
|
public interface ICharacterSystems {
|
||||||
IPerkFactory PerkFactory { get; }
|
IPerkFactory PerkFactory { get; }
|
||||||
IModifiersFactory ModifiersFactory { get; }
|
IModifiersFactory ModifiersFactory { get; }
|
||||||
|
IModifierResolver ModifierResolver { get; }
|
||||||
ICharacterFactory CharacterFactory { get; }
|
ICharacterFactory CharacterFactory { get; }
|
||||||
IPartyFactory PartyFactory { get; }
|
IPartyFactory PartyFactory { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterSystems : ICharacterSystems {
|
public sealed class CharacterSystems : ICharacterSystems {
|
||||||
public CharacterSystems(IPerkFactory perkFactory, IModifiersFactory modifiersFactory, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
|
public CharacterSystems(IPerkFactory perkFactory, IModifiersFactory modifiersFactory, IModifierResolver modifierResolver, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
|
||||||
ModifiersFactory = modifiersFactory;
|
ModifiersFactory = modifiersFactory;
|
||||||
|
ModifierResolver = modifierResolver;
|
||||||
PerkFactory = perkFactory;
|
PerkFactory = perkFactory;
|
||||||
CharacterFactory = characterFactory;
|
CharacterFactory = characterFactory;
|
||||||
PartyFactory = partyFactory;
|
PartyFactory = partyFactory;
|
||||||
@@ -17,6 +19,7 @@ namespace Nox.Game {
|
|||||||
|
|
||||||
public IPerkFactory PerkFactory { get; }
|
public IPerkFactory PerkFactory { get; }
|
||||||
public IModifiersFactory ModifiersFactory { get; }
|
public IModifiersFactory ModifiersFactory { get; }
|
||||||
|
public IModifierResolver ModifierResolver { get; }
|
||||||
public ICharacterFactory CharacterFactory { get; }
|
public ICharacterFactory CharacterFactory { get; }
|
||||||
public IPartyFactory PartyFactory { get; }
|
public IPartyFactory PartyFactory { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ namespace Nox.Game {
|
|||||||
ModifiersRegistry modifiersRegistry) {
|
ModifiersRegistry modifiersRegistry) {
|
||||||
IPerkFactory perkFactory = new PerkFactory(perksRegistry);
|
IPerkFactory perkFactory = new PerkFactory(perksRegistry);
|
||||||
IModifiersFactory modifiersFactory = new ModifiersFactory(modifiersRegistry);
|
IModifiersFactory modifiersFactory = new ModifiersFactory(modifiersRegistry);
|
||||||
ICharacterAttributesFactory attributesFactory = new CharacterAttributesFactory(characterRegistry);
|
IModifierResolver modifierResolver = new ModifierResolver();
|
||||||
ICharacterStatsFactory statsFactory = new CharacterStatsFactory(characterRegistry);
|
ICharacterAttributesFactory attributesFactory = new CharacterAttributesFactory(modifierResolver);
|
||||||
|
ICharacterStatsFactory statsFactory = new CharacterStatsFactory(starterCharacterSettings, modifierResolver);
|
||||||
ICharacterFactory characterFactory = new CharacterFactory(attributesFactory, statsFactory, perkFactory, modifiersFactory);
|
ICharacterFactory characterFactory = new CharacterFactory(attributesFactory, statsFactory, perkFactory, modifiersFactory);
|
||||||
IPartyFactory partyFactory = new PartyFactory(starterCharacterSettings);
|
IPartyFactory partyFactory = new PartyFactory(starterCharacterSettings);
|
||||||
|
|
||||||
return new CharacterSystems(perkFactory, modifiersFactory, characterFactory, partyFactory);
|
return new CharacterSystems(perkFactory, modifiersFactory, modifierResolver, characterFactory, partyFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ namespace Nox.Game {
|
|||||||
public Stat[] stats;
|
public Stat[] stats;
|
||||||
|
|
||||||
public int GetValue(StatType statType) {
|
public int GetValue(StatType statType) {
|
||||||
return stats.First(stat => stat.stat == statType).value;
|
var match = stats?.FirstOrDefault(stat => stat.stat == statType);
|
||||||
|
return match?.value ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Nox.Game {
|
|
||||||
|
|
||||||
public class ModifiersHandler {
|
|
||||||
private readonly ModifiersData modifiersData;
|
|
||||||
public ModifiersHandler(ModifiersData modifiersData) {
|
|
||||||
this.modifiersData = modifiersData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
98
Assets/Code/GameState/Entities/ModifiersResolver.cs
Normal file
98
Assets/Code/GameState/Entities/ModifiersResolver.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
|
||||||
|
public interface IModifierResolver {
|
||||||
|
int Resolve(int baseValue, IEnumerable<IModifier> modifiers);
|
||||||
|
IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, StatType statType);
|
||||||
|
IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, AttributeType attributeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ModifierResolver : IModifierResolver {
|
||||||
|
public int Resolve(int baseValue, IEnumerable<IModifier> modifiers) {
|
||||||
|
if(modifiers == null) {
|
||||||
|
return baseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var grouped = modifiers
|
||||||
|
.Where(m => m != null && m.Operation != ModifierOperation.None)
|
||||||
|
.GroupBy(m => m.Operation)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
float result = baseValue;
|
||||||
|
|
||||||
|
// 1. Flat — if any flat modifiers exist, they replace the base entirely
|
||||||
|
if(grouped.TryGetValue(ModifierOperation.Flat, out var flatMods)) {
|
||||||
|
result = flatMods.Sum(m => m.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Addition — sum all additive bonuses
|
||||||
|
if(grouped.TryGetValue(ModifierOperation.Addition, out var addMods)) {
|
||||||
|
result += addMods.Sum(m => m.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Percentage — summed into one multiplier: result * (1 + totalPercent / 100)
|
||||||
|
if(grouped.TryGetValue(ModifierOperation.Percentage, out var pctMods)) {
|
||||||
|
var totalPercent = pctMods.Sum(m => m.Value);
|
||||||
|
result *= 1f + (totalPercent / 100f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Multiplication — each factor applied sequentially
|
||||||
|
if(grouped.TryGetValue(ModifierOperation.Multiplication, out var mulMods)) {
|
||||||
|
foreach(var mod in mulMods) {
|
||||||
|
result *= mod.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)Math.Round(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, StatType statType) {
|
||||||
|
if(entity == null) {
|
||||||
|
return Enumerable.Empty<IModifier>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var direct = entity.Modifiers?.modifiers?
|
||||||
|
.Where(m => m != null && m.StatType == statType)
|
||||||
|
?? Enumerable.Empty<IModifier>();
|
||||||
|
|
||||||
|
var fromPerks = entity.Perks?.perks?
|
||||||
|
.Where(p => p?.Modifiers?.modifiers != null)
|
||||||
|
.SelectMany(p => p.Modifiers.modifiers)
|
||||||
|
.Where(m => m != null && m.StatType == statType)
|
||||||
|
?? Enumerable.Empty<IModifier>();
|
||||||
|
|
||||||
|
return direct.Concat(fromPerks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, AttributeType attributeType) {
|
||||||
|
if(entity == null) {
|
||||||
|
return Enumerable.Empty<IModifier>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var direct = entity.Modifiers?.modifiers?
|
||||||
|
.Where(m => m != null && m.AttributeType == attributeType)
|
||||||
|
?? Enumerable.Empty<IModifier>();
|
||||||
|
|
||||||
|
var fromPerks = entity.Perks?.perks?
|
||||||
|
.Where(p => p?.Modifiers?.modifiers != null)
|
||||||
|
.SelectMany(p => p.Modifiers.modifiers)
|
||||||
|
.Where(m => m != null && m.AttributeType == attributeType)
|
||||||
|
?? Enumerable.Empty<IModifier>();
|
||||||
|
|
||||||
|
return direct.Concat(fromPerks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user