forked from Shardstone/trail-into-darkness
added full characte creation support
This commit is contained in:
@@ -24,7 +24,7 @@ namespace Nox.Game {
|
||||
attributes = attributes.attributes.AsValueEnumerable()
|
||||
.Select(a => {
|
||||
var modifiers = modifierResolver.CollectModifiers(entityDefinition, a.attribute);
|
||||
return new Attribute(a.attribute, modifierResolver.Resolve(a.value, modifiers));
|
||||
return new Attribute(a.attribute, modifierResolver.Resolve(a.value, modifiers, entityDefinition));
|
||||
})
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Nox.Game {
|
||||
public sealed class CharacterTemplate : IEntityDefinition {
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = "New Character";
|
||||
public int PortraitIndex { get; set; }
|
||||
public CharacterRace Race { get; set; } = (CharacterRace)GetRandomInt(1, Enum.GetValues(typeof(CharacterRace)).Length-1);
|
||||
public CharacterClass Class { get; set; } = (CharacterClass)GetRandomInt(1, Enum.GetValues(typeof(CharacterClass)).Length-1);
|
||||
public CharacterRole Role { get; set; } = CharacterRole.Companion;
|
||||
@@ -38,6 +39,7 @@ namespace Nox.Game {
|
||||
public sealed class CharacterCreationRequest : IEntityDefinition {
|
||||
public Guid Id { get; set; } = Guid.Empty;
|
||||
public string Name { get; set; }
|
||||
public int PortraitIndex { get; set; }
|
||||
public CharacterRace Race { get; set; }
|
||||
public CharacterClass Class { get; set; }
|
||||
public CharacterRole Role { get; set; } = CharacterRole.Protagonist;
|
||||
@@ -78,6 +80,7 @@ namespace Nox.Game {
|
||||
Race = request.Race,
|
||||
Class = request.Class,
|
||||
Role = CharacterRole.Protagonist,
|
||||
PortraitIndex = request.PortraitIndex,
|
||||
Attributes = attributes,
|
||||
Stats = stats,
|
||||
Perks = request.Perks ?? new PerksData(),
|
||||
|
||||
@@ -23,16 +23,16 @@ namespace Nox.Game {
|
||||
}
|
||||
|
||||
var healthModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Health);
|
||||
var staminaModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Stamina);
|
||||
var staminaModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Mana);
|
||||
var levelModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Level);
|
||||
var experienceModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Experience);
|
||||
|
||||
return new EntityStats {
|
||||
stats = new[] {
|
||||
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))
|
||||
new Stat(StatType.Health, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Health), healthModifiers, entityDefinition)),
|
||||
new Stat(StatType.Mana, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Mana), staminaModifiers, entityDefinition)),
|
||||
new Stat(StatType.Level, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Level), levelModifiers, entityDefinition)),
|
||||
new Stat(StatType.Experience, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Experience), experienceModifiers, entityDefinition))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Nox.Game {
|
||||
public interface IEntityDefinition {
|
||||
Guid Id { get; }
|
||||
string Name { get; }
|
||||
int PortraitIndex { get; }
|
||||
CharacterRace Race { get; }
|
||||
CharacterClass Class { get; }
|
||||
CharacterRole Role { get; }
|
||||
@@ -22,7 +23,7 @@ namespace Nox.Game {
|
||||
public enum StatType {
|
||||
None,
|
||||
Health,
|
||||
Stamina,
|
||||
Mana,
|
||||
Level,
|
||||
Experience
|
||||
}
|
||||
@@ -129,6 +130,7 @@ namespace Nox.Game {
|
||||
[Serializable]
|
||||
public class CharacterDefinition : IEntityDefinition {
|
||||
public Guid Id { get; set; }
|
||||
public int PortraitIndex { get; set; } = 0;
|
||||
[field: SerializeField] public string Name { get; set; }
|
||||
[field: SerializeField] public CharacterRace Race { get; set; }
|
||||
[field: SerializeField] public CharacterClass Class { get; set; }
|
||||
@@ -156,17 +158,18 @@ namespace Nox.Game {
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
Modifiers = p.Modifiers
|
||||
}).ToList() ?? new()
|
||||
}).ToList() ?? new List<PerkDefinition>()
|
||||
},
|
||||
Modifiers = new ModifiersData {
|
||||
modifiers = Modifiers?.modifiers?.AsValueEnumerable().Select(m => new ModifierDefinition {
|
||||
Id = m.Id,
|
||||
Name = m.Name,
|
||||
StatType = m.StatType,
|
||||
AttributeType = m.AttributeType,
|
||||
Target = m.Target,
|
||||
ScalingSource = m.ScalingSource,
|
||||
Operation = m.Operation,
|
||||
Value = m.Value
|
||||
}).ToList() ?? new()
|
||||
Value = m.Value,
|
||||
Requirements = m.Requirements?.ToList() ?? new List<ModifierRequirement>()
|
||||
}).ToList() ?? new List<ModifierDefinition>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ namespace Nox.Game {
|
||||
public interface IModifier {
|
||||
string Name { get; }
|
||||
Guid Id { get; }
|
||||
StatType StatType { get; }
|
||||
AttributeType AttributeType { get; }
|
||||
ModifierTarget Target { get; }
|
||||
ModifierTarget ScalingSource { get; }
|
||||
ModifierOperation Operation { get; }
|
||||
float Value { get; }
|
||||
IReadOnlyList<ModifierRequirement> Requirements { get; }
|
||||
}
|
||||
|
||||
public interface IModifiersFactory {
|
||||
@@ -30,20 +31,73 @@ namespace Nox.Game {
|
||||
Percentage
|
||||
}
|
||||
|
||||
public enum ModifierTargetType {
|
||||
None,
|
||||
Attribute,
|
||||
Stat,
|
||||
CombatScore
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ModifierTarget {
|
||||
[field: SerializeField] public ModifierTargetType Type { get; set; }
|
||||
[field: SerializeField] public AttributeType AttributeType { get; set; }
|
||||
[field: SerializeField] public StatType StatType { get; set; }
|
||||
[field: SerializeField] public CombatScoreType CombatScoreType { get; set; }
|
||||
|
||||
public bool Matches(StatType statType) {
|
||||
return Type == ModifierTargetType.Stat && StatType == statType;
|
||||
}
|
||||
|
||||
public bool Matches(AttributeType attributeType) {
|
||||
return Type == ModifierTargetType.Attribute && AttributeType == attributeType;
|
||||
}
|
||||
|
||||
public bool Matches(CombatScoreType combatScoreType) {
|
||||
return Type == ModifierTargetType.CombatScore && CombatScoreType == combatScoreType;
|
||||
}
|
||||
|
||||
public bool IsSet => Type != ModifierTargetType.None;
|
||||
|
||||
public override string ToString() {
|
||||
return Type switch {
|
||||
ModifierTargetType.Attribute => AttributeType.ToString(),
|
||||
ModifierTargetType.Stat => StatType.ToString(),
|
||||
ModifierTargetType.CombatScore => CombatScoreType.ToString(),
|
||||
_ => "None"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ModifierRequirement {
|
||||
[field: SerializeField] public AttributeType Attribute { get; set; }
|
||||
[field: SerializeField] public int MinimumValue { get; set; }
|
||||
|
||||
public bool IsMet(EntityAttributes attributes) {
|
||||
if(Attribute == AttributeType.None || attributes?.attributes == null) {
|
||||
return true;
|
||||
}
|
||||
return attributes.GetValue(Attribute) >= MinimumValue;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ModifierDefinition : IModifier {
|
||||
[field: SerializeField] public string Name { get; set; }
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[field: SerializeField] public StatType StatType { get; set; }
|
||||
[field: SerializeField] public AttributeType AttributeType { get; set; }
|
||||
[field: SerializeField] public ModifierTarget Target { get; set; }
|
||||
[field: SerializeField] public ModifierTarget ScalingSource { get; set; }
|
||||
[field: SerializeField] public ModifierOperation Operation { get; set; }
|
||||
[field: SerializeField] public CombatScoreType CombatScoreType { get; set; }
|
||||
[field: SerializeField] public float Value { get; set; }
|
||||
[field: SerializeField] public List<ModifierRequirement> Requirements { get; set; } = new();
|
||||
|
||||
IReadOnlyList<ModifierRequirement> IModifier.Requirements => Requirements;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ModifiersData {
|
||||
public List<ModifierDefinition> modifiers = new ();
|
||||
public List<ModifierDefinition> modifiers = new();
|
||||
|
||||
public override string ToString() {
|
||||
return $"Modifiers: {string.Join(", ", modifiers.Select(modifier => $"{modifier.Name}"))}";
|
||||
@@ -52,7 +106,7 @@ namespace Nox.Game {
|
||||
|
||||
public class ModifiersFactory : IModifiersFactory {
|
||||
private readonly ModifiersRegistry modifiersRegistry;
|
||||
private readonly Dictionary<Guid, IModifier> modifierPool = new ();
|
||||
private readonly Dictionary<Guid, IModifier> modifierPool = new();
|
||||
|
||||
public ModifiersFactory(ModifiersRegistry modifiersRegistry) {
|
||||
this.modifiersRegistry = modifiersRegistry;
|
||||
@@ -92,10 +146,11 @@ namespace Nox.Game {
|
||||
character.Modifiers.modifiers.Add(new ModifierDefinition {
|
||||
Id = modifier.Id,
|
||||
Name = modifier.Name,
|
||||
StatType = modifier.StatType,
|
||||
AttributeType = modifier.AttributeType,
|
||||
Target = modifier.Target,
|
||||
ScalingSource = modifier.ScalingSource,
|
||||
Operation = modifier.Operation,
|
||||
Value = modifier.Value
|
||||
Value = modifier.Value,
|
||||
Requirements = modifier.Requirements?.ToList() ?? new List<ModifierRequirement>()
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -5,9 +5,10 @@ using ZLinq;
|
||||
namespace Nox.Game {
|
||||
|
||||
public interface IModifierResolver {
|
||||
int Resolve(int baseValue, IEnumerable<IModifier> modifiers);
|
||||
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>
|
||||
@@ -19,9 +20,14 @@ namespace Nox.Game {
|
||||
/// 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) {
|
||||
public int Resolve(int baseValue, IEnumerable<IModifier> modifiers, IEntityDefinition entity = null) {
|
||||
if(modifiers == null) {
|
||||
return baseValue;
|
||||
}
|
||||
@@ -37,19 +43,21 @@ namespace Nox.Game {
|
||||
continue;
|
||||
}
|
||||
|
||||
var effectiveValue = ResolveScaling(m, entity);
|
||||
|
||||
switch(m.Operation) {
|
||||
case ModifierOperation.Flat:
|
||||
flatSum += m.Value;
|
||||
flatSum += effectiveValue;
|
||||
hasFlat = true;
|
||||
break;
|
||||
case ModifierOperation.Addition:
|
||||
addSum += m.Value;
|
||||
addSum += effectiveValue;
|
||||
break;
|
||||
case ModifierOperation.Percentage:
|
||||
pctSum += m.Value;
|
||||
pctSum += effectiveValue;
|
||||
break;
|
||||
case ModifierOperation.Multiplication:
|
||||
mulValues.Add(m.Value);
|
||||
mulValues.Add(effectiveValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -65,34 +73,31 @@ namespace Nox.Game {
|
||||
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>();
|
||||
|
||||
if(entity.Modifiers?.modifiers != null) {
|
||||
foreach(var m in entity.Modifiers.modifiers) {
|
||||
if(m != null && m.StatType == statType) {
|
||||
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 && m.StatType == statType) {
|
||||
result.Add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(statType));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -102,10 +107,37 @@ namespace Nox.Game {
|
||||
}
|
||||
|
||||
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 && m.AttributeType == attributeType) {
|
||||
if(m != null && predicate(m) && MeetsRequirements(m, entity)) {
|
||||
result.Add(m);
|
||||
}
|
||||
}
|
||||
@@ -117,14 +149,12 @@ namespace Nox.Game {
|
||||
continue;
|
||||
}
|
||||
foreach(var m in p.Modifiers.modifiers) {
|
||||
if(m != null && m.AttributeType == attributeType) {
|
||||
if(m != null && predicate(m)) {
|
||||
result.Add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ namespace Nox.Game {
|
||||
throw new System.ArgumentException("Too many characters requested.");
|
||||
}
|
||||
var protagonist = characterFactory.CreateProtagonist(characterCreationRequests.Find(r => r.Role == CharacterRole.Protagonist));
|
||||
var companions = characterCreationRequests.FindAll(r => r.Role != CharacterRole.Protagonist).Select(r => characterFactory.CreateProtagonist(r));
|
||||
return partyFactory.Create(protagonist, companions);
|
||||
return partyFactory.Create(protagonist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,13 @@ namespace Nox.Game {
|
||||
public ModifiersData defaultModifiersData;
|
||||
|
||||
[Header("General Racial Bonuses and Perks per Class")]
|
||||
public CharacterRace race;
|
||||
|
||||
public CharacterClass @class;
|
||||
public RacialBonuses [] racialBonuses;
|
||||
public ClassBonuses [] classBonuses;
|
||||
|
||||
|
||||
private void OnEnable() {
|
||||
race = (CharacterRace)Random.Range(0, Enum.GetNames(typeof(CharacterRace)).Length-1);
|
||||
@class = (CharacterClass)Random.Range(0, Enum.GetNames(typeof(CharacterClass)).Length-1);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RacialBonuses {
|
||||
public CharacterRace race;
|
||||
public EntityAttributes bonusAttributes;
|
||||
public EntityStats bonusStats;
|
||||
public PerksData startingPerks;
|
||||
public ModifiersData permanentModifiers;
|
||||
}
|
||||
@@ -38,8 +27,6 @@ namespace Nox.Game {
|
||||
[Serializable]
|
||||
public sealed class ClassBonuses {
|
||||
public CharacterClass @class;
|
||||
public EntityAttributes bonusAttributes;
|
||||
public EntityStats bonusStats;
|
||||
public PerksData startingPerks;
|
||||
public ModifiersData permanentModifiers;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user