First commit
This commit is contained in:
33
Assets/Code/GameState/Entities/CharacterAttributesFactory.cs
Normal file
33
Assets/Code/GameState/Entities/CharacterAttributesFactory.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using ZLinq;
|
||||
|
||||
namespace Nox.Game {
|
||||
public interface ICharacterAttributesFactory {
|
||||
EntityAttributes Create(IEntityDefinition entityDefinition);
|
||||
}
|
||||
|
||||
public sealed class CharacterAttributesFactory : ICharacterAttributesFactory {
|
||||
private readonly IModifierResolver modifierResolver;
|
||||
|
||||
public CharacterAttributesFactory(IModifierResolver modifierResolver) {
|
||||
this.modifierResolver = modifierResolver ?? throw new ArgumentNullException(nameof(modifierResolver));
|
||||
}
|
||||
|
||||
public EntityAttributes Create(IEntityDefinition entityDefinition) {
|
||||
var attributes = entityDefinition.Attributes;
|
||||
|
||||
if(attributes.attributes.AsValueEnumerable().Any(a => a.value <= 0)) {
|
||||
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
||||
}
|
||||
|
||||
return new EntityAttributes {
|
||||
attributes = attributes.attributes.AsValueEnumerable()
|
||||
.Select(a => {
|
||||
var modifiers = modifierResolver.CollectModifiers(entityDefinition, a.attribute);
|
||||
return new Attribute(a.attribute, modifierResolver.Resolve(a.value, modifiers, entityDefinition));
|
||||
})
|
||||
.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c5d19a12e554fd981cec483ccfcff68
|
||||
timeCreated: 1774183852
|
||||
139
Assets/Code/GameState/Entities/CharacterFactory.cs
Normal file
139
Assets/Code/GameState/Entities/CharacterFactory.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using ZLinq;
|
||||
|
||||
namespace Nox.Game {
|
||||
public interface ICharacterFactory {
|
||||
CharacterDefinition CreateProtagonist(CharacterCreationRequest request);
|
||||
CharacterDefinition CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
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;
|
||||
public EntityAttributes Attributes { get; set; } = GetDefaultAttributes();
|
||||
public EntityStats Stats { get; set; } = new() { stats = Array.Empty<Stat>() };
|
||||
public PerksData Perks { get; set; } = new();
|
||||
public ModifiersData Modifiers { get; set; } = new();
|
||||
|
||||
private static int GetRandomInt(int start, int end) => new Random().Next(start, end);
|
||||
private static EntityAttributes GetDefaultAttributes() {
|
||||
var attributes = new EntityAttributes {
|
||||
attributes = new Attribute[] {
|
||||
new (AttributeType.Might, 1),
|
||||
new (AttributeType.Knowledge, 1),
|
||||
new (AttributeType.Perception, 1),
|
||||
new (AttributeType.Reflex, 1)
|
||||
}
|
||||
};
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
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;
|
||||
public EntityAttributes Attributes { get; set; }
|
||||
public EntityStats Stats { get; set; } = new() { stats = Array.Empty<Stat>() };
|
||||
public PerksData Perks { get; set; } = new();
|
||||
public ModifiersData Modifiers { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class CharacterFactory : ICharacterFactory {
|
||||
private readonly ICharacterAttributesFactory attributesFactory;
|
||||
private readonly ICharacterStatsFactory statsFactory;
|
||||
private readonly IPerkFactory perkFactory;
|
||||
private readonly IModifiersFactory modifiersFactory;
|
||||
|
||||
public CharacterFactory(
|
||||
ICharacterAttributesFactory attributesFactory,
|
||||
ICharacterStatsFactory statsFactory,
|
||||
IPerkFactory perkFactory,
|
||||
IModifiersFactory modifiersFactory) {
|
||||
this.attributesFactory = attributesFactory ?? throw new ArgumentNullException(nameof(attributesFactory));
|
||||
this.statsFactory = statsFactory ?? throw new ArgumentNullException(nameof(statsFactory));
|
||||
this.perkFactory = perkFactory ?? throw new ArgumentNullException(nameof(perkFactory));
|
||||
this.modifiersFactory = modifiersFactory ?? throw new ArgumentNullException(nameof(modifiersFactory));
|
||||
}
|
||||
|
||||
public CharacterDefinition CreateProtagonist(CharacterCreationRequest request) {
|
||||
if(request == null) {
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var attributes = attributesFactory.Create(request);
|
||||
var stats = statsFactory.Create(request);
|
||||
|
||||
var character = new CharacterDefinition {
|
||||
Id = request.Id == Guid.Empty ? Guid.NewGuid() : request.Id,
|
||||
Name = request.Name,
|
||||
Race = request.Race,
|
||||
Class = request.Class,
|
||||
Role = CharacterRole.Protagonist,
|
||||
PortraitIndex = request.PortraitIndex,
|
||||
Attributes = attributes,
|
||||
Stats = stats,
|
||||
Perks = request.Perks ?? new PerksData(),
|
||||
Modifiers = request.Modifiers ?? new ModifiersData()
|
||||
};
|
||||
|
||||
AddStartingPerks(character, request.Perks);
|
||||
return character;
|
||||
}
|
||||
|
||||
public CharacterDefinition CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion) {
|
||||
if(template == null) {
|
||||
throw new ArgumentNullException(nameof(template));
|
||||
}
|
||||
|
||||
template.Perks ??= new PerksData();
|
||||
template.Modifiers ??= new ModifiersData();
|
||||
|
||||
var character = new CharacterDefinition {
|
||||
Id = template.Id == Guid.Empty ? Guid.NewGuid() : template.Id,
|
||||
Name = template.Name,
|
||||
Race = template.Race,
|
||||
Class = template.Class,
|
||||
Role = role,
|
||||
Attributes = attributesFactory.Create(template),
|
||||
Stats = statsFactory.Create(template),
|
||||
Perks = template.Perks,
|
||||
Modifiers = template.Modifiers
|
||||
};
|
||||
|
||||
AddStartingPerks(character, template.Perks);
|
||||
AddStartingModifiers(character, template.Modifiers);
|
||||
return character;
|
||||
}
|
||||
|
||||
private void AddStartingPerks(CharacterDefinition character, PerksData perkData) {
|
||||
if(perkData?.perks == null || perkData.perks.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var perkId in perkData.perks.AsValueEnumerable().Distinct()) {
|
||||
perkFactory.TryAddPerk(character, perkId.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddStartingModifiers(CharacterDefinition character, ModifiersData modifiersData) {
|
||||
if(modifiersData?.modifiers == null || modifiersData.modifiers.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var modifierId in modifiersData.modifiers.AsValueEnumerable().Distinct()) {
|
||||
modifiersFactory.TryAddModifier(character, modifierId.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/CharacterFactory.cs.meta
Normal file
3
Assets/Code/GameState/Entities/CharacterFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0033c4836b0d4fbbb6e4dde0ac23a2f6
|
||||
timeCreated: 1774183767
|
||||
10
Assets/Code/GameState/Entities/CharacterRegistry.cs
Normal file
10
Assets/Code/GameState/Entities/CharacterRegistry.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
[CreateAssetMenu(fileName = "CharacterRegistry", menuName = "Nox/Database/Entities/Character Registry")]
|
||||
public class CharacterRegistry: ScriptableObject {
|
||||
public CharacterDefinition defaultCharacter;
|
||||
public List<CharacterDefinition> characters = new ();
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/CharacterRegistry.cs.meta
Normal file
3
Assets/Code/GameState/Entities/CharacterRegistry.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91e3086d6951456e9a8a59fcbac0f750
|
||||
timeCreated: 1774179358
|
||||
40
Assets/Code/GameState/Entities/CharacterStatsFactory.cs
Normal file
40
Assets/Code/GameState/Entities/CharacterStatsFactory.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using ZLinq;
|
||||
|
||||
namespace Nox.Game {
|
||||
public interface ICharacterStatsFactory {
|
||||
EntityStats Create(IEntityDefinition entityDefinition);
|
||||
}
|
||||
|
||||
public sealed class CharacterStatsFactory : ICharacterStatsFactory {
|
||||
private readonly StarterCharacterSettings baseSettings;
|
||||
private readonly IModifierResolver modifierResolver;
|
||||
|
||||
public CharacterStatsFactory(StarterCharacterSettings baseSettings, IModifierResolver modifierResolver) {
|
||||
this.baseSettings = baseSettings;
|
||||
this.modifierResolver = modifierResolver ?? throw new ArgumentNullException(nameof(modifierResolver));
|
||||
}
|
||||
|
||||
public EntityStats Create(IEntityDefinition entityDefinition) {
|
||||
var attributes = entityDefinition.Attributes;
|
||||
|
||||
if(attributes.attributes.AsValueEnumerable().Any(a => a.value <= 0)) {
|
||||
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
||||
}
|
||||
|
||||
var healthModifiers = modifierResolver.CollectModifiers(entityDefinition, StatType.Health);
|
||||
var manaModifiers = 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, entityDefinition)),
|
||||
new Stat(StatType.Mana, modifierResolver.Resolve(baseSettings.defaultEntityStats.GetValue(StatType.Mana), manaModifiers, 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))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75a241c095744a518eb8d1c1b14d24e4
|
||||
timeCreated: 1774183822
|
||||
26
Assets/Code/GameState/Entities/CharacterSystems.cs
Normal file
26
Assets/Code/GameState/Entities/CharacterSystems.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Nox.Game {
|
||||
|
||||
public interface ICharacterSystems {
|
||||
IPerkFactory PerkFactory { get; }
|
||||
IModifiersFactory ModifiersFactory { get; }
|
||||
IModifierResolver ModifierResolver { get; }
|
||||
ICharacterFactory CharacterFactory { get; }
|
||||
IPartyFactory PartyFactory { get; }
|
||||
}
|
||||
|
||||
public sealed class CharacterSystems : ICharacterSystems {
|
||||
public CharacterSystems(IPerkFactory perkFactory, IModifiersFactory modifiersFactory, IModifierResolver modifierResolver, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
|
||||
ModifiersFactory = modifiersFactory;
|
||||
ModifierResolver = modifierResolver;
|
||||
PerkFactory = perkFactory;
|
||||
CharacterFactory = characterFactory;
|
||||
PartyFactory = partyFactory;
|
||||
}
|
||||
|
||||
public IPerkFactory PerkFactory { get; }
|
||||
public IModifiersFactory ModifiersFactory { get; }
|
||||
public IModifierResolver ModifierResolver { get; }
|
||||
public ICharacterFactory CharacterFactory { get; }
|
||||
public IPartyFactory PartyFactory { get; }
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/CharacterSystems.cs.meta
Normal file
3
Assets/Code/GameState/Entities/CharacterSystems.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9cec54a2252457595e3a97d3dcd755d
|
||||
timeCreated: 1774184077
|
||||
22
Assets/Code/GameState/Entities/CharacterSystemsFactory.cs
Normal file
22
Assets/Code/GameState/Entities/CharacterSystemsFactory.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Nox.Game {
|
||||
public static class CharacterSystemsFactory {
|
||||
public static ICharacterSystems Create(
|
||||
PartySettings partySettings,
|
||||
StarterCharacterSettings starterCharacterSettings,
|
||||
PerksRegistry perksRegistry,
|
||||
CharacterRegistry characterRegistry,
|
||||
ModifiersRegistry modifiersRegistry) {
|
||||
IPerkFactory perkFactory = new PerkFactory(perksRegistry);
|
||||
IModifiersFactory modifiersFactory = new ModifiersFactory(modifiersRegistry);
|
||||
IModifierResolver modifierResolver = new ModifierResolver();
|
||||
ICharacterAttributesFactory attributesFactory = new CharacterAttributesFactory(modifierResolver);
|
||||
ICharacterStatsFactory statsFactory = new CharacterStatsFactory(starterCharacterSettings, modifierResolver);
|
||||
ICharacterFactory characterFactory = new CharacterFactory(attributesFactory, statsFactory, perkFactory, modifiersFactory);
|
||||
IPartyFactory partyFactory = new PartyFactory(partySettings, starterCharacterSettings);
|
||||
|
||||
return new CharacterSystems(perkFactory, modifiersFactory, modifierResolver, characterFactory, partyFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60db4014b69d403db42ee766204eb2d7
|
||||
timeCreated: 1774183978
|
||||
192
Assets/Code/GameState/Entities/EntitiesDefinitions.cs
Normal file
192
Assets/Code/GameState/Entities/EntitiesDefinitions.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ZLinq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
public interface IEntityDefinition {
|
||||
Guid Id { get; }
|
||||
string Name { get; }
|
||||
int PortraitIndex { get; }
|
||||
CharacterRace Race { get; }
|
||||
CharacterClass Class { get; }
|
||||
CharacterRole Role { get; }
|
||||
EntityAttributes Attributes { get; }
|
||||
EntityStats Stats { get; }
|
||||
PerksData Perks { get; }
|
||||
ModifiersData Modifiers { get; }
|
||||
|
||||
}
|
||||
|
||||
public enum StatType {
|
||||
None,
|
||||
Health,
|
||||
Mana,
|
||||
Level,
|
||||
Experience
|
||||
}
|
||||
|
||||
public enum AttributeType {
|
||||
None,
|
||||
Might,
|
||||
Reflex,
|
||||
Knowledge,
|
||||
Perception
|
||||
}
|
||||
|
||||
public enum CombatScoreType {
|
||||
None,
|
||||
ATK,
|
||||
DEF,
|
||||
INIT,
|
||||
SPD,
|
||||
RES
|
||||
}
|
||||
|
||||
public enum CharacterRole {
|
||||
None,
|
||||
Protagonist,
|
||||
Companion
|
||||
}
|
||||
|
||||
public enum CharacterClass {
|
||||
None,
|
||||
Warrior,
|
||||
Rogue,
|
||||
Mage,
|
||||
Herald
|
||||
}
|
||||
|
||||
public enum CharacterRace {
|
||||
None,
|
||||
Human,
|
||||
DarkElf,
|
||||
Tunneler
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed record Stat {
|
||||
public StatType stat;
|
||||
public int value;
|
||||
public Stat(StatType stat, int value) {
|
||||
this.stat = stat;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed record Attribute {
|
||||
public AttributeType attribute;
|
||||
public int value;
|
||||
public Attribute(AttributeType attribute, int value) {
|
||||
this.attribute = attribute;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class EntityAttributes {
|
||||
public Attribute[] attributes;
|
||||
|
||||
public static EntityAttributes operator +(EntityAttributes a, EntityAttributes b) {
|
||||
return new EntityAttributes {
|
||||
attributes = a.attributes.AsValueEnumerable()
|
||||
.Select(attr => {
|
||||
var match = b.attributes?.AsValueEnumerable().FirstOrDefault(attr2 => attr2.attribute == attr.attribute);
|
||||
return new Attribute(attr.attribute, attr.value + (match?.value ?? 0));
|
||||
})
|
||||
.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public int GetValue(AttributeType attributeType) {
|
||||
return attributes.AsValueEnumerable().First(attr => attr.attribute == attributeType).value;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"Attributes: {string.Join(", ", attributes.Select(attr => $"{attr.attribute}: {attr.value}"))}";
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class EntityStats {
|
||||
public Stat[] stats;
|
||||
|
||||
public int GetValue(StatType statType) {
|
||||
if(stats == null) {
|
||||
return 0;
|
||||
}
|
||||
var match = stats.AsValueEnumerable().FirstOrDefault(stat => stat.stat == statType);
|
||||
return match?.value ?? 0;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"Stats: {string.Join(", ", stats.Select(stat => $"{stat.stat}: {stat.value}"))}";
|
||||
}
|
||||
}
|
||||
|
||||
[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; }
|
||||
[field: SerializeField] public CharacterRole Role { get; set; }
|
||||
[field: SerializeField] public EntityAttributes Attributes { get; set; }
|
||||
[field: SerializeField] public EntityStats Stats { get; set; }
|
||||
[field: SerializeField] public PerksData Perks { get; set; }
|
||||
[field: SerializeField] public ModifiersData Modifiers { get; set; }
|
||||
|
||||
public CharacterDefinition Clone() {
|
||||
return new CharacterDefinition {
|
||||
Id = Guid.NewGuid(),
|
||||
Name = Name,
|
||||
Role = Role,
|
||||
Race = Race,
|
||||
Class = Class,
|
||||
PortraitIndex = PortraitIndex,
|
||||
Attributes = new EntityAttributes {
|
||||
attributes = Attributes?.attributes?.AsValueEnumerable().Select(a => new Attribute(a.attribute, a.value)).ToArray()
|
||||
},
|
||||
Stats = new EntityStats {
|
||||
stats = Stats?.stats?.AsValueEnumerable().Select(s => new Stat(s.stat, s.value)).ToArray()
|
||||
},
|
||||
Perks = new PerksData {
|
||||
perks = Perks?.perks?.AsValueEnumerable().Select(p => new PerkDefinition {
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
Modifiers = p.Modifiers
|
||||
}).ToList() ?? new List<PerkDefinition>()
|
||||
},
|
||||
Modifiers = new ModifiersData {
|
||||
modifiers = Modifiers?.modifiers?.AsValueEnumerable().Select(m => new ModifierDefinition {
|
||||
Id = m.Id,
|
||||
Name = m.Name,
|
||||
Target = m.Target,
|
||||
ScalingSource = m.ScalingSource,
|
||||
Operation = m.Operation,
|
||||
Value = m.Value,
|
||||
Requirements = m.Requirements?.ToList() ?? new List<ModifierRequirement>()
|
||||
}).ToList() ?? new List<ModifierDefinition>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PartyDefinition {
|
||||
public int maxPartySize;
|
||||
public List<CharacterDefinition> members = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public CharacterDefinition Protagonist => members.AsValueEnumerable().FirstOrDefault(m => m.Role == CharacterRole.Protagonist);
|
||||
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<CharacterDefinition> Companions => members.AsValueEnumerable().Where(m => m.Role == CharacterRole.Companion).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98b50e969ea0412ca3575cd28b6018b7
|
||||
timeCreated: 1772574533
|
||||
159
Assets/Code/GameState/Entities/ModifiersFactory.cs
Normal file
159
Assets/Code/GameState/Entities/ModifiersFactory.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Jovian.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ZLinq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
public interface IModifier {
|
||||
string Name { get; }
|
||||
Guid Id { get; }
|
||||
ModifierTarget Target { get; }
|
||||
ModifierTarget ScalingSource { get; }
|
||||
ModifierOperation Operation { get; }
|
||||
float Value { get; }
|
||||
IReadOnlyList<ModifierRequirement> Requirements { get; }
|
||||
}
|
||||
|
||||
public interface IModifiersFactory {
|
||||
IReadOnlyCollection<IModifier> GetAll();
|
||||
IModifier GetById(Guid modifierId);
|
||||
IReadOnlyCollection<IModifier> GetModifiersFor(IEntityDefinition character);
|
||||
bool TryAddModifier(IEntityDefinition character, Guid modiferId);
|
||||
}
|
||||
|
||||
public enum ModifierOperation {
|
||||
None,
|
||||
Flat,
|
||||
Addition,
|
||||
Multiplication,
|
||||
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 ModifierTarget Target { get; set; }
|
||||
[field: SerializeField] public ModifierTarget ScalingSource { get; set; }
|
||||
[field: SerializeField] public ModifierOperation Operation { 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 override string ToString() {
|
||||
return $"Modifiers: {string.Join(", ", modifiers.Select(modifier => $"{modifier.Name}"))}";
|
||||
}
|
||||
}
|
||||
|
||||
public class ModifiersFactory : IModifiersFactory {
|
||||
private readonly ModifiersRegistry modifiersRegistry;
|
||||
private readonly Dictionary<Guid, IModifier> modifierPool = new();
|
||||
|
||||
public ModifiersFactory(ModifiersRegistry modifiersRegistry) {
|
||||
this.modifiersRegistry = modifiersRegistry;
|
||||
var allAvailableModifiers = modifiersRegistry.modifiersData;
|
||||
foreach(var modifier in allAvailableModifiers.modifiers) {
|
||||
modifierPool.Add(modifier.Id, modifier);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<IModifier> GetAll() {
|
||||
return modifiersRegistry.modifiersData.modifiers;
|
||||
}
|
||||
public IModifier GetById(Guid modifierId) {
|
||||
return modifiersRegistry.modifiersData.modifiers.AsValueEnumerable().FirstOrDefault(m => m.Id == modifierId);
|
||||
}
|
||||
public IReadOnlyCollection<IModifier> GetModifiersFor(IEntityDefinition character) {
|
||||
return character.Modifiers.modifiers;
|
||||
}
|
||||
public bool TryAddModifier(IEntityDefinition character, Guid modifierId) {
|
||||
if(character == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(modifierId == Guid.Empty) {
|
||||
GlobalLogger.LogException("Cannot add a modifier with an empty ID!", LogCategory.GameLogic);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(character.Modifiers.modifiers.AsValueEnumerable().Any(p => p != null && p.Id == modifierId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!modifierPool.TryGetValue(modifierId, out var modifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
character.Modifiers.modifiers.Add(new ModifierDefinition {
|
||||
Id = modifier.Id,
|
||||
Name = modifier.Name,
|
||||
Target = modifier.Target,
|
||||
ScalingSource = modifier.ScalingSource,
|
||||
Operation = modifier.Operation,
|
||||
Value = modifier.Value,
|
||||
Requirements = modifier.Requirements?.ToList() ?? new List<ModifierRequirement>()
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/ModifiersFactory.cs.meta
Normal file
3
Assets/Code/GameState/Entities/ModifiersFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 882798d3e5a24150b1f11f486afdbc3e
|
||||
timeCreated: 1774199617
|
||||
8
Assets/Code/GameState/Entities/ModifiersRegistry.cs
Normal file
8
Assets/Code/GameState/Entities/ModifiersRegistry.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
[CreateAssetMenu(fileName = "ModifiersRegistry", menuName = "Nox/Database/Entities/Modifiers Registry")]
|
||||
public class ModifiersRegistry : ScriptableObject {
|
||||
public ModifiersData modifiersData;
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/ModifiersRegistry.cs.meta
Normal file
3
Assets/Code/GameState/Entities/ModifiersRegistry.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6ae0d7bf0164ff793609e7f161fd7e3
|
||||
timeCreated: 1774200023
|
||||
160
Assets/Code/GameState/Entities/ModifiersResolver.cs
Normal file
160
Assets/Code/GameState/Entities/ModifiersResolver.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/ModifiersResolver.cs.meta
Normal file
3
Assets/Code/GameState/Entities/ModifiersResolver.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 130a7ebdbcf2417392d8fc3f9ae76eb2
|
||||
timeCreated: 1774176049
|
||||
28
Assets/Code/GameState/Entities/PartyCreatorModel.cs
Normal file
28
Assets/Code/GameState/Entities/PartyCreatorModel.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nox.Game {
|
||||
public class PartyCreatorModel {
|
||||
private readonly ICharacterFactory characterFactory;
|
||||
private readonly IPartyFactory partyFactory;
|
||||
private readonly List<CharacterCreationRequest> characterCreationRequests;
|
||||
private readonly PartySettings partySettings;
|
||||
|
||||
public PartyCreatorModel(ICharacterFactory characterFactory,
|
||||
IPartyFactory partyFactory,
|
||||
List<CharacterCreationRequest> characterCreationRequests,
|
||||
PartySettings partySettings) {
|
||||
this.characterFactory = characterFactory;
|
||||
this.partyFactory = partyFactory;
|
||||
this.characterCreationRequests = characterCreationRequests;
|
||||
this.partySettings = partySettings;
|
||||
}
|
||||
public PartyDefinition CreatePartyForNewRun() {
|
||||
if(characterCreationRequests.Count > partySettings.maxPartySize) {
|
||||
throw new System.ArgumentException("Too many characters requested.");
|
||||
}
|
||||
var protagonist = characterFactory.CreateProtagonist(characterCreationRequests.Find(r => r.Role == CharacterRole.Protagonist));
|
||||
return partyFactory.Create(protagonist);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/PartyCreatorModel.cs.meta
Normal file
3
Assets/Code/GameState/Entities/PartyCreatorModel.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f231d6e487bc4577847de98936498bde
|
||||
timeCreated: 1772644731
|
||||
64
Assets/Code/GameState/Entities/PartyFactory.cs
Normal file
64
Assets/Code/GameState/Entities/PartyFactory.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ZLinq;
|
||||
|
||||
namespace Nox.Game {
|
||||
public interface IPartyFactory {
|
||||
PartyDefinition Create(CharacterDefinition protagonist, IEnumerable<CharacterDefinition> companions = null);
|
||||
}
|
||||
|
||||
public sealed class PartyFactory : IPartyFactory {
|
||||
private readonly PartySettings partySettings;
|
||||
private readonly StarterCharacterSettings starterCharacterSettings;
|
||||
|
||||
public PartyFactory(PartySettings partySettings, StarterCharacterSettings starterCharacterSettings) {
|
||||
this.partySettings = partySettings;
|
||||
this.starterCharacterSettings = starterCharacterSettings;
|
||||
}
|
||||
|
||||
public PartyDefinition Create(CharacterDefinition protagonist, IEnumerable<CharacterDefinition> companions = null) {
|
||||
if(protagonist == null) {
|
||||
throw new ArgumentNullException(nameof(protagonist));
|
||||
}
|
||||
|
||||
var party = new PartyDefinition {
|
||||
maxPartySize = partySettings.maxPartySize <= 0 ? int.MaxValue : partySettings.maxPartySize
|
||||
};
|
||||
|
||||
var protagonistClone = protagonist.Clone();
|
||||
protagonistClone.Role = CharacterRole.Protagonist;
|
||||
party.members.Add(protagonistClone);
|
||||
|
||||
if(companions != null) {
|
||||
foreach(var companion in companions.AsValueEnumerable().Where(c => c != null)) {
|
||||
var companionClone = companion.Clone();
|
||||
companionClone.Role = CharacterRole.Companion;
|
||||
party.members.Add(companionClone);
|
||||
}
|
||||
}
|
||||
|
||||
ValidateParty(party);
|
||||
return party;
|
||||
}
|
||||
|
||||
private void ValidateParty(PartyDefinition party) {
|
||||
if(party.members.Count > party.maxPartySize) {
|
||||
throw new ArgumentException($"Party size {party.members.Count} exceeds max {party.maxPartySize}.");
|
||||
}
|
||||
|
||||
var protagonistCount = party.members.AsValueEnumerable().Count(m => m.Role == CharacterRole.Protagonist);
|
||||
if(protagonistCount != 1) {
|
||||
throw new ArgumentException($"Party must contain exactly one protagonist, found {protagonistCount}.");
|
||||
}
|
||||
|
||||
var uniqueIds = party.members.AsValueEnumerable()
|
||||
.Where(m => m.Id != Guid.Empty)
|
||||
.Select(m => m.Id)
|
||||
.Distinct()
|
||||
.Count();
|
||||
if(uniqueIds != party.members.Count) {
|
||||
throw new ArgumentException("Party contains duplicate or missing character ids.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/PartyFactory.cs.meta
Normal file
3
Assets/Code/GameState/Entities/PartyFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f54ae3aaf9da4f5eaeb6d1cafe83a74f
|
||||
timeCreated: 1774183779
|
||||
6
Assets/Code/GameState/Entities/PartyReference.cs
Normal file
6
Assets/Code/GameState/Entities/PartyReference.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
public class PartyReference : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/Entities/PartyReference.cs.meta
Normal file
2
Assets/Code/GameState/Entities/PartyReference.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 067750df0e87b834bbbb239b7c03abb4
|
||||
50
Assets/Code/GameState/Entities/PartySettings.cs
Normal file
50
Assets/Code/GameState/Entities/PartySettings.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ZLinq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
[CreateAssetMenu(fileName = "DefaultPartySettings", menuName = "Nox/Database/Entities/Default Party Settings")]
|
||||
public class PartySettings : ScriptableObject {
|
||||
[Header("Party System Defaults")]
|
||||
public int maxPartySize = 4;
|
||||
|
||||
[Header("This will be default starting set")]
|
||||
public string testStartingSetId;
|
||||
|
||||
[Header("Party Definition Sets")]
|
||||
public List<PartyDefinitionSet> testPartyDefinitionSets;
|
||||
|
||||
private void OnValidate() {
|
||||
if(String.IsNullOrEmpty(testStartingSetId)) {
|
||||
Debug.LogError("DefaultPartySettings: startingSetId cannot be null or empty");
|
||||
return;
|
||||
}
|
||||
foreach(var partyDefinitionSet in testPartyDefinitionSets) {
|
||||
var partyDefinition = partyDefinitionSet.partyDefinition;
|
||||
if(partyDefinition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(var i = 0; i < partyDefinition.maxPartySize; i++) {
|
||||
if (partyDefinition.members.Count <= i) {
|
||||
partyDefinition.members.Add(new CharacterDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
if(partyDefinition.members.Count <= partyDefinition.maxPartySize) {
|
||||
continue;
|
||||
}
|
||||
Debug.LogError($"Party definition '{partyDefinitionSet.id}' has more members than the maximum allowed size.Removing extra members.");
|
||||
partyDefinition.members.RemoveRange(partyDefinition.maxPartySize, partyDefinition.members.Count - partyDefinition.maxPartySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PartyDefinitionSet {
|
||||
public string id;
|
||||
public bool isTestingSet;
|
||||
public PartyDefinition partyDefinition;
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/PartySettings.cs.meta
Normal file
3
Assets/Code/GameState/Entities/PartySettings.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff702898b3cd4100b9b8230f04711fa9
|
||||
timeCreated: 1774195937
|
||||
99
Assets/Code/GameState/Entities/PerkFactory.cs
Normal file
99
Assets/Code/GameState/Entities/PerkFactory.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Jovian.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ZLinq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
|
||||
public interface IPerk {
|
||||
Guid Id { get; }
|
||||
string Name { get; }
|
||||
ModifiersData Modifiers { get; }
|
||||
}
|
||||
|
||||
public interface IPerkFactory {
|
||||
IReadOnlyCollection<IPerk> GetAll();
|
||||
IPerk GetById(Guid perkId);
|
||||
IReadOnlyCollection<IPerk> GetPerksFor(IEntityDefinition character);
|
||||
bool TryAddPerk(IEntityDefinition character, Guid perkId);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PerkDefinition : IPerk {
|
||||
[field: SerializeField] public string Name { get; set; }
|
||||
[field: SerializeField] public ModifiersData Modifiers { get; set; }
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PerksData {
|
||||
public List<PerkDefinition> perks = new ();
|
||||
|
||||
public override string ToString() {
|
||||
return $"Perks: {string.Join(", ", perks.Select(perk => $"{perk.Name}"))}";
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PerkFactory : IPerkFactory {
|
||||
private readonly Dictionary<Guid, IPerk> perkPool = new ();
|
||||
|
||||
public PerkFactory(PerksRegistry perksRegistry) {
|
||||
if(!perksRegistry) {
|
||||
throw new ArgumentNullException(nameof(perksRegistry));
|
||||
}
|
||||
var allAvailablePerks = perksRegistry.perksData;
|
||||
foreach(var perk in allAvailablePerks.perks) {
|
||||
perkPool.Add(perk.Id, perk);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<IPerk> GetAll() {
|
||||
return perkPool.Values.AsValueEnumerable().ToList();
|
||||
}
|
||||
|
||||
public IPerk GetById(Guid perkId) {
|
||||
perkPool.TryGetValue(perkId, out var perk);
|
||||
return perk;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<IPerk> GetPerksFor(IEntityDefinition character) {
|
||||
if(character == null) {
|
||||
return perkPool.Values.AsValueEnumerable().ToList();
|
||||
}
|
||||
|
||||
var ownedPerkIds = character.Perks.perks.AsValueEnumerable()
|
||||
.Select(p => p.Id)
|
||||
.ToHashSet();
|
||||
|
||||
return perkPool.Values.AsValueEnumerable().Where(p => !ownedPerkIds.Contains(p.Id)).ToList();
|
||||
}
|
||||
|
||||
public bool TryAddPerk(IEntityDefinition character, Guid perkId) {
|
||||
if(character == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(perkId == Guid.Empty) {
|
||||
GlobalLogger.LogException("Cannot add a perk with an empty Id!", LogCategory.GameLogic);
|
||||
}
|
||||
|
||||
if(character.Perks.perks.AsValueEnumerable().Any(p => p != null && p.Id == perkId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!perkPool.TryGetValue(perkId, out var perk)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
character.Perks.perks.Add(new PerkDefinition {
|
||||
Id = perk.Id,
|
||||
Name = perk.Name,
|
||||
Modifiers = perk.Modifiers
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/PerkFactory.cs.meta
Normal file
3
Assets/Code/GameState/Entities/PerkFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1de2461f550e4ef8a324d5746222898f
|
||||
timeCreated: 1774183789
|
||||
8
Assets/Code/GameState/Entities/PerksRegistry.cs
Normal file
8
Assets/Code/GameState/Entities/PerksRegistry.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
[CreateAssetMenu(fileName = "PerksRegistry", menuName = "Nox/Database/Entities/Perks Registry")]
|
||||
public class PerksRegistry : ScriptableObject {
|
||||
public PerksData perksData;
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/Entities/PerksRegistry.cs.meta
Normal file
3
Assets/Code/GameState/Entities/PerksRegistry.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0adb42b13ce44d8792247246a49e9c3f
|
||||
timeCreated: 1774179294
|
||||
39
Assets/Code/GameState/Entities/StarterCharacterSettings.cs
Normal file
39
Assets/Code/GameState/Entities/StarterCharacterSettings.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Nox.Game {
|
||||
[CreateAssetMenu(fileName = "CharacterBaseSettings", menuName = "Nox/Database/Entities/CharacterBaseSettings")]
|
||||
public class StarterCharacterSettings: ScriptableObject {
|
||||
[Header("Character Creation Defaults")]
|
||||
public DistributionPointsPerClass[] distributionPointsPerClass;
|
||||
public EntityAttributes defaultEntityAttributes;
|
||||
public EntityStats defaultEntityStats;
|
||||
public PerksData defaultPerksData;
|
||||
public ModifiersData defaultModifiersData;
|
||||
|
||||
[Header("General Racial Bonuses and Perks per Class")]
|
||||
public RacialBonuses [] racialBonuses;
|
||||
public ClassBonuses [] classBonuses;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RacialBonuses {
|
||||
public CharacterRace race;
|
||||
public PerksData startingPerks;
|
||||
public ModifiersData permanentModifiers;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ClassBonuses {
|
||||
public CharacterClass @class;
|
||||
public PerksData startingPerks;
|
||||
public ModifiersData permanentModifiers;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class DistributionPointsPerClass {
|
||||
public CharacterClass @class;
|
||||
public int points;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bbecc15c7cd4a7ca90ce17b3d3d75f0
|
||||
timeCreated: 1774184829
|
||||
Reference in New Issue
Block a user