a bit more work on the system to create a default character set and testable environment for that

This commit is contained in:
Sebastian Bularca
2026-03-22 18:11:58 +01:00
parent da74abb039
commit 47d30c0c49
16 changed files with 241 additions and 59 deletions

View File

@@ -126,7 +126,7 @@ MonoBehaviour:
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9e8339bc69fe33641a8fbc5bffd0ebd5
m_Address: EntitiesBaseSettings
m_Address: CharacterBaseSettings
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
@@ -201,6 +201,11 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f37df040ec9864f4dbafe3f5e6dfe4d9
m_Address: DefaultPartySettings
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: fe393ace9b354375a9cb14cdbbc28be4
m_Address: Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile.shader
m_ReadOnly: 0

View File

@@ -99,11 +99,14 @@ namespace Nox.Core {
saveSystem = new SaveSystem(saveSerializer, saveStorage, saveSlotManager, saveSettings);
var adventureData = new AdventureData();
var characterBaseSettings = Addressables.LoadAssetAsync<CharacterBaseSettings>("CharacterBaseSettings").WaitForCompletion();
var perKRegistry = Addressables.LoadAssetAsync<PerkRegistry>("PerkRegistry").WaitForCompletion();
var perKRegistry = Addressables.LoadAssetAsync<PerksRegistry>("PerksRegistry ").WaitForCompletion();
var characterRegistry = Addressables.LoadAssetAsync<CharacterRegistry>("CharacterRegistry").WaitForCompletion();
var defaultPartySettings = Addressables.LoadAssetAsync<DefaultPartySettings>("DefaultPartySettings").WaitForCompletion();
var characterSystems = DefaultCharacterSystemsFactory.Create(characterBaseSettings, perKRegistry, characterRegistry);
var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory);
var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory, defaultPartySettings);
applicationStates = new Dictionary<GameState, IGameState> {
[GameState.BootState] = new SplashGameState(bootstrapReferences, gameDataState),

View File

@@ -2,13 +2,10 @@ using System;
using UnityEngine;
namespace Nox.Game {
[CreateAssetMenu(fileName = "EntitiesBaseSettings", menuName = "Nox/Database/Entities/EntitiesBaseSettings")]
[CreateAssetMenu(fileName = "CharacterBaseSettings", menuName = "Nox/Database/Entities/CharacterBaseSettings")]
public class CharacterBaseSettings: ScriptableObject {
[Header("Character General Defaults")]
public int startAttributesPool = 12;
[Header("Character Creation Defaults")]
public DistributionPointsPerClass distributionPointsPerClass;
public EntityAttributes defaultEntityAttributes;
public EntityStats defaultEntityStats;
public PerksData defaultPerksData;
@@ -25,18 +22,24 @@ namespace Nox.Game {
[Serializable]
public sealed class RacialBonuses {
public CharacterRace race;
public EntityAttributes defaultEntityAttributes;
public EntityStats defaultEntityStats;
public PerksData perksData;
public ModifiersData modifiersData;
public EntityAttributes bonusAttributes;
public EntityStats bonusStats;
public PerksData startingPerks;
public ModifiersData permanentModifiers;
}
[Serializable]
public sealed class ClassBonuses {
public CharacterClass @class;
public EntityAttributes defaultEntityAttributes;
public EntityStats defaultEntityStats;
public PerksData perksData;
public ModifiersData modifiersData;
public EntityAttributes bonusAttributes;
public EntityStats bonusStats;
public PerksData startingPerks;
public ModifiersData permanentModifiers;
}
[Serializable]
public sealed class DistributionPointsPerClass {
public CharacterClass @class;
public int points;
}
}

View File

@@ -2,16 +2,12 @@ using System;
namespace Nox.Game {
public static class DefaultCharacterSystemsFactory {
public static ICharacterSystems Create(CharacterBaseSettings characterBaseSettings, PerkRegistry perkRegistry, CharacterRegistry characterRegistry) {
IPerkFactory perkFactory = new PerkFactory(perkRegistry);
public static ICharacterSystems Create(CharacterBaseSettings characterBaseSettings, PerksRegistry perksRegistry, CharacterRegistry characterRegistry) {
IPerkFactory perkFactory = new PerkFactory(perksRegistry);
ICharacterAttributesFactory attributesFactory = new CharacterAttributesFactory(characterRegistry);
ICharacterStatsFactory statsFactory = new CharacterStatsFactory();
ICharacterFactory characterFactory = new CharacterFactory(attributesFactory, statsFactory, perkFactory);
IPartyFactory partyFactory = new PartyFactory(new PartyFactoryOptions {
minPartySize = 1,
maxPartySize = characterBaseSettings.maxPartySize,
enforceUniqueCharacterIds = true
});
IPartyFactory partyFactory = new PartyFactory(characterBaseSettings);
return new CharacterSystems(perkFactory, characterFactory, partyFactory);
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Nox.Game {
[CreateAssetMenu(fileName = "DefaultPartySettings", menuName = "Nox/Database/Entities/Default Party Settings")]
public class DefaultPartySettings : ScriptableObject {
[Header("This will be default starting set")]
public string startingSetId;
[Header("Party Definition Sets")]
public List<PartyDefinitionSet> partyDefinitionSets;
[Header("Testing Party Definition Sets")]
public CharacterBaseSettings characterBaseSettings;
private void OnValidate() {
if(String.IsNullOrEmpty(startingSetId)) {
Debug.LogError("DefaultPartySettings: startingSetId cannot be null or empty");
return;
}
foreach(var partyDefinitionSet in partyDefinitionSets) {
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(partyDefinitionSets.FirstOrDefault(pds => pds.id == partyDefinitionSet.id && pds.isTestingSet) != null) {
var testingSet = partyDefinitionSets.FirstOrDefault(pds => pds.id == partyDefinitionSet.id && pds.isTestingSet);
ApplyClassAndRacialBonuses(testingSet);
}
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);
}
}
private void ApplyClassAndRacialBonuses(PartyDefinitionSet testingSet) {
var partyDefinition = testingSet.partyDefinition;
foreach(var member in partyDefinition.members) {
var baseSettings = characterBaseSettings.defaultEntityAttributes;
var classAttributes = characterBaseSettings.classBonuses.FirstOrDefault(c => c.@class == member.@class)?.bonusAttributes;
var racialAttributes = characterBaseSettings.racialBonuses.FirstOrDefault(rb => rb.race == member.race)?.bonusAttributes;
if (classAttributes != null && racialAttributes != null) {
member.Attributes += baseSettings + classAttributes + racialAttributes;
}
}
}
}
[Serializable]
public sealed class PartyDefinitionSet {
public string id;
public bool isTestingSet;
public PartyDefinition partyDefinition;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ff702898b3cd4100b9b8230f04711fa9
timeCreated: 1774195937

View File

@@ -40,7 +40,14 @@ namespace Nox.Game {
public int knowledge;
public int perception;
public int Total => might + reflex + knowledge;
public static EntityAttributes operator +(EntityAttributes a, EntityAttributes b) {
return new EntityAttributes {
might = a.might + b.might,
reflex = a.reflex + b.reflex,
knowledge = a.knowledge + b.knowledge,
perception = a.perception + b.perception
};
}
}
[Serializable]
@@ -118,8 +125,8 @@ namespace Nox.Game {
[Serializable]
public sealed class PartyDefinition {
public List<CharacterDefinition> members = new();
public int maxPartySize;
public List<CharacterDefinition> members = new();
[JsonIgnore]
public CharacterDefinition Protagonist => members.FirstOrDefault(m => m.role == CharacterRole.Protagonist);

View File

@@ -4,7 +4,7 @@ namespace Nox.Game {
public class PartyCreatorModel {
private readonly ICharacterFactory characterFactory;
private readonly IPartyFactory partyFactory;
public PartyCreatorModel(ICharacterFactory characterFactory, IPartyFactory partyFactory) {
public PartyCreatorModel(ICharacterFactory characterFactory, IPartyFactory partyFactory, DefaultPartySettings defaultPartySettings) {
this.characterFactory = characterFactory;
this.partyFactory = partyFactory;
}

View File

@@ -7,17 +7,11 @@ namespace Nox.Game {
PartyDefinition Create(CharacterDefinition protagonist, IEnumerable<CharacterDefinition> companions = null);
}
public sealed class PartyFactoryOptions {
public int minPartySize = 1;
public int maxPartySize = 4;
public bool enforceUniqueCharacterIds = true;
}
public sealed class PartyFactory : IPartyFactory {
private readonly PartyFactoryOptions options;
private readonly CharacterBaseSettings characterBaseSettings;
public PartyFactory(PartyFactoryOptions options = null) {
this.options = options ?? new PartyFactoryOptions();
public PartyFactory(CharacterBaseSettings characterBaseSettings) {
this.characterBaseSettings = characterBaseSettings;
}
public PartyDefinition Create(CharacterDefinition protagonist, IEnumerable<CharacterDefinition> companions = null) {
@@ -25,17 +19,17 @@ namespace Nox.Game {
throw new ArgumentNullException(nameof(protagonist));
}
PartyDefinition party = new PartyDefinition {
maxPartySize = options.maxPartySize <= 0 ? int.MaxValue : options.maxPartySize
var party = new PartyDefinition {
maxPartySize = characterBaseSettings.maxPartySize <= 0 ? int.MaxValue : characterBaseSettings.maxPartySize
};
CharacterDefinition protagonistClone = protagonist.Clone();
var protagonistClone = protagonist.Clone();
protagonistClone.role = CharacterRole.Protagonist;
party.members.Add(protagonistClone);
if(companions != null) {
foreach(CharacterDefinition companion in companions.Where(c => c != null)) {
CharacterDefinition companionClone = companion.Clone();
foreach(var companion in companions.Where(c => c != null)) {
var companionClone = companion.Clone();
companionClone.role = CharacterRole.Companion;
party.members.Add(companionClone);
}
@@ -46,10 +40,6 @@ namespace Nox.Game {
}
private void ValidateParty(PartyDefinition party) {
if(party.members.Count < options.minPartySize) {
throw new ArgumentException($"Party size {party.members.Count} is below minimum {options.minPartySize}.");
}
if(party.members.Count > party.maxPartySize) {
throw new ArgumentException($"Party size {party.members.Count} exceeds max {party.maxPartySize}.");
}
@@ -59,7 +49,6 @@ namespace Nox.Game {
throw new ArgumentException($"Party must contain exactly one protagonist, found {protagonistCount}.");
}
if(options.enforceUniqueCharacterIds) {
int uniqueIds = party.members
.Where(m => !string.IsNullOrWhiteSpace(m.ID))
.Select(m => m.ID)
@@ -71,4 +60,3 @@ namespace Nox.Game {
}
}
}
}

View File

@@ -14,11 +14,11 @@ namespace Nox.Game {
public sealed class PerkFactory : IPerkFactory {
private readonly Dictionary<string, PerkDefinition> perkPool = new ();
public PerkFactory(PerkRegistry perkRegistry) {
if(perkRegistry == null) {
throw new ArgumentNullException(nameof(perkRegistry));
public PerkFactory(PerksRegistry perksRegistry) {
if(perksRegistry == null) {
throw new ArgumentNullException(nameof(perksRegistry));
}
var allAvailablePerks = perkRegistry.perksData;
var allAvailablePerks = perksRegistry.perksData;
foreach(var perk in allAvailablePerks.perks) {
perkPool.Add(perk.id, perk);
}

View File

@@ -3,7 +3,7 @@ using UnityEngine;
namespace Nox.Game {
[CreateAssetMenu(fileName = "PerksRegistry", menuName = "Nox/Database/Entities/Perks Registry")]
public class PerkRegistry : ScriptableObject {
public class PerksRegistry : ScriptableObject {
public PerksData perksData;
}
}

View File

@@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1bbecc15c7cd4a7ca90ce17b3d3d75f0, type: 3}
m_Name: EntitiesBaseSettings
m_Name: CharacterBaseSettings
m_EditorClassIdentifier: Assembly-CSharp::Nox.Game.CharacterBaseSettings
startAttributesPool: 12
defaultEntityAttributes:

View File

@@ -0,0 +1,103 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ff702898b3cd4100b9b8230f04711fa9, type: 3}
m_Name: DefaultPartySettings
m_EditorClassIdentifier: Assembly-CSharp::Nox.Game.DefaultPartySettings
partyDefinitionSets:
- id: startingParty1
partyDefinition:
maxPartySize: 4
members:
- id:
displayName:
race: 0
class: 0
role: 0
attributes:
might: 0
reflex: 0
knowledge: 0
perception: 0
stats:
health: 0
stamina: 0
level: 0
experience: 0
perksData:
perks: []
activeModifiers:
modifiers: []
- id:
displayName:
race: 0
class: 0
role: 0
attributes:
might: 0
reflex: 0
knowledge: 0
perception: 0
stats:
health: 0
stamina: 0
level: 0
experience: 0
perksData:
perks: []
activeModifiers:
modifiers: []
- id:
displayName:
race: 0
class: 0
role: 0
attributes:
might: 0
reflex: 0
knowledge: 0
perception: 0
stats:
health: 0
stamina: 0
level: 0
experience: 0
perksData:
perks: []
activeModifiers:
modifiers: []
- id:
displayName:
race: 0
class: 0
role: 0
attributes:
might: 0
reflex: 0
knowledge: 0
perception: 0
stats:
health: 0
stamina: 0
level: 0
experience: 0
perksData:
perks: []
activeModifiers:
modifiers: []
attributesPointDistribution:
might: 0
reflex: 0
knowledge: 0
perception: 0
distributionPointsPerClass:
class: 0
points: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f37df040ec9864f4dbafe3f5e6dfe4d9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant: