End of refactor for the Entities

This commit is contained in:
Sebastian Bularca
2026-03-30 00:33:07 +02:00
parent 00c1764fdb
commit 30f319a52d
11 changed files with 215 additions and 210 deletions

View File

@@ -9,25 +9,39 @@ namespace Nox.Game {
[Serializable]
public sealed class CharacterTemplate {
public string id;
public string displayName;
public EntityAttributes attributes;
public CharacterRace characterRace;
public CharacterClass characterClass;
public EntityStats stats;
public PerksData perksData = new ();
public Guid id = Guid.NewGuid();
public string displayName = "New Character";
public CharacterRace race = (CharacterRace)GetRandomInt(1, Enum.GetValues(typeof(CharacterRace)).Length-1);
public CharacterClass @class = (CharacterClass)GetRandomInt(1, Enum.GetValues(typeof(CharacterClass)).Length-1);
public EntityAttributes attributes = GetDefaultAttributes();
public PerksData perksData = new();
public ModifiersData modifiersData = 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 CustomCharacterCreationRequest {
public string id;
public Guid id = Guid.Empty;
public string displayName;
public CharacterRace characterRace;
public CharacterClass characterClass;
public CharacterRace race;
public CharacterClass @class;
public EntityStats stats;
public EntityAttributes attributes;
public PerksData perksData = new();
public ModifiersData modifiersData = new();
public PerksData perks = new();
public ModifiersData modifiers = new();
}
public sealed class CharacterFactory : ICharacterFactory {
@@ -53,18 +67,18 @@ namespace Nox.Game {
var stats = statsFactory.Create(request.attributes);
var character = new CharacterDefinition {
ID = string.IsNullOrWhiteSpace(request.id) ? Guid.NewGuid().ToString("N") : request.id,
DisplayName = request.displayName,
race = request.characterRace,
@class = request.characterClass,
role = CharacterRole.Protagonist,
ID = request.id == Guid.Empty ? Guid.NewGuid() : request.id,
Name = request.displayName,
Race = request.race,
Class = request.@class,
Role = CharacterRole.Protagonist,
Attributes = attributes,
Stats = stats,
perksData = request.perksData ?? new PerksData(),
activeModifiers = request.modifiersData ?? new ModifiersData()
Perks = request.perks ?? new PerksData(),
Modifiers = request.modifiers ?? new ModifiersData()
};
AddStartingPerks(character, request.perksData);
AddStartingPerks(character, request.perks);
return character;
}
@@ -73,21 +87,23 @@ namespace Nox.Game {
throw new ArgumentNullException(nameof(template));
}
var attributes = attributesFactory.Create(template.attributes);
template.perksData ??= new PerksData();
template.modifiersData ??= new ModifiersData();
var character = new CharacterDefinition {
ID = string.IsNullOrWhiteSpace(template.id) ? Guid.NewGuid().ToString("N") : template.id,
DisplayName = template.displayName,
race = template.characterRace,
@class = template.characterClass,
role = role,
Attributes = attributes,
Stats = statsFactory.Create(attributes),
perksData = new PerksData(),
activeModifiers = new ModifiersData()
ID = template.id == Guid.Empty ? Guid.NewGuid() : template.id,
Name = template.displayName,
Race = template.race,
Class = template.@class,
Role = role,
Attributes = attributesFactory.Create(template.attributes),
Stats = statsFactory.Create(template.attributes),
Perks = template.perksData,
Modifiers = template.modifiersData
};
AddStartingPerks(character, template.perksData);
AddStartingModifiers(character, template.modifiersData);
return character;
}
@@ -97,7 +113,17 @@ namespace Nox.Game {
}
foreach(var perkId in perkData.perks.Distinct()) {
perkFactory.TryAddPerk(character, perkId.id);
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.Distinct()) {
perkFactory.TryAddPerk(character, modifierId.Id);
}
}
}

View File

