forked from Shardstone/trail-into-darkness
factored the character system - not funtional yet
This commit is contained in:
@@ -99,7 +99,7 @@ namespace Nox.Core {
|
|||||||
saveSystem = new SaveSystem(saveSerializer, saveStorage, saveSlotManager, saveSettings);
|
saveSystem = new SaveSystem(saveSerializer, saveStorage, saveSlotManager, saveSettings);
|
||||||
|
|
||||||
var adventuredata = new AdventureData();
|
var adventuredata = new AdventureData();
|
||||||
var characterSystems = DefaultCharacterSystemsFactory.Create(maxPartySize: 8);
|
var characterSystems = DefaultCharacterSystemsFactory.Create(maxPartySize: 4, new PerkRegistry(), new CharacterRegistry());
|
||||||
var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory);
|
var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory);
|
||||||
|
|
||||||
applicationStates = new Dictionary<GameState, IGameState> {
|
applicationStates = new Dictionary<GameState, IGameState> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Nox.Core {
|
|||||||
public Vector3? savedPartyPosition;
|
public Vector3? savedPartyPosition;
|
||||||
public GameState ActiveGameState { get; set; }
|
public GameState ActiveGameState { get; set; }
|
||||||
public PlayMode ActivePlayMode { get; set; }
|
public PlayMode ActivePlayMode { get; set; }
|
||||||
public PartyData ActiveParty { get; set; }
|
public PartyDefinition ActiveParty { get; set; }
|
||||||
|
|
||||||
public PlayMode PreviousPlayMode { get; private set; }
|
public PlayMode PreviousPlayMode { get; private set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,469 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Nox.Game {
|
|
||||||
public enum CharacterRole {
|
|
||||||
Protagonist,
|
|
||||||
Companion
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class CharacterAttributes {
|
|
||||||
public int might;
|
|
||||||
public int reflex;
|
|
||||||
public int knowledge;
|
|
||||||
|
|
||||||
public int Total => might + reflex + knowledge;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class CharacterStats {
|
|
||||||
public int maxHealth;
|
|
||||||
public int maxStamina;
|
|
||||||
public float dodgeStaminaLossMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class PerkDefinition {
|
|
||||||
public string id;
|
|
||||||
public string name;
|
|
||||||
public string mechanicalBonus;
|
|
||||||
public string thematicPenalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class CharacterData {
|
|
||||||
public string id;
|
|
||||||
public string displayName;
|
|
||||||
public CharacterRole role;
|
|
||||||
public int level;
|
|
||||||
public int experience;
|
|
||||||
public CharacterAttributes attributes;
|
|
||||||
public CharacterStats stats;
|
|
||||||
public List<PerkDefinition> perks = new List<PerkDefinition>();
|
|
||||||
|
|
||||||
public CharacterData Clone() {
|
|
||||||
return new CharacterData {
|
|
||||||
id = id,
|
|
||||||
displayName = displayName,
|
|
||||||
role = role,
|
|
||||||
level = level,
|
|
||||||
experience = experience,
|
|
||||||
attributes = new CharacterAttributes {
|
|
||||||
might = attributes?.might ?? 0,
|
|
||||||
reflex = attributes?.reflex ?? 0,
|
|
||||||
knowledge = attributes?.knowledge ?? 0
|
|
||||||
},
|
|
||||||
stats = new CharacterStats {
|
|
||||||
maxHealth = stats?.maxHealth ?? 0,
|
|
||||||
maxStamina = stats?.maxStamina ?? 0,
|
|
||||||
dodgeStaminaLossMultiplier = stats?.dodgeStaminaLossMultiplier ?? 1f
|
|
||||||
},
|
|
||||||
perks = perks.Select(p => new PerkDefinition {
|
|
||||||
id = p.id,
|
|
||||||
name = p.name,
|
|
||||||
mechanicalBonus = p.mechanicalBonus,
|
|
||||||
thematicPenalty = p.thematicPenalty
|
|
||||||
}).ToList()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class CharacterTemplate {
|
|
||||||
public string id;
|
|
||||||
public string displayName;
|
|
||||||
public CharacterAttributes attributes;
|
|
||||||
public int level = 1;
|
|
||||||
public int experience;
|
|
||||||
public List<string> startingPerkIds = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class CustomCharacterCreationRequest {
|
|
||||||
public string id;
|
|
||||||
public string displayName;
|
|
||||||
public int mightPoints;
|
|
||||||
public int reflexPoints;
|
|
||||||
public int knowledgePoints;
|
|
||||||
public List<string> startingPerkIds = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class PartyData {
|
|
||||||
public List<CharacterData> members = new List<CharacterData>();
|
|
||||||
public int maxPartySize;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public CharacterData Protagonist => members.FirstOrDefault(m => m.role == CharacterRole.Protagonist);
|
|
||||||
[JsonIgnore]
|
|
||||||
public IReadOnlyList<CharacterData> Companions => members.Where(m => m.role == CharacterRole.Companion).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ICharacterAttributesFactory {
|
|
||||||
CharacterAttributes Create(int might, int reflex, int knowledge);
|
|
||||||
CharacterAttributes CreateFromPointAllocation(int mightPoints, int reflexPoints, int knowledgePoints);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ICharacterStatsFactory {
|
|
||||||
CharacterStats Create(CharacterAttributes attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPerkFactory {
|
|
||||||
IReadOnlyCollection<PerkDefinition> GetAll();
|
|
||||||
PerkDefinition GetById(string perkId);
|
|
||||||
IReadOnlyCollection<PerkDefinition> GetAvailableFor(CharacterData character);
|
|
||||||
bool TryAddPerk(CharacterData character, string perkId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ICharacterFactory {
|
|
||||||
CharacterData CreateCustomProtagonist(CustomCharacterCreationRequest request);
|
|
||||||
CharacterData CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPartyFactory {
|
|
||||||
PartyData Create(CharacterData protagonist, IEnumerable<CharacterData> companions = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ICharacterSystems {
|
|
||||||
IPerkFactory PerkFactory { get; }
|
|
||||||
ICharacterFactory CharacterFactory { get; }
|
|
||||||
IPartyFactory PartyFactory { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CharacterFactoryOptions {
|
|
||||||
public int baseMight = 1;
|
|
||||||
public int baseReflex = 1;
|
|
||||||
public int baseKnowledge = 1;
|
|
||||||
public int customAttributePointBudget = 10;
|
|
||||||
public int startingLevel = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CharacterStatsFactoryOptions {
|
|
||||||
public int baseHealth = 10;
|
|
||||||
public int baseStamina = 5;
|
|
||||||
public int mightHealthBonus = 3;
|
|
||||||
public int mightStaminaBonus = 1;
|
|
||||||
public int knowledgeStaminaBonus = 2;
|
|
||||||
public float baseDodgeStaminaLossMultiplier = 1f;
|
|
||||||
public float reflexDodgeStaminaLossReduction = 0.03f;
|
|
||||||
public float minDodgeStaminaLossMultiplier = 0.4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class PartyFactoryOptions {
|
|
||||||
public int minPartySize = 1;
|
|
||||||
public int maxPartySize = 4;
|
|
||||||
public bool enforceUniqueCharacterIds = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CharacterSystems : ICharacterSystems {
|
|
||||||
public CharacterSystems(IPerkFactory perkFactory, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
|
|
||||||
PerkFactory = perkFactory;
|
|
||||||
CharacterFactory = characterFactory;
|
|
||||||
PartyFactory = partyFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPerkFactory PerkFactory { get; }
|
|
||||||
public ICharacterFactory CharacterFactory { get; }
|
|
||||||
public IPartyFactory PartyFactory { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DefaultCharacterSystemsFactory {
|
|
||||||
public static ICharacterSystems Create(int maxPartySize = 8) {
|
|
||||||
IPerkFactory perkFactory = new PerkFactory(CreateDefaultPerks());
|
|
||||||
ICharacterAttributesFactory attributesFactory = new CharacterAttributesFactory(new CharacterFactoryOptions {
|
|
||||||
baseMight = 1,
|
|
||||||
baseReflex = 1,
|
|
||||||
baseKnowledge = 1,
|
|
||||||
customAttributePointBudget = 10,
|
|
||||||
startingLevel = 1
|
|
||||||
});
|
|
||||||
ICharacterStatsFactory statsFactory = new CharacterStatsFactory();
|
|
||||||
ICharacterFactory characterFactory = new CharacterFactory(attributesFactory, statsFactory, perkFactory);
|
|
||||||
IPartyFactory partyFactory = new PartyFactory(new PartyFactoryOptions {
|
|
||||||
minPartySize = 1,
|
|
||||||
maxPartySize = maxPartySize,
|
|
||||||
enforceUniqueCharacterIds = true
|
|
||||||
});
|
|
||||||
|
|
||||||
return new CharacterSystems(perkFactory, characterFactory, partyFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<PerkDefinition> CreateDefaultPerks() {
|
|
||||||
return new[] {
|
|
||||||
new PerkDefinition { id = "iron-will", name = "Iron Will", mechanicalBonus = "+1 max health per level", thematicPenalty = "-1 social flexibility in dialogue checks" },
|
|
||||||
new PerkDefinition { id = "steadfast", name = "Steadfast", mechanicalBonus = "-10% stamina loss when bracing", thematicPenalty = "-10% movement speed in retreat events" },
|
|
||||||
new PerkDefinition { id = "nimble-step", name = "Nimble Step", mechanicalBonus = "-15% dodge stamina loss", thematicPenalty = "+10% stamina loss on heavy actions" },
|
|
||||||
new PerkDefinition { id = "lorekeeper", name = "Lorekeeper", mechanicalBonus = "+15% knowledge event success", thematicPenalty = "-10% intimidation success chance" },
|
|
||||||
new PerkDefinition { id = "bulwark", name = "Bulwark", mechanicalBonus = "+2 base defense checks", thematicPenalty = "-1 reflex in stealth checks" },
|
|
||||||
new PerkDefinition { id = "pathfinder", name = "Pathfinder", mechanicalBonus = "+15% scouting event success", thematicPenalty = "-1 max health during ambush events" }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CharacterAttributesFactory : ICharacterAttributesFactory {
|
|
||||||
private readonly CharacterFactoryOptions options;
|
|
||||||
|
|
||||||
public CharacterAttributesFactory(CharacterFactoryOptions options = null) {
|
|
||||||
this.options = options ?? new CharacterFactoryOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharacterAttributes Create(int might, int reflex, int knowledge) {
|
|
||||||
if(might < 0 || reflex < 0 || knowledge < 0) {
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(might), "attributes cannot be negative.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CharacterAttributes {
|
|
||||||
might = might,
|
|
||||||
reflex = reflex,
|
|
||||||
knowledge = knowledge
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharacterAttributes CreateFromPointAllocation(int mightPoints, int reflexPoints, int knowledgePoints) {
|
|
||||||
if(mightPoints < 0 || reflexPoints < 0 || knowledgePoints < 0) {
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(mightPoints), "Point allocation cannot be negative.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int allocated = mightPoints + reflexPoints + knowledgePoints;
|
|
||||||
if(allocated > options.customAttributePointBudget) {
|
|
||||||
throw new ArgumentException($"Allocated {allocated} points but budget is {options.customAttributePointBudget}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CharacterAttributes {
|
|
||||||
might = options.baseMight + mightPoints,
|
|
||||||
reflex = options.baseReflex + reflexPoints,
|
|
||||||
knowledge = options.baseKnowledge + knowledgePoints
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CharacterStatsFactory : ICharacterStatsFactory {
|
|
||||||
private readonly CharacterStatsFactoryOptions options;
|
|
||||||
|
|
||||||
public CharacterStatsFactory(CharacterStatsFactoryOptions options = null) {
|
|
||||||
this.options = options ?? new CharacterStatsFactoryOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharacterStats Create(CharacterAttributes attributes) {
|
|
||||||
if(attributes == null) {
|
|
||||||
throw new ArgumentNullException(nameof(attributes));
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxHealth = options.baseHealth + attributes.might * options.mightHealthBonus;
|
|
||||||
int maxStamina = options.baseStamina +
|
|
||||||
attributes.might * options.mightStaminaBonus +
|
|
||||||
attributes.knowledge * options.knowledgeStaminaBonus;
|
|
||||||
float dodgeMultiplier = options.baseDodgeStaminaLossMultiplier -
|
|
||||||
attributes.reflex * options.reflexDodgeStaminaLossReduction;
|
|
||||||
dodgeMultiplier = Math.Max(options.minDodgeStaminaLossMultiplier, dodgeMultiplier);
|
|
||||||
|
|
||||||
return new CharacterStats {
|
|
||||||
maxHealth = maxHealth,
|
|
||||||
maxStamina = maxStamina,
|
|
||||||
dodgeStaminaLossMultiplier = dodgeMultiplier
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class PerkFactory : IPerkFactory {
|
|
||||||
private readonly Dictionary<string, PerkDefinition> perkPool;
|
|
||||||
|
|
||||||
public PerkFactory(IEnumerable<PerkDefinition> perkPool) {
|
|
||||||
if(perkPool == null) {
|
|
||||||
throw new ArgumentNullException(nameof(perkPool));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.perkPool = perkPool
|
|
||||||
.Where(p => p != null && !string.IsNullOrWhiteSpace(p.id))
|
|
||||||
.GroupBy(p => p.id)
|
|
||||||
.ToDictionary(g => g.Key, g => g.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyCollection<PerkDefinition> GetAll() {
|
|
||||||
return perkPool.Values.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PerkDefinition GetById(string perkId) {
|
|
||||||
if(string.IsNullOrWhiteSpace(perkId)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
perkPool.TryGetValue(perkId, out PerkDefinition perk);
|
|
||||||
return perk;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyCollection<PerkDefinition> GetAvailableFor(CharacterData character) {
|
|
||||||
if(character == null) {
|
|
||||||
return perkPool.Values.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
HashSet<string> ownedPerkIds = character.perks
|
|
||||||
.Where(p => p != null && !string.IsNullOrWhiteSpace(p.id))
|
|
||||||
.Select(p => p.id)
|
|
||||||
.ToHashSet();
|
|
||||||
|
|
||||||
return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.id)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryAddPerk(CharacterData character, string perkId) {
|
|
||||||
if(character == null || string.IsNullOrWhiteSpace(perkId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(character.perks.Any(p => p != null && p.id == perkId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!perkPool.TryGetValue(perkId, out PerkDefinition perk)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
character.perks.Add(new PerkDefinition {
|
|
||||||
id = perk.id,
|
|
||||||
name = perk.name,
|
|
||||||
mechanicalBonus = perk.mechanicalBonus,
|
|
||||||
thematicPenalty = perk.thematicPenalty
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CharacterFactory : ICharacterFactory {
|
|
||||||
private readonly CharacterFactoryOptions options;
|
|
||||||
private readonly ICharacterAttributesFactory attributesFactory;
|
|
||||||
private readonly ICharacterStatsFactory statsFactory;
|
|
||||||
private readonly IPerkFactory perkFactory;
|
|
||||||
|
|
||||||
public CharacterFactory(
|
|
||||||
ICharacterAttributesFactory attributesFactory,
|
|
||||||
ICharacterStatsFactory statsFactory,
|
|
||||||
IPerkFactory perkFactory,
|
|
||||||
CharacterFactoryOptions options = null) {
|
|
||||||
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.options = options ?? new CharacterFactoryOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharacterData CreateCustomProtagonist(CustomCharacterCreationRequest request) {
|
|
||||||
if(request == null) {
|
|
||||||
throw new ArgumentNullException(nameof(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
CharacterAttributes attributes = attributesFactory.CreateFromPointAllocation(
|
|
||||||
request.mightPoints,
|
|
||||||
request.reflexPoints,
|
|
||||||
request.knowledgePoints);
|
|
||||||
|
|
||||||
CharacterData character = new CharacterData {
|
|
||||||
id = string.IsNullOrWhiteSpace(request.id) ? Guid.NewGuid().ToString("N") : request.id,
|
|
||||||
displayName = request.displayName,
|
|
||||||
role = CharacterRole.Protagonist,
|
|
||||||
level = options.startingLevel,
|
|
||||||
experience = 0,
|
|
||||||
attributes = attributes,
|
|
||||||
stats = statsFactory.Create(attributes)
|
|
||||||
};
|
|
||||||
|
|
||||||
AddStartingPerks(character, request.startingPerkIds);
|
|
||||||
return character;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharacterData CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion) {
|
|
||||||
if(template == null) {
|
|
||||||
throw new ArgumentNullException(nameof(template));
|
|
||||||
}
|
|
||||||
|
|
||||||
CharacterAttributes sourceAttributes = template.attributes ?? attributesFactory.Create(0, 0, 0);
|
|
||||||
CharacterAttributes attributes = attributesFactory.Create(
|
|
||||||
sourceAttributes.might,
|
|
||||||
sourceAttributes.reflex,
|
|
||||||
sourceAttributes.knowledge);
|
|
||||||
|
|
||||||
CharacterData character = new CharacterData {
|
|
||||||
id = string.IsNullOrWhiteSpace(template.id) ? Guid.NewGuid().ToString("N") : template.id,
|
|
||||||
displayName = template.displayName,
|
|
||||||
role = role,
|
|
||||||
level = template.level <= 0 ? options.startingLevel : template.level,
|
|
||||||
experience = template.experience,
|
|
||||||
attributes = attributes,
|
|
||||||
stats = statsFactory.Create(attributes)
|
|
||||||
};
|
|
||||||
|
|
||||||
AddStartingPerks(character, template.startingPerkIds);
|
|
||||||
return character;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddStartingPerks(CharacterData character, IEnumerable<string> perkIds) {
|
|
||||||
if(perkIds == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(string perkId in perkIds.Distinct()) {
|
|
||||||
perkFactory.TryAddPerk(character, perkId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class PartyFactory : IPartyFactory {
|
|
||||||
private readonly PartyFactoryOptions options;
|
|
||||||
|
|
||||||
public PartyFactory(PartyFactoryOptions options = null) {
|
|
||||||
this.options = options ?? new PartyFactoryOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartyData Create(CharacterData protagonist, IEnumerable<CharacterData> companions = null) {
|
|
||||||
if(protagonist == null) {
|
|
||||||
throw new ArgumentNullException(nameof(protagonist));
|
|
||||||
}
|
|
||||||
|
|
||||||
PartyData party = new PartyData {
|
|
||||||
maxPartySize = options.maxPartySize <= 0 ? int.MaxValue : options.maxPartySize
|
|
||||||
};
|
|
||||||
|
|
||||||
CharacterData protagonistClone = protagonist.Clone();
|
|
||||||
protagonistClone.role = CharacterRole.Protagonist;
|
|
||||||
party.members.Add(protagonistClone);
|
|
||||||
|
|
||||||
if(companions != null) {
|
|
||||||
foreach(CharacterData companion in companions.Where(c => c != null)) {
|
|
||||||
CharacterData companionClone = companion.Clone();
|
|
||||||
companionClone.role = CharacterRole.Companion;
|
|
||||||
party.members.Add(companionClone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidateParty(party);
|
|
||||||
return party;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateParty(PartyData 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}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int protagonistCount = party.members.Count(m => m.role == CharacterRole.Protagonist);
|
|
||||||
if(protagonistCount != 1) {
|
|
||||||
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)
|
|
||||||
.Distinct()
|
|
||||||
.Count();
|
|
||||||
if(uniqueIds != party.members.Count) {
|
|
||||||
throw new ArgumentException("Party contains duplicate or missing character ids.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: ec5c743399d71cd41b2db4f9a7e5dec1
|
|
||||||
23
Assets/Code/GameState/Entities/CharacterAttributesFactory.cs
Normal file
23
Assets/Code/GameState/Entities/CharacterAttributesFactory.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
public interface ICharacterAttributesFactory {
|
||||||
|
EntityAttributes Create(EntityAttributes attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterAttributesFactory : ICharacterAttributesFactory {
|
||||||
|
private readonly CharacterRegistry characterRegistry;
|
||||||
|
|
||||||
|
public CharacterAttributesFactory(CharacterRegistry characterRegistry) {
|
||||||
|
this.characterRegistry = characterRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityAttributes Create(EntityAttributes attributes) {
|
||||||
|
if(attributes.might <= 0 || attributes.reflex <= 0 || attributes.knowledge <= 0 || attributes.perception <= 0) {
|
||||||
|
throw new ArgumentOutOfRangeException( "attributes cannot be zero or negative.", new ArgumentException() );
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1c5d19a12e554fd981cec483ccfcff68
|
||||||
|
timeCreated: 1774183852
|
||||||
107
Assets/Code/GameState/Entities/CharacterFactory.cs
Normal file
107
Assets/Code/GameState/Entities/CharacterFactory.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
public interface ICharacterFactory {
|
||||||
|
CharacterDefinition CreateCustomProtagonist(CustomCharacterCreationRequest request);
|
||||||
|
CharacterDefinition CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 ();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class CustomCharacterCreationRequest {
|
||||||
|
public string id;
|
||||||
|
public string displayName;
|
||||||
|
public CharacterRace characterRace;
|
||||||
|
public CharacterClass characterClass;
|
||||||
|
public EntityStats stats;
|
||||||
|
public EntityAttributes attributes;
|
||||||
|
public PerksData perksData = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterFactory : ICharacterFactory {
|
||||||
|
private readonly ICharacterAttributesFactory attributesFactory;
|
||||||
|
private readonly ICharacterStatsFactory statsFactory;
|
||||||
|
private readonly IPerkFactory perkFactory;
|
||||||
|
|
||||||
|
public CharacterFactory(
|
||||||
|
ICharacterAttributesFactory attributesFactory,
|
||||||
|
ICharacterStatsFactory statsFactory,
|
||||||
|
IPerkFactory perkFactory) {
|
||||||
|
this.attributesFactory = attributesFactory ?? throw new ArgumentNullException(nameof(attributesFactory));
|
||||||
|
this.statsFactory = statsFactory ?? throw new ArgumentNullException(nameof(statsFactory));
|
||||||
|
this.perkFactory = perkFactory ?? throw new ArgumentNullException(nameof(perkFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharacterDefinition CreateCustomProtagonist(CustomCharacterCreationRequest request) {
|
||||||
|
if(request == null) {
|
||||||
|
throw new ArgumentNullException(nameof(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = attributesFactory.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,
|
||||||
|
Attributes = attributes,
|
||||||
|
Stats = {
|
||||||
|
level = statsFactory.Create(attributes).level,
|
||||||
|
health = statsFactory.Create(attributes).health,
|
||||||
|
stamina = statsFactory.Create(attributes).stamina,
|
||||||
|
experience = statsFactory.Create(attributes).experience
|
||||||
|
},
|
||||||
|
perksData = new PerksData(),
|
||||||
|
activeModifiers = new ModifiersData()
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStartingPerks(character, request.perksData);
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharacterDefinition CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion) {
|
||||||
|
if(template == null) {
|
||||||
|
throw new ArgumentNullException(nameof(template));
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = attributesFactory.Create(template.attributes);
|
||||||
|
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStartingPerks(character, template.perksData);
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddStartingPerks(CharacterDefinition character, PerksData perkData) {
|
||||||
|
if(perkData?.perks == null || perkData.perks.Count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var perkId in perkData.perks.Distinct()) {
|
||||||
|
perkFactory.TryAddPerk(character, perkId.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
|
||||||
37
Assets/Code/GameState/Entities/CharacterStatsFactory.cs
Normal file
37
Assets/Code/GameState/Entities/CharacterStatsFactory.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
public interface ICharacterStatsFactory {
|
||||||
|
EntityStats Create(EntityAttributes attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterStatsFactoryOptions {
|
||||||
|
public EntityStats entityStats = new EntityStats() {
|
||||||
|
health = 10,
|
||||||
|
stamina = 10,
|
||||||
|
level = 1,
|
||||||
|
experience = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterStatsFactory : ICharacterStatsFactory {
|
||||||
|
private readonly CharacterStatsFactoryOptions options;
|
||||||
|
|
||||||
|
public CharacterStatsFactory(CharacterStatsFactoryOptions options = null) {
|
||||||
|
this.options = options ?? new CharacterStatsFactoryOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityStats Create(EntityAttributes attributes) {
|
||||||
|
if(attributes == null) {
|
||||||
|
throw new ArgumentNullException(nameof(attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
// int maxHealth = options.baseHealth + attributes.might * options.mightHealthBonus;
|
||||||
|
// int maxStamina = options.baseStamina +
|
||||||
|
// attributes.might * options.mightStaminaBonus +
|
||||||
|
// attributes.knowledge * options.knowledgeStaminaBonus;
|
||||||
|
|
||||||
|
return new EntityStats { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 75a241c095744a518eb8d1c1b14d24e4
|
||||||
|
timeCreated: 1774183822
|
||||||
20
Assets/Code/GameState/Entities/CharacterSystems.cs
Normal file
20
Assets/Code/GameState/Entities/CharacterSystems.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Nox.Game {
|
||||||
|
|
||||||
|
public interface ICharacterSystems {
|
||||||
|
IPerkFactory PerkFactory { get; }
|
||||||
|
ICharacterFactory CharacterFactory { get; }
|
||||||
|
IPartyFactory PartyFactory { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CharacterSystems : ICharacterSystems {
|
||||||
|
public CharacterSystems(IPerkFactory perkFactory, ICharacterFactory characterFactory, IPartyFactory partyFactory) {
|
||||||
|
PerkFactory = perkFactory;
|
||||||
|
CharacterFactory = characterFactory;
|
||||||
|
PartyFactory = partyFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPerkFactory PerkFactory { 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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
public static class DefaultCharacterSystemsFactory {
|
||||||
|
public static ICharacterSystems Create(int maxPartySize, PerkRegistry perkRegistry, CharacterRegistry characterRegistry) {
|
||||||
|
IPerkFactory perkFactory = new PerkFactory(perkRegistry);
|
||||||
|
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 = maxPartySize,
|
||||||
|
enforceUniqueCharacterIds = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return new CharacterSystems(perkFactory, characterFactory, partyFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 60db4014b69d403db42ee766204eb2d7
|
||||||
|
timeCreated: 1774183978
|
||||||
18
Assets/Code/GameState/Entities/EntitiesBaseSettings.cs
Normal file
18
Assets/Code/GameState/Entities/EntitiesBaseSettings.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
[CreateAssetMenu(fileName = "EntitiesBaseSettings", menuName = "Nox/Database/Entities/EntitiesBaseSettings")]
|
||||||
|
public class EntitiesBaseSettings: ScriptableObject {
|
||||||
|
[Header("Character Creation")]
|
||||||
|
public int startAttributesPool = 12;
|
||||||
|
public int startLevel = 1;
|
||||||
|
|
||||||
|
public RacialBonus [] racialBonuses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RacialBonus {
|
||||||
|
public CharacterRace race;
|
||||||
|
public CharacterClass characterClass;
|
||||||
|
public int bonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1bbecc15c7cd4a7ca90ce17b3d3d75f0
|
||||||
|
timeCreated: 1774184829
|
||||||
130
Assets/Code/GameState/Entities/EntitiesDefinitions.cs
Normal file
130
Assets/Code/GameState/Entities/EntitiesDefinitions.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
public interface IEntityDefinition {
|
||||||
|
string ID { get; }
|
||||||
|
string DisplayName { get; }
|
||||||
|
EntityAttributes Attributes { get; }
|
||||||
|
EntityStats Stats { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CharacterRole {
|
||||||
|
None,
|
||||||
|
Protagonist,
|
||||||
|
Companion
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CharacterClass {
|
||||||
|
None,
|
||||||
|
Warrior,
|
||||||
|
Rogue,
|
||||||
|
Mage,
|
||||||
|
Herald
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CharacterRace {
|
||||||
|
None,
|
||||||
|
Human,
|
||||||
|
DarkElf,
|
||||||
|
Tunneler
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class EntityAttributes {
|
||||||
|
public int might;
|
||||||
|
public int reflex;
|
||||||
|
public int knowledge;
|
||||||
|
public int perception;
|
||||||
|
|
||||||
|
public int Total => might + reflex + knowledge;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class EntityStats {
|
||||||
|
public int health;
|
||||||
|
public int stamina;
|
||||||
|
public int level;
|
||||||
|
public int experience;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class PerkDefinition {
|
||||||
|
public string id;
|
||||||
|
public string name;
|
||||||
|
public ModifiersData modifiers = new ();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class PerksData {
|
||||||
|
public List<PerkDefinition> perks = new ();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class CharacterDefinition : IEntityDefinition {
|
||||||
|
[SerializeField] private string id;
|
||||||
|
[SerializeField] private string displayName;
|
||||||
|
public CharacterRace race;
|
||||||
|
public CharacterClass @class;
|
||||||
|
public CharacterRole role;
|
||||||
|
[SerializeField] EntityAttributes attributes;
|
||||||
|
[SerializeField] EntityStats stats;
|
||||||
|
public PerksData perksData = new();
|
||||||
|
public ModifiersData activeModifiers = new();
|
||||||
|
|
||||||
|
public CharacterDefinition Clone() {
|
||||||
|
return new CharacterDefinition {
|
||||||
|
id = id,
|
||||||
|
displayName = displayName,
|
||||||
|
role = role,
|
||||||
|
race = race,
|
||||||
|
@class = @class,
|
||||||
|
attributes = new EntityAttributes {
|
||||||
|
might = attributes?.might ?? 1,
|
||||||
|
reflex = attributes?.reflex ?? 1,
|
||||||
|
knowledge = attributes?.knowledge ?? 1
|
||||||
|
},
|
||||||
|
stats = new EntityStats {
|
||||||
|
health = stats?.health ?? 1,
|
||||||
|
stamina = stats?.stamina ?? 1
|
||||||
|
},
|
||||||
|
perksData = new PerksData(),
|
||||||
|
activeModifiers = new ModifiersData()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
public sealed class PartyDefinition {
|
||||||
|
public List<CharacterDefinition> members = new();
|
||||||
|
public int maxPartySize;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public CharacterDefinition Protagonist => members.FirstOrDefault(m => m.role == CharacterRole.Protagonist);
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IReadOnlyList<CharacterDefinition> Companions => members.Where(m => m.role == CharacterRole.Companion).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace v {
|
|
||||||
public interface IEntity {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
Assets/Code/GameState/Entities/ModifiersHandler.cs
Normal file
31
Assets/Code/GameState/Entities/ModifiersHandler.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
|
||||||
|
public enum ModifierType {
|
||||||
|
None,
|
||||||
|
Flat,
|
||||||
|
Addition,
|
||||||
|
Multiplication,
|
||||||
|
Percentage
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class Modifier {
|
||||||
|
public string id;
|
||||||
|
public ModifierType type;
|
||||||
|
public float value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class ModifiersData {
|
||||||
|
public Modifier[] modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModifiersHandler {
|
||||||
|
private readonly ModifiersData modifiersData;
|
||||||
|
public ModifiersHandler(ModifiersData modifiersData) {
|
||||||
|
this.modifiersData = modifiersData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Code/GameState/Entities/ModifiersHandler.cs.meta
Normal file
3
Assets/Code/GameState/Entities/ModifiersHandler.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 130a7ebdbcf2417392d8fc3f9ae76eb2
|
||||||
|
timeCreated: 1774176049
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
public class PartyCreatorModel {
|
public class PartyCreatorModel {
|
||||||
private readonly ICharacterFactory characterFactory;
|
private readonly ICharacterFactory characterFactory;
|
||||||
@@ -6,50 +8,60 @@ namespace Nox.Game {
|
|||||||
this.characterFactory = characterFactory;
|
this.characterFactory = characterFactory;
|
||||||
this.partyFactory = partyFactory;
|
this.partyFactory = partyFactory;
|
||||||
}
|
}
|
||||||
public PartyData CreatePartyForNewRun(int companionCount) {
|
public PartyDefinition CreatePartyForNewRun(int companionCount) {
|
||||||
var protagonist = characterFactory.CreateCustomProtagonist(new CustomCharacterCreationRequest {
|
var protagonist = characterFactory.CreateCustomProtagonist(new CustomCharacterCreationRequest {
|
||||||
id = "protagonist",
|
id = "protagonist",
|
||||||
displayName = "The Warden",
|
displayName = "The Warden",
|
||||||
mightPoints = 4,
|
attributes = new EntityAttributes(),
|
||||||
reflexPoints = 3,
|
perksData = new PerksData(){
|
||||||
knowledgePoints = 3,
|
perks = new List<PerkDefinition>()
|
||||||
startingPerkIds = new System.Collections.Generic.List<string> { "iron-will" }
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
CharacterTemplate[] companionTemplates = {
|
CharacterTemplate[] companionTemplates = {
|
||||||
new() {
|
new() {
|
||||||
id = "companion-bruiser",
|
id = "companion-bruiser",
|
||||||
displayName = "Rook",
|
displayName = "Rook",
|
||||||
attributes = new CharacterAttributes { might = 5, reflex = 2, knowledge = 1 },
|
attributes = new EntityAttributes { might = 5, reflex = 2, knowledge = 1 },
|
||||||
startingPerkIds = new System.Collections.Generic.List<string> { "steadfast" }
|
perksData = new PerksData(){
|
||||||
|
perks = new List<PerkDefinition>()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new() {
|
new() {
|
||||||
id = "companion-scout",
|
id = "companion-scout",
|
||||||
displayName = "Sable",
|
displayName = "Sable",
|
||||||
attributes = new CharacterAttributes { might = 2, reflex = 5, knowledge = 1 },
|
attributes = new EntityAttributes { might = 2, reflex = 5, knowledge = 1 },
|
||||||
startingPerkIds = new System.Collections.Generic.List<string> { "nimble-step" }
|
perksData = new PerksData(){
|
||||||
|
perks = new List<PerkDefinition>()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new() {
|
new() {
|
||||||
id = "companion-scholar",
|
id = "companion-scholar",
|
||||||
displayName = "Quill",
|
displayName = "Quill",
|
||||||
attributes = new CharacterAttributes { might = 1, reflex = 2, knowledge = 5 },
|
attributes = new EntityAttributes { might = 1, reflex = 2, knowledge = 5 },
|
||||||
startingPerkIds = new System.Collections.Generic.List<string> { "lorekeeper" }
|
perksData = new PerksData(){
|
||||||
|
perks = new List<PerkDefinition>()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new() {
|
new() {
|
||||||
id = "companion-vanguard",
|
id = "companion-vanguard",
|
||||||
displayName = "Brant",
|
displayName = "Brant",
|
||||||
attributes = new CharacterAttributes { might = 4, reflex = 3, knowledge = 2 },
|
attributes = new EntityAttributes { might = 4, reflex = 3, knowledge = 2 },
|
||||||
startingPerkIds = new System.Collections.Generic.List<string> { "bulwark" }
|
perksData = new PerksData(){
|
||||||
|
perks = new List<PerkDefinition>()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new() {
|
new() {
|
||||||
id = "companion-tracker",
|
id = "companion-tracker",
|
||||||
displayName = "Mira",
|
displayName = "Mira",
|
||||||
attributes = new CharacterAttributes { might = 2, reflex = 4, knowledge = 3 },
|
attributes = new EntityAttributes { might = 2, reflex = 4, knowledge = 3 },
|
||||||
startingPerkIds = new System.Collections.Generic.List<string> { "pathfinder" }
|
perksData = new PerksData(){
|
||||||
|
perks = new List<PerkDefinition>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var companions = new System.Collections.Generic.List<CharacterData>();
|
var companions = new List<CharacterDefinition>();
|
||||||
for(var i = 0; i < companionCount && i < companionTemplates.Length; i++) {
|
for(var i = 0; i < companionCount && i < companionTemplates.Length; i++) {
|
||||||
companions.Add(characterFactory.CreateFromTemplate(companionTemplates[i], CharacterRole.Companion));
|
companions.Add(characterFactory.CreateFromTemplate(companionTemplates[i], CharacterRole.Companion));
|
||||||
}
|
}
|
||||||
|
|||||||
74
Assets/Code/GameState/Entities/PartyFactory.cs
Normal file
74
Assets/Code/GameState/Entities/PartyFactory.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
public interface IPartyFactory {
|
||||||
|
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;
|
||||||
|
|
||||||
|
public PartyFactory(PartyFactoryOptions options = null) {
|
||||||
|
this.options = options ?? new PartyFactoryOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartyDefinition Create(CharacterDefinition protagonist, IEnumerable<CharacterDefinition> companions = null) {
|
||||||
|
if(protagonist == null) {
|
||||||
|
throw new ArgumentNullException(nameof(protagonist));
|
||||||
|
}
|
||||||
|
|
||||||
|
PartyDefinition party = new PartyDefinition {
|
||||||
|
maxPartySize = options.maxPartySize <= 0 ? int.MaxValue : options.maxPartySize
|
||||||
|
};
|
||||||
|
|
||||||
|
CharacterDefinition 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();
|
||||||
|
companionClone.role = CharacterRole.Companion;
|
||||||
|
party.members.Add(companionClone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateParty(party);
|
||||||
|
return party;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int protagonistCount = party.members.Count(m => m.role == CharacterRole.Protagonist);
|
||||||
|
if(protagonistCount != 1) {
|
||||||
|
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)
|
||||||
|
.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
|
||||||
73
Assets/Code/GameState/Entities/PerkFactory.cs
Normal file
73
Assets/Code/GameState/Entities/PerkFactory.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
|
||||||
|
public interface IPerkFactory {
|
||||||
|
IReadOnlyCollection<PerkDefinition> GetAll();
|
||||||
|
PerkDefinition GetById(string perkId);
|
||||||
|
IReadOnlyCollection<PerkDefinition> GetPerksFor(CharacterDefinition character);
|
||||||
|
bool TryAddPerk(CharacterDefinition character, string perkId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PerkFactory : IPerkFactory {
|
||||||
|
private readonly Dictionary<string, PerkDefinition> perkPool = new ();
|
||||||
|
|
||||||
|
public PerkFactory(PerkRegistry perkRegistry) {
|
||||||
|
if(perkRegistry == null) {
|
||||||
|
throw new ArgumentNullException(nameof(perkRegistry));
|
||||||
|
}
|
||||||
|
var allAvailablePerks = perkRegistry.perksData;
|
||||||
|
foreach(var perk in allAvailablePerks.perks) {
|
||||||
|
perkPool.Add(perk.id, perk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<PerkDefinition> GetAll() {
|
||||||
|
return perkPool.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerkDefinition GetById(string perkId) {
|
||||||
|
if(string.IsNullOrWhiteSpace(perkId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
perkPool.TryGetValue(perkId, out var perk);
|
||||||
|
return perk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<PerkDefinition> GetPerksFor(CharacterDefinition character) {
|
||||||
|
if(character == null) {
|
||||||
|
return perkPool.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var ownedPerkIds = character.perksData.perks
|
||||||
|
.Where(p => p != null && !string.IsNullOrWhiteSpace(p.id))
|
||||||
|
.Select(p => p.id)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.id)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAddPerk(CharacterDefinition character, string perkId) {
|
||||||
|
if(character == null || string.IsNullOrWhiteSpace(perkId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(character.perksData.perks.Any(p => p != null && p.id == perkId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!perkPool.TryGetValue(perkId, out PerkDefinition perk)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
character.perksData.perks.Add(new PerkDefinition {
|
||||||
|
id = perk.id,
|
||||||
|
name = perk.name
|
||||||
|
});
|
||||||
|
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
|
||||||
9
Assets/Code/GameState/Entities/PerkRegistry.cs
Normal file
9
Assets/Code/GameState/Entities/PerkRegistry.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Nox.Game {
|
||||||
|
[CreateAssetMenu(fileName = "PerksRegistry", menuName = "Nox/Database/Entities/Perks Registry")]
|
||||||
|
public class PerkRegistry : ScriptableObject {
|
||||||
|
public PerksData perksData;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Code/GameState/Entities/PerkRegistry.cs.meta
Normal file
3
Assets/Code/GameState/Entities/PerkRegistry.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0adb42b13ce44d8792247246a49e9c3f
|
||||||
|
timeCreated: 1774179294
|
||||||
@@ -33,7 +33,7 @@ namespace Nox.Game {
|
|||||||
|
|
||||||
gameDataState.activeSessionId = latestSession.sessionId;
|
gameDataState.activeSessionId = latestSession.sessionId;
|
||||||
gameDataState.savedPartyPosition = saveData.partyPosition.ToVector3();
|
gameDataState.savedPartyPosition = saveData.partyPosition.ToVector3();
|
||||||
gameDataState.ActiveParty = saveData.partyData;
|
gameDataState.ActiveParty = saveData.partyDefinition;
|
||||||
adventureData = saveData.adventureData;
|
adventureData = saveData.adventureData;
|
||||||
|
|
||||||
return saveData;
|
return saveData;
|
||||||
@@ -53,7 +53,7 @@ namespace Nox.Game {
|
|||||||
public AdventureData adventureData;
|
public AdventureData adventureData;
|
||||||
|
|
||||||
// Party
|
// Party
|
||||||
public PartyData partyData;
|
public PartyDefinition partyDefinition;
|
||||||
public SerializableVector3 partyPosition;
|
public SerializableVector3 partyPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Nox.Game {
|
|||||||
private readonly PlayModeSettings bootstrapSettings;
|
private readonly PlayModeSettings bootstrapSettings;
|
||||||
private readonly GameDataState gameDataState;
|
private readonly GameDataState gameDataState;
|
||||||
private readonly ISaveSystem saveSystem;
|
private readonly ISaveSystem saveSystem;
|
||||||
private PartyData partyData;
|
private PartyDefinition partyDefinition;
|
||||||
private AdventureData adventureData;
|
private AdventureData adventureData;
|
||||||
private AdventureModePrefabs scenePrefabs;
|
private AdventureModePrefabs scenePrefabs;
|
||||||
private ICameraController cameraController;
|
private ICameraController cameraController;
|
||||||
@@ -40,14 +40,14 @@ namespace Nox.Game {
|
|||||||
|
|
||||||
public AdventurePlayMode(
|
public AdventurePlayMode(
|
||||||
PlatformSettings platformSettings,
|
PlatformSettings platformSettings,
|
||||||
PartyData partyData,
|
PartyDefinition partyDefinition,
|
||||||
PlayModeSettings bootstrapSettings,
|
PlayModeSettings bootstrapSettings,
|
||||||
GameDataState gameDataState,
|
GameDataState gameDataState,
|
||||||
ISaveSystem saveSystem,
|
ISaveSystem saveSystem,
|
||||||
AdventureSettings adventureSettings,
|
AdventureSettings adventureSettings,
|
||||||
AdventureData adventureData) {
|
AdventureData adventureData) {
|
||||||
this.platformSettings = platformSettings;
|
this.platformSettings = platformSettings;
|
||||||
this.partyData = partyData;
|
this.partyDefinition = partyDefinition;
|
||||||
this.bootstrapSettings = bootstrapSettings;
|
this.bootstrapSettings = bootstrapSettings;
|
||||||
this.gameDataState = gameDataState;
|
this.gameDataState = gameDataState;
|
||||||
this.saveSystem = saveSystem;
|
this.saveSystem = saveSystem;
|
||||||
@@ -73,7 +73,7 @@ namespace Nox.Game {
|
|||||||
inputActions.Player.Enable();
|
inputActions.Player.Enable();
|
||||||
inputActions.UI.PauseMenu.Enable();
|
inputActions.UI.PauseMenu.Enable();
|
||||||
Debug.Log("Entering Adventure Play Mode");
|
Debug.Log("Entering Adventure Play Mode");
|
||||||
if(partyData == null) {
|
if(partyDefinition == null) {
|
||||||
var sessions = saveSystem.GetAllSessions().OrderByDescending(s => s.lastSaveDateUtc).ToList();
|
var sessions = saveSystem.GetAllSessions().OrderByDescending(s => s.lastSaveDateUtc).ToList();
|
||||||
if(sessions.Count == 0) {
|
if(sessions.Count == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -156,7 +156,7 @@ namespace Nox.Game {
|
|||||||
public NoxSavedDataSet CaptureNoxSaveData() {
|
public NoxSavedDataSet CaptureNoxSaveData() {
|
||||||
return new NoxSavedDataSet {
|
return new NoxSavedDataSet {
|
||||||
activePlayMode = PlayMode.Adventure,
|
activePlayMode = PlayMode.Adventure,
|
||||||
partyData = partyData,
|
partyDefinition = partyDefinition,
|
||||||
partyPosition = partyRef ? SerializableVector3.FromVector3(partyRef.transform.position) : SerializableVector3.Zero
|
partyPosition = partyRef ? SerializableVector3.FromVector3(partyRef.transform.position) : SerializableVector3.Zero
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ using UnityEngine;
|
|||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
public class CombatPlayMode : IPlayMode {
|
public class CombatPlayMode : IPlayMode {
|
||||||
private readonly PlatformSettings platformSettings;
|
private readonly PlatformSettings platformSettings;
|
||||||
private readonly PartyData partyData;
|
private readonly PartyDefinition partyDefinition;
|
||||||
|
|
||||||
public CombatPlayMode(PlatformSettings platformSettings, PartyData partyData) {
|
public CombatPlayMode(PlatformSettings platformSettings, PartyDefinition partyDefinition) {
|
||||||
this.platformSettings = platformSettings;
|
this.platformSettings = platformSettings;
|
||||||
this.partyData = partyData;
|
this.partyDefinition = partyDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGameModeInitialized { get; private set; }
|
public bool IsGameModeInitialized { get; private set; }
|
||||||
|
|
||||||
public void EnterPlayMode() {
|
public void EnterPlayMode() {
|
||||||
if(partyData == null) {
|
if(partyDefinition == null) {
|
||||||
Debug.LogWarning("CombatPlayMode started without PartyData.");
|
Debug.LogWarning("CombatPlayMode started without PartyData.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ using UnityEngine;
|
|||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
public class RestPlayMode : IPlayMode {
|
public class RestPlayMode : IPlayMode {
|
||||||
private readonly PlatformSettings platformSettings;
|
private readonly PlatformSettings platformSettings;
|
||||||
private readonly PartyData partyData;
|
private readonly PartyDefinition partyDefinition;
|
||||||
|
|
||||||
public RestPlayMode(PlatformSettings platformSettings, PartyData partyData) {
|
public RestPlayMode(PlatformSettings platformSettings, PartyDefinition partyDefinition) {
|
||||||
this.platformSettings = platformSettings;
|
this.platformSettings = platformSettings;
|
||||||
this.partyData = partyData;
|
this.partyDefinition = partyDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGameModeInitialized { get; private set; }
|
public bool IsGameModeInitialized { get; private set; }
|
||||||
|
|
||||||
public void EnterPlayMode() {
|
public void EnterPlayMode() {
|
||||||
if(partyData == null) {
|
if(partyDefinition == null) {
|
||||||
Debug.LogWarning("RestPlayMode started without PartyData.");
|
Debug.LogWarning("RestPlayMode started without PartyData.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ using UnityEngine;
|
|||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
public class TownPlayMode : IPlayMode {
|
public class TownPlayMode : IPlayMode {
|
||||||
private readonly PlatformSettings platformSettings;
|
private readonly PlatformSettings platformSettings;
|
||||||
private readonly PartyData partyData;
|
private readonly PartyDefinition partyDefinition;
|
||||||
|
|
||||||
public TownPlayMode(PlatformSettings platformSettings, PartyData partyData) {
|
public TownPlayMode(PlatformSettings platformSettings, PartyDefinition partyDefinition) {
|
||||||
this.platformSettings = platformSettings;
|
this.platformSettings = platformSettings;
|
||||||
this.partyData = partyData;
|
this.partyDefinition = partyDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGameModeInitialized { get; private set; }
|
public bool IsGameModeInitialized { get; private set; }
|
||||||
|
|
||||||
public void EnterPlayMode() {
|
public void EnterPlayMode() {
|
||||||
if(partyData == null) {
|
if(partyDefinition == null) {
|
||||||
Debug.LogWarning("TownPlayMode started without PartyData.");
|
Debug.LogWarning("TownPlayMode started without PartyData.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ayellowpaper.serialized-dictionary": {
|
||||||
|
"version": "file:SerializedDictionary-main",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "embedded",
|
||||||
|
"dependencies": {}
|
||||||
|
},
|
||||||
"com.jovian.savesystem": {
|
"com.jovian.savesystem": {
|
||||||
"version": "file:com.jovian.savesystem",
|
"version": "file:com.jovian.savesystem",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user