@@ -2,13 +2,13 @@ namespace Nox.Game {
public interface ICharacterSystems {
IPerkFactory PerkFactory { get; }
IModfiersFactory ModifiersFactory { get; }
IModifiersFactory ModifiersFactory { get; }
ICharacterFactory CharacterFactory { get; }
IPartyFactory PartyFactory { get; }
}
public sealed class CharacterSystems : ICharacterSystems {
public CharacterSystems(IPerkFactory perkFactory, IModfiersFactory modifiersFactory, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
public CharacterSystems(IPerkFactory perkFactory, IModifiersFactory modifiersFactory, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
ModifiersFactory = modifiersFactory;
PerkFactory = perkFactory;
CharacterFactory = characterFactory;
@@ -16,7 +16,7 @@ namespace Nox.Game {
}
public IPerkFactory PerkFactory { get; }
public IModfiersFactory ModifiersFactory { get; }
public IModifiersFactory ModifiersFactory { get; }
public ICharacterFactory CharacterFactory { get; }
public IPartyFactory PartyFactory { get; }
}

View File

@@ -8,7 +8,7 @@ namespace Nox.Game {
CharacterRegistry characterRegistry,
ModifiersRegistry modifiersRegistry) {
IPerkFactory perkFactory = new PerkFactory(perksRegistry);
IModfiersFactory modifiersFactory = new ModifiersFactory(modifiersRegistry);
IModifiersFactory modifiersFactory = new ModifiersFactory(modifiersRegistry);
ICharacterAttributesFactory attributesFactory = new CharacterAttributesFactory(characterRegistry);
ICharacterStatsFactory statsFactory = new CharacterStatsFactory(characterRegistry);
ICharacterFactory characterFactory = new CharacterFactory(attributesFactory, statsFactory, perkFactory);

View File

@@ -48,8 +48,8 @@ namespace Nox.Game {
var partyDefinition = testingSet.partyDefinition;
foreach(var member in partyDefinition.members) {
var baseSettings = starterCharacterSettings.defaultEntityAttributes;
var classAttributes = starterCharacterSettings.classBonuses.FirstOrDefault(c => c.@class == member.@class)?.bonusAttributes;
var racialAttributes = starterCharacterSettings.racialBonuses.FirstOrDefault(rb => rb.race == member.race)?.bonusAttributes;
var classAttributes = starterCharacterSettings.classBonuses.FirstOrDefault(c => c.@class == member.Class)?.bonusAttributes;
var racialAttributes = starterCharacterSettings.racialBonuses.FirstOrDefault(rb => rb.race == member.Race)?.bonusAttributes;
if (classAttributes != null && racialAttributes != null) {
member.Attributes += baseSettings + classAttributes + racialAttributes;
}

View File

@@ -6,10 +6,16 @@ using UnityEngine;
namespace Nox.Game {
public interface IEntityDefinition {
string ID { get; }
string DisplayName { get; }
Guid ID { get; }
string Name { 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 {
@@ -51,22 +57,21 @@ namespace Nox.Game {
[Serializable]
public sealed class Stat {
private readonly StatType health;
public StatType type;
public StatType stat;
public int value;
public Stat(StatType health, int value) {
this.health = health;
public Stat(StatType stat, int value) {
this.stat = stat;
this.value = value;
}
}
[Serializable]
public sealed record Attribute {
private readonly int values;
public AttributeType attribute;
public int value;
public Attribute(AttributeType attributeType, int values) {
this.values = values;
public Attribute(AttributeType attribute, int value) {
this.attribute = attribute;
this.value = value;
}
}
@@ -89,55 +94,37 @@ namespace Nox.Game {
public Stat[] stats;
public int GetValue(StatType statType) {
return stats.First(stat => stat.type == statType).value;
return stats.First(stat => stat.stat == statType).value;
}
}
[Serializable]
public sealed class CharacterDefinition : IEntityDefinition {
[SerializeField] private string id;
[SerializeField] private string displayName;
public CharacterRace race;
public CharacterClass @class;
public CharacterRole role;
[SerializeField] private EntityAttributes attributes;
[SerializeField] private EntityStats stats;
public PerksData perksData = new();
public ModifiersData activeModifiers = new();
public class CharacterDefinition : IEntityDefinition {
public Guid ID { get; set; }
[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 = id,
displayName = displayName,
role = role,
race = race,
@class = @class,
attributes = new EntityAttributes(),
stats = new EntityStats(),
perksData = new PerksData(),
activeModifiers = new ModifiersData()
ID = Guid.NewGuid(),
Name = Name,
Role = Role,
Race = Race,
Class = Class,
Attributes = Attributes,
Stats = Stats,
Perks = Perks,
Modifiers = Modifiers
};
}
public string ID {
get => id;
set => id = value;
}
public string DisplayName {
get => displayName;
set => displayName = value;
}
public EntityAttributes Attributes {
get => attributes;
set => attributes = value;
}
public EntityStats Stats {
get => stats;
set => stats = value;
}
}
[Serializable]
@@ -146,9 +133,9 @@ namespace Nox.Game {
public List<CharacterDefinition> members = new();
[JsonIgnore]
public CharacterDefinition Protagonist => members.FirstOrDefault(m => m.role == CharacterRole.Protagonist);
public CharacterDefinition Protagonist => members.FirstOrDefault(m => m.Role == CharacterRole.Protagonist);
[JsonIgnore]
public IReadOnlyList<CharacterDefinition> Companions => members.Where(m => m.role == CharacterRole.Companion).ToList();
public IReadOnlyList<CharacterDefinition> Companions => members.Where(m => m.Role == CharacterRole.Companion).ToList();
}
}

View File

@@ -1,14 +1,26 @@
using Jovian.InspectorTools;
using Jovian.Logger;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Nox.Game {
public interface IModfiersFactory {
IReadOnlyCollection<ModifierDefinition> GetAll();
ModifierDefinition GetById(Guid modifierId);
IReadOnlyCollection<ModifierDefinition> GetModifiersFor(CharacterDefinition character);
bool TryAddModifier(CharacterDefinition character, string modiferId);
public interface IModifier {
string Name { get; }
Guid Id { get; }
StatType StatType { get; }
AttributeType AttributeType { get; }
ModifierOperation Operation { get; }
float Value { 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 {
@@ -20,44 +32,65 @@ namespace Nox.Game {
}
[Serializable]
public sealed class ModifierDefinition {
public string name;
[ReadOnly]
public Guid id = Guid.NewGuid();
public StatType statType;
public AttributeType attributeType;
public ModifierOperation operation;
public float value;
public sealed class ModifierDefinition : IModifier {
[field: SerializeField] public string Name { get; set; }
public Guid Id { get; set; }
[field: SerializeField] public StatType StatType { get; set; }
[field: SerializeField] public AttributeType AttributeType { get; set; }
[field: SerializeField] public ModifierOperation Operation { get; set; }
[field: SerializeField] public float Value { get; set; }
}
[Serializable]
public sealed class ModifiersData {
public ModifierDefinition[] modifiers;
public List<ModifierDefinition> modifiers = new ();
}
public class ModifiersFactory : IModfiersFactory {
public class ModifiersFactory : IModifiersFactory {
private readonly ModifiersRegistry modifiersRegistry;
private readonly Dictionary<Guid, ModifierDefinition> modifierPool = new ();
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);
modifierPool.Add(modifier.Id, modifier);
}
}
public IReadOnlyCollection<ModifierDefinition> GetAll() {
public IReadOnlyCollection<IModifier> GetAll() {
return modifiersRegistry.modifiersData.modifiers;
}
public ModifierDefinition GetById(Guid modifierId) {
return modifiersRegistry.modifiersData.modifiers.FirstOrDefault(m => m.id == modifierId);
public IModifier GetById(Guid modifierId) {
return modifiersRegistry.modifiersData.modifiers.FirstOrDefault(m => m.Id == modifierId);
}
public IReadOnlyCollection<ModifierDefinition> GetModifiersFor(CharacterDefinition character) {
throw new NotImplementedException();
public IReadOnlyCollection<IModifier> GetModifiersFor(IEntityDefinition character) {
return character.Modifiers.modifiers;
}
public bool TryAddModifier(CharacterDefinition character, string modiferId) {
throw new NotImplementedException();
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.Any(p => p != null && p.Id == modifierId)) {
return false;
}
if(!modifierPool.TryGetValue(modifierId, out var modifier)) {
return false;
}
character.Perks.perks.Add(new PerkDefinition {
Id = modifier.Id,
Name = modifier.Name
});
return true;
}
}
}

View File

@@ -10,55 +10,28 @@ namespace Nox.Game {
}
public PartyDefinition CreatePartyForNewRun(int companionCount) {
var protagonist = characterFactory.CreateCustomProtagonist(new CustomCharacterCreationRequest {
id = "protagonist",
displayName = "The Warden",
displayName = "John Doe",
attributes = new EntityAttributes(),
perksData = new PerksData(){
perks = new PerksData(){
perks = new List<PerkDefinition>()
}
});
var rook_attributes = new EntityAttributes();
rook_attributes.attributes = new[] {
new Attribute(AttributeType.Might, 6),
new Attribute(AttributeType.Knowledge, 2),
new Attribute(AttributeType.Perception, 2),
new Attribute(AttributeType.Reflex, 4)
};
CharacterTemplate[] companionTemplates = {
// new() {
// id = "companion-bruiser",
// displayName = "Rook",
// attributes = new EntityAttributes { might = 5, reflex = 2, knowledge = 1 },
// perksData = new PerksData(){
// perks = new List<PerkDefinition>()
// }
// },
// new() {
// id = "companion-scout",
// displayName = "Sable",
// attributes = new EntityAttributes { might = 2, reflex = 5, knowledge = 1 },
// perksData = new PerksData(){
// perks = new List<PerkDefinition>()
// }
// },
// new() {
// id = "companion-scholar",
// displayName = "Quill",
// attributes = new EntityAttributes { might = 1, reflex = 2, knowledge = 5 },
// perksData = new PerksData(){
// perks = new List<PerkDefinition>()
// }
// },
// new() {
// id = "companion-vanguard",
// displayName = "Brant",
// attributes = new EntityAttributes { might = 4, reflex = 3, knowledge = 2 },
// perksData = new PerksData(){
// perks = new List<PerkDefinition>()
// }
// },
// new() {
// id = "companion-tracker",
// displayName = "Mira",
// attributes = new EntityAttributes { might = 2, reflex = 4, knowledge = 3 },
// perksData = new PerksData(){
// perks = new List<PerkDefinition>()
// }
// }
new() {
displayName = "Rook",
attributes = rook_attributes,
perksData = new PerksData(){
perks = new List<PerkDefinition>()
}
}
};
var companions = new List<CharacterDefinition>();

View File

@@ -24,13 +24,13 @@ namespace Nox.Game {
};
var protagonistClone = protagonist.Clone();
protagonistClone.role = CharacterRole.Protagonist;
protagonistClone.Role = CharacterRole.Protagonist;
party.members.Add(protagonistClone);
if(companions != null) {
foreach(var companion in companions.Where(c => c != null)) {
var companionClone = companion.Clone();
companionClone.role = CharacterRole.Companion;
companionClone.Role = CharacterRole.Companion;
party.members.Add(companionClone);
}
}
@@ -44,13 +44,13 @@ namespace Nox.Game {
throw new ArgumentException($"Party size {party.members.Count} exceeds max {party.maxPartySize}.");
}
int protagonistCount = party.members.Count(m => m.role == CharacterRole.Protagonist);
var protagonistCount = party.members.Count(m => m.Role == CharacterRole.Protagonist);
if(protagonistCount != 1) {
throw new ArgumentException($"Party must contain exactly one protagonist, found {protagonistCount}.");
}
int uniqueIds = party.members
.Where(m => !string.IsNullOrWhiteSpace(m.ID))
var uniqueIds = party.members
.Where(m => m.ID != Guid.Empty)
.Select(m => m.ID)
.Distinct()
.Count();

View File

@@ -1,23 +1,29 @@
using Jovian.InspectorTools;
using Jovian.Logger;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Nox.Game {
public interface IPerk {
Guid Id { get; }
string Name { get; }
ModifiersData Modifiers { get; }
}
public interface IPerkFactory {
IReadOnlyCollection<PerkDefinition> GetAll();
PerkDefinition GetById(Guid perkId);
IReadOnlyCollection<PerkDefinition> GetPerksFor(CharacterDefinition character);
bool TryAddPerk(CharacterDefinition character, Guid perkId);
IReadOnlyCollection<IPerk> GetAll();
IPerk GetById(Guid perkId);
IReadOnlyCollection<IPerk> GetPerksFor(IEntityDefinition character);
bool TryAddPerk(IEntityDefinition character, Guid perkId);
}
[Serializable]
public sealed class PerkDefinition {
public string name;
[ReadOnly]
public Guid id;
public ModifiersData modifiers = new ();
public sealed class PerkDefinition : IPerk {
[field: SerializeField] public string Name { get; set; }
[field: SerializeField] public ModifiersData Modifiers { get; set; }
public Guid Id { get; set; }
}
[Serializable]
@@ -26,7 +32,7 @@ namespace Nox.Game {
}
public sealed class PerkFactory : IPerkFactory {
private readonly Dictionary<Guid, PerkDefinition> perkPool = new ();
private readonly Dictionary<Guid, IPerk> perkPool = new ();
public PerkFactory(PerksRegistry perksRegistry) {
if(!perksRegistry) {
@@ -34,37 +40,41 @@ namespace Nox.Game {
}
var allAvailablePerks = perksRegistry.perksData;
foreach(var perk in allAvailablePerks.perks) {
perkPool.Add(perk.id, perk);
perkPool.Add(perk.Id, perk);
}
}
public IReadOnlyCollection<PerkDefinition> GetAll() {
public IReadOnlyCollection<IPerk> GetAll() {
return perkPool.Values.ToList();
}
public PerkDefinition GetById(Guid perkId) {
public IPerk GetById(Guid perkId) {
perkPool.TryGetValue(perkId, out var perk);
return perk;
}
public IReadOnlyCollection<PerkDefinition> GetPerksFor(CharacterDefinition character) {
public IReadOnlyCollection<IPerk> GetPerksFor(IEntityDefinition character) {
if(character == null) {
return perkPool.Values.ToList();
}
var ownedPerkIds = character.perksData.perks
.Select(p => p.id)
var ownedPerkIds = character.Perks.perks
.Select(p => p.Id)
.ToHashSet();
return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.id)).ToList();
return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.Id)).ToList();
}
public bool TryAddPerk(CharacterDefinition character, Guid perkId) {
public bool TryAddPerk(IEntityDefinition character, Guid perkId) {
if(character == null) {
return false;
}
if(character.perksData.perks.Any(p => p != null && p.id == perkId)) {
if(perkId == Guid.Empty) {
GlobalLogger.LogException("Cannot add a perk with an empty Id!", LogCategory.GameLogic);
}
if(character.Perks.perks.Any(p => p != null && p.Id == perkId)) {
return false;
}
@@ -72,10 +82,11 @@ namespace Nox.Game {
return false;
}
character.perksData.perks.Add(new PerkDefinition {
id = perk.id,
name = perk.name
character.Perks.perks.Add(new PerkDefinition {
Id = perk.Id,
Name = perk.Name
});
return true;
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using UnityEngine;
namespace Nox.Game {

View File

@@ -33,33 +33,16 @@ MonoBehaviour:
value: 1
defaultEntityStats:
stats:
- type: 3
- stat: 1
value: 1
- type: 1
- stat: 2
value: 0
- type: 2
- stat: 3
value: 0
- type: 4
- stat: 4
value: 0
defaultPerksData:
perks: []
defaultModifiersData:
modifiers:
- name: Global Health Modifier per MGT
statType: 1
attributeType: 0
operation: 3
value: 3
- name: Global Stamina modifier per MGT
statType: 2
attributeType: 0
operation: 3
value: 1
- name: Global Stamina Modifier per KNO
statType: 2
attributeType: 0
operation: 3
value: 2
racialBonuses: []
classBonuses:
- class: 1
@@ -69,11 +52,4 @@ MonoBehaviour:
stats: []
startingPerks:
perks: []
permanentModifiers:
modifiers:
- name: Warrior Health per MGT
statType: 1
attributeType: 0
operation: 3
value: 4
maxPartySize: 4