diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 97aee87..9f033ac 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,12 @@ "Bash(cmd //c \"where gh\")", "Bash(\"/c/Program Files/GitHub CLI/gh.exe\" --version)", "Bash(\"/c/Users/sebas/AppData/Local/GitHub CLI/gh.exe\" --version)", - "Bash(cmd //c \"gh --version\")" + "Bash(cmd //c \"gh --version\")", + "Bash(find /d/repos/trail-into-darkness -maxdepth 2 -type f \\\\\\(-name manifest.json -o -name ProjectSettings.asset -o -name *.yml -o -name *.yaml -o -name *.json \\\\\\))", + "Bash(grep -E \"\\\\.cs$\")", + "Bash(ls -la /d/repos/trail-into-darkness/Assets/Database/Entities/*.asset)", + "Bash(grep -r \"CharacterBaseSettings\\\\|PerkRegistry\\\\|CharacterRegistry\" /d/repos/trail-into-darkness/Assets/AddressableAssetsData --include=*.yaml --include=*.asset)", + "Bash(grep -l \"bc14507c6a9500d4eac9e684e289d084\" /d/repos/trail-into-darkness/Assets/**/*.meta)" ] } } diff --git a/Assets/AddressableAssetsData/AssetGroups/Common_Assets.asset b/Assets/AddressableAssetsData/AssetGroups/Common_Assets.asset index 6a66611..fd0bf5c 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Common_Assets.asset +++ b/Assets/AddressableAssetsData/AssetGroups/Common_Assets.asset @@ -30,6 +30,11 @@ MonoBehaviour: m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 11a8dc4883c617b46a29d5dfdd4a67d1 + m_Address: AdventureSettings + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: 13c02b14c4d048fa9653293d54f6e0e1 m_Address: Packages/com.unity.render-pipelines.universal/Shaders/2D/Sprite-Unlit-Default.shader m_ReadOnly: 0 @@ -60,6 +65,11 @@ MonoBehaviour: m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 5672eb4ca16a50b4c86b7fae03e98a30 + m_Address: PerksRegistry + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: 60a7c1e10ed7ff94cbe191d9d3e305ed m_Address: Assets/Art/UI/menu_corner.png m_ReadOnly: 0 @@ -115,6 +125,11 @@ MonoBehaviour: m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 9e8339bc69fe33641a8fbc5bffd0ebd5 + m_Address: EntitiesBaseSettings + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: a0ee74bf6f853704a8a568d5ef638ee9 m_Address: Assets/TextMesh Pro/Fonts/1529ChampFleuryPro SDF.asset m_ReadOnly: 0 @@ -136,6 +151,11 @@ MonoBehaviour: m_SerializedLabels: - mouse_cursor FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: bc14507c6a9500d4eac9e684e289d084 + m_Address: CharacterRegistry + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: c8578ae5b690b4a43b15e00c4c8314be m_Address: Assets/Art/Map/map_icon_village.png m_ReadOnly: 0 diff --git a/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset b/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset index db6f75c..7745123 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset +++ b/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset @@ -14,12 +14,7 @@ MonoBehaviour: m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup m_GroupName: Default Local Group m_GUID: 05b724f967e7d40e69a2f8086a7d78cd - m_SerializeEntries: - - m_GUID: 11a8dc4883c617b46a29d5dfdd4a67d1 - m_Address: Assets/Database/Game/AdventureSettings.asset - m_ReadOnly: 0 - m_SerializedLabels: [] - FlaggedDuringContentUpdateRestriction: 0 + m_SerializeEntries: [] m_ReadOnly: 0 m_Settings: {fileID: 11400000, guid: 7e98b357dd76a43e191428299c44eef2, type: 2} m_SchemaSet: diff --git a/Assets/Code/Core/EntryPoint.cs b/Assets/Code/Core/EntryPoint.cs index d1f83c3..69ea6a4 100644 --- a/Assets/Code/Core/EntryPoint.cs +++ b/Assets/Code/Core/EntryPoint.cs @@ -98,14 +98,17 @@ namespace Nox.Core { ISaveSlotManager saveSlotManager = new SaveSlotManager(saveStorage, saveSettings); saveSystem = new SaveSystem(saveSerializer, saveStorage, saveSlotManager, saveSettings); - var adventuredata = new AdventureData(); - var characterSystems = DefaultCharacterSystemsFactory.Create(maxPartySize: 4, new PerkRegistry(), new CharacterRegistry()); + var adventureData = new AdventureData(); + var characterBaseSettings = Addressables.LoadAssetAsync("CharacterBaseSettings").WaitForCompletion(); + var perKRegistry = Addressables.LoadAssetAsync("PerkRegistry").WaitForCompletion(); + var characterRegistry = Addressables.LoadAssetAsync("CharacterRegistry").WaitForCompletion(); + var characterSystems = DefaultCharacterSystemsFactory.Create(characterBaseSettings, perKRegistry, characterRegistry); var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory); applicationStates = new Dictionary { [GameState.BootState] = new SplashGameState(bootstrapReferences, gameDataState), - [GameState.MainMenu] = new MainMenuGameState(gameDataState, menuGameStateData, bootstrapReferences, saveSystem, partyCreatorModel, adventuredata), - [GameState.GameMode] = new GameModeGameState(gameDataState, bootstrapReferences, platform.PlatformSettings, saveSystem, sceneTransition, adventuredata), + [GameState.MainMenu] = new MainMenuGameState(gameDataState, menuGameStateData, bootstrapReferences, saveSystem, partyCreatorModel, adventureData), + [GameState.GameMode] = new GameModeGameState(gameDataState, bootstrapReferences, platform.PlatformSettings, saveSystem, sceneTransition, adventureData), }; createApplicationStateMarker.End(); } diff --git a/Assets/Code/Core/GameModeGameState.cs b/Assets/Code/Core/GameModeGameState.cs index 2da3e08..aa9558f 100644 --- a/Assets/Code/Core/GameModeGameState.cs +++ b/Assets/Code/Core/GameModeGameState.cs @@ -60,7 +60,7 @@ namespace Nox.Core { gameDataState.ChangePlayMode(sceneReference.playMode); } } - advSettingsHandler = Addressables.LoadAssetAsync("Assets/Database/Game/AdventureSettings.asset"); + advSettingsHandler = Addressables.LoadAssetAsync("AdventureSettings"); adventureSettings = advSettingsHandler.WaitForCompletion(); InitializeGameViews(); currentPlaymode = gameDataState.ActivePlayMode; diff --git a/Assets/Code/GameState/Entities/CharacterBaseSettings.cs b/Assets/Code/GameState/Entities/CharacterBaseSettings.cs new file mode 100644 index 0000000..f61e88c --- /dev/null +++ b/Assets/Code/GameState/Entities/CharacterBaseSettings.cs @@ -0,0 +1,42 @@ +using System; +using UnityEngine; + +namespace Nox.Game { + [CreateAssetMenu(fileName = "EntitiesBaseSettings", menuName = "Nox/Database/Entities/EntitiesBaseSettings")] + public class CharacterBaseSettings: ScriptableObject { + + [Header("Character General Defaults")] + public int startAttributesPool = 12; + + [Header("Character Creation Defaults")] + public EntityAttributes defaultEntityAttributes; + public EntityStats defaultEntityStats; + public PerksData defaultPerksData; + public ModifiersData defaultModifiersData; + + [Header("General Racial Bonuses and Perks per Class")] + public RacialBonuses [] racialBonuses; + public ClassBonuses [] classBonuses; + + [Header("Party System Defaults")] + public int maxPartySize = 4; + } + + [Serializable] + public sealed class RacialBonuses { + public CharacterRace race; + public EntityAttributes defaultEntityAttributes; + public EntityStats defaultEntityStats; + public PerksData perksData; + public ModifiersData modifiersData; + } + + [Serializable] + public sealed class ClassBonuses { + public CharacterClass @class; + public EntityAttributes defaultEntityAttributes; + public EntityStats defaultEntityStats; + public PerksData perksData; + public ModifiersData modifiersData; + } +} diff --git a/Assets/Code/GameState/Entities/EntitiesBaseSettings.cs.meta b/Assets/Code/GameState/Entities/CharacterBaseSettings.cs.meta similarity index 100% rename from Assets/Code/GameState/Entities/EntitiesBaseSettings.cs.meta rename to Assets/Code/GameState/Entities/CharacterBaseSettings.cs.meta diff --git a/Assets/Code/GameState/Entities/DefaultCharacterSystemsFactory.cs b/Assets/Code/GameState/Entities/DefaultCharacterSystemsFactory.cs index 7b11849..cfb3317 100644 --- a/Assets/Code/GameState/Entities/DefaultCharacterSystemsFactory.cs +++ b/Assets/Code/GameState/Entities/DefaultCharacterSystemsFactory.cs @@ -2,14 +2,14 @@ using System; namespace Nox.Game { public static class DefaultCharacterSystemsFactory { - public static ICharacterSystems Create(int maxPartySize, PerkRegistry perkRegistry, CharacterRegistry characterRegistry) { + public static ICharacterSystems Create(CharacterBaseSettings characterBaseSettings, 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, + maxPartySize = characterBaseSettings.maxPartySize, enforceUniqueCharacterIds = true }); diff --git a/Assets/Code/GameState/Entities/EntitiesBaseSettings.cs b/Assets/Code/GameState/Entities/EntitiesBaseSettings.cs deleted file mode 100644 index a8784f3..0000000 --- a/Assets/Code/GameState/Entities/EntitiesBaseSettings.cs +++ /dev/null @@ -1,18 +0,0 @@ -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; - } -} diff --git a/Assets/Database/Entities.meta b/Assets/Database/Entities.meta new file mode 100644 index 0000000..f0ec83c --- /dev/null +++ b/Assets/Database/Entities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dc417caaee5ca6245ac02ea6fd77fbed +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Database/Entities/CharacterRegistry.asset b/Assets/Database/Entities/CharacterRegistry.asset new file mode 100644 index 0000000..0d47c3f --- /dev/null +++ b/Assets/Database/Entities/CharacterRegistry.asset @@ -0,0 +1,35 @@ +%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: 91e3086d6951456e9a8a59fcbac0f750, type: 3} + m_Name: CharacterRegistry + m_EditorClassIdentifier: Assembly-CSharp::Nox.Game.CharacterRegistry + defaultCharacter: + 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: [] + characters: [] diff --git a/Assets/Database/Entities/CharacterRegistry.asset.meta b/Assets/Database/Entities/CharacterRegistry.asset.meta new file mode 100644 index 0000000..0e7ec94 --- /dev/null +++ b/Assets/Database/Entities/CharacterRegistry.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc14507c6a9500d4eac9e684e289d084 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Database/Entities/EntitiesBaseSettings.asset b/Assets/Database/Entities/EntitiesBaseSettings.asset new file mode 100644 index 0000000..9578b16 --- /dev/null +++ b/Assets/Database/Entities/EntitiesBaseSettings.asset @@ -0,0 +1,32 @@ +%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: 1bbecc15c7cd4a7ca90ce17b3d3d75f0, type: 3} + m_Name: EntitiesBaseSettings + m_EditorClassIdentifier: Assembly-CSharp::Nox.Game.CharacterBaseSettings + startAttributesPool: 12 + defaultEntityAttributes: + might: 0 + reflex: 0 + knowledge: 0 + perception: 0 + defaultEntityStats: + health: 0 + stamina: 0 + level: 0 + experience: 0 + defaultPerksData: + perks: [] + defaultModifiersData: + modifiers: [] + racialBonuses: [] + classBonuses: [] + maxPartySize: 4 diff --git a/Assets/Database/Entities/EntitiesBaseSettings.asset.meta b/Assets/Database/Entities/EntitiesBaseSettings.asset.meta new file mode 100644 index 0000000..0329824 --- /dev/null +++ b/Assets/Database/Entities/EntitiesBaseSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9e8339bc69fe33641a8fbc5bffd0ebd5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Database/Entities/PerksRegistry.asset b/Assets/Database/Entities/PerksRegistry.asset new file mode 100644 index 0000000..3af7695 --- /dev/null +++ b/Assets/Database/Entities/PerksRegistry.asset @@ -0,0 +1,16 @@ +%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: 0adb42b13ce44d8792247246a49e9c3f, type: 3} + m_Name: PerksRegistry + m_EditorClassIdentifier: Assembly-CSharp::Nox.Game.PerkRegistry + perksData: + perks: [] diff --git a/Assets/Database/Entities/PerksRegistry.asset.meta b/Assets/Database/Entities/PerksRegistry.asset.meta new file mode 100644 index 0000000..a603c98 --- /dev/null +++ b/Assets/Database/Entities/PerksRegistry.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5672eb4ca16a50b4c86b7fae03e98a30 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Adventure.unity b/Assets/Scenes/Adventure.unity index c951780..7e990e8 100644 --- a/Assets/Scenes/Adventure.unity +++ b/Assets/Scenes/Adventure.unity @@ -592,6 +592,38 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: + - target: {fileID: 836462145768403853, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 836462145768403853, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 836462145768403853, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 836462145768403853, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2124443195524357696, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2124443195524357696, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2124443195524357696, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2124443195524357696, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} - target: {fileID: 2913371113149423864, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} propertyPath: m_Name value: GUI @@ -676,6 +708,38 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 6675701153400291410, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6675701153400291410, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6675701153400291410, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6675701153400291410, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7497223681040273609, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7497223681040273609, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7497223681040273609, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7497223681040273609, guid: ddc1b5dd628590a4084c1997dd102f62, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] diff --git a/CLAUDE.md b/CLAUDE.md index 8be4461..659de09 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,57 +4,108 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Nox is a Unity RPG game with party-based gameplay. It uses Addressables for asset management, Newtonsoft JSON for serialization, and URP for rendering. +Nox is a Unity 6 RPG game (product name "Nox", Windows build target `Nox.exe`). Party-based gameplay with Addressables for asset management, Newtonsoft JSON for serialization, URP 17.3.0 for rendering, and Unity Input System 1.18.0. + +## Build & Development + +- **Unity version**: 6.0.3 (C# 9.0, .NET Framework 4.7.1) +- **Build output**: `Builds/Nox.exe` +- **No CI/CD pipeline** — builds are done through Unity Editor +- **No test suite exists** — `com.unity.test-framework` is in manifest but unused +- **Scene authoring**: Individual scenes can be played standalone via `SceneReference` component ## Architecture ### Dual-Layer State Management -The game uses two layers of state: +Two layers of state drive the application: -- **Game States** (`IGameState`): Application-level flow — Splash → MainMenu → GameMode (PlayModeGameState) -- **Play Modes** (`IPlayMode`): Gameplay-level modes within GameMode — Adventure, Town, Rest, Combat, PauseMenu +- **Game States** (`IGameState`): Application-level flow — `SplashGameState` → `MainMenuGameState` → `GameModeGameState` + - Lifecycle: `EnterGameState()` → `Tick()` / `LateTick()` → `ExitGameState()` → `Dispose()` +- **Play Modes** (`IPlayMode`): Gameplay modes within GameMode — Adventure, Town, Rest, Combat, PauseMenu + - Lifecycle: `EnterPlayMode()` → `Tick()` / `LateTick()` → `ExitGameMode()` → `Dispose()` -`PlayModeGameState` bridges these layers, managing play mode transitions and scene loading via Addressables. +`GameModeGameState` bridges these layers, managing play mode transitions and scene loading via Addressables. `GameStateRunner` (MonoBehaviour) drives the update loop and handles fade-in/fade-out transitions via `ISceneTransition`. -### Key Subsystems +### Boot Sequence -- **Boot/Entry**: `Boot.cs` → `EntryPoint.cs` → `GameStateRunner.cs` (main update loop) -- **GameData**: Global state container holding party, map, settings, and current play mode -- **Platform Abstraction**: `IPlatform` with Desktop and UnityEditor implementations, selecting input mode and settings per platform -- **Persistence**: `GamePersistenceController` + `IPlayModeStateSerializer` for JSON save/load -- **Character System**: Attributes (Might, Reflex, Knowledge) → derived Stats (Health, Stamina), with Perks and party Roles (Protagonist + Companions) +`Boot.cs` (`[RuntimeInitializeOnLoadMethod]`) → loads `"Initializer"` prefab via Addressables → `EntryPoint.cs` (MonoBehaviour) loads all config ScriptableObjects, creates game states, initializes platform subsystems and character systems → `GameStateRunner` begins ticking states. + +In editor, `Boot.cs` checks `BootMode` preference (Full Boot, Scene Boot, Unity Default). + +### GameDataState + +Central state container holding current `GameState`, `PlayMode`, `ActiveParty`, `platformSelector`, `activeSessionId`, and `savedPartyPosition`. State changes via `ChangeGameState()` and `ChangePlayMode()`. + +### Character System + +Factory-based architecture with ScriptableObject configuration: + +``` +DefaultCharacterSystemsFactory.Create() +├─ PerkFactory (PerkRegistry ScriptableObject) +├─ CharacterAttributesFactory (validates attributes) +├─ CharacterStatsFactory (derives stats from attributes) +├─ CharacterFactory (creates from templates or custom requests) +└─ PartyFactory (assembles: 1 protagonist + companions) +``` + +- **Data**: `EntityAttributes` (might, reflex, knowledge, perception) → `EntityStats` (health, stamina, level, experience) +- **CharacterDefinition**: ID, DisplayName, Attributes, Stats, Race, Class, Role (Protagonist/Companion), Perks, Modifiers +- **Config ScriptableObjects**: `CharacterBaseSettings`, `PerkRegistry`, `CharacterRegistry` +- **Key interfaces**: `ICharacterSystems`, `ICharacterFactory`, `IPartyFactory`, `IPerkFactory` + +### Persistence + +`GamePersistenceController` + Jovian.SaveSystem with JSON serialization to `Application.persistentDataPath`. Save data model: `NoxSavedDataSet` (activePlayMode, partyDefinition, partyPosition, adventureData). Save slots managed by `SaveSlotManager`. + +### Platform Abstraction + +`IPlatform` with `DesktopPlatform` and `UnityEditorPlatform`. Each initializes platform-specific `IInput` (DesktopInput/EditorInput using Unity InputSystem generated code). + +### Addressables Asset Keys + +Assets loaded by string key: `"Initializer"`, `"AdventureMapPrefabs"`, `"PauseMenuPrefabs"`, `"AdventureSettings"`, `"CharacterBaseSettings"`, `"PerkRegistry"`, `"CharacterRegistry"`, `"BootstrapReferences"`. ### Namespace Convention -Namespaces mirror the folder structure: `Nox.Core`, `Nox.Game`, `Nox.Input`, `Nox.Platform`, `Nox.Util`. +Namespaces mirror folder structure: `Nox.Core`, `Nox.Game`, `Nox.Input`, `Nox.Platform`, `Nox.Util`. ## Code Layout ``` Assets/Code/ -├── Core/ # Game states, bootstrapping, GameData, GameStateRunner -├── Game/ # Gameplay: PlayModes/, Camera/, movement, persistence, factories -├── Platform/ # IPlatform, DesktopPlatform, UnityEditorPlatform -├── Input/ # IInput abstraction with Desktop/Editor implementations -├── UI/ # Menu views and MonoBehaviour references -└── Util/ # BootMode, editor utilities +├── Core/ # Boot, EntryPoint, GameStateRunner, IGameState, IPlayMode, GameDataState +├── GameState/ +│ ├── Camera/ # CameraController, CameraSettings +│ ├── Entities/ # Character/Party/Perk factories and registries +│ ├── PlayModes/ # AdventurePlayMode, PauseMenuPlayMode, stubs for Town/Rest/Combat +│ └── UI/ # View implementations (PauseMenu, Adventure views) +├── Input/ # IInput abstraction, Desktop/Editor implementations +├── Platform/ # IPlatform, DesktopPlatform, UnityEditorPlatform +├── SplashMainMenuUI/ +└── Util/ # BootMode editor utility ``` ## C# Style (from .editorconfig) - 4 spaces, LF line endings -- **No `var`** — use explicit types (`csharp_style_var_*: false`) +- **`var` preferred** (`csharp_style_var_*: true:suggestion`) — use `var` when type is apparent +- **Target-typed `new`** when type is evident (`CharacterDefinition c = new()`) - **No space after keywords** in control flow (`if(`, `for(`, not `if (`) - **Opening braces on same line** (`csharp_new_line_before_open_brace = none`) +- **`else`/`catch`/`finally` on new line** after closing brace - Block-scoped namespaces (`namespace Foo { ... }`) - Always use braces (`csharp_prefer_braces = true`) - Naming: PascalCase for types/methods/properties, camelCase for all fields (public and private), `I` prefix for interfaces -- Expression-bodied: yes for properties/accessors/indexers/lambdas, no for constructors/methods/operators +- Expression-bodied: yes for properties/accessors/indexers/lambdas; no for constructors/methods/operators/local functions +- No `this.` qualifier +- Use language keywords over BCL types (`int` not `Int32`) ## Patterns - Constructor-based dependency injection (no DI container) - ScriptableObject-based configuration loaded via Addressables - Factory pattern for character/party creation -- Action delegates for UI event communication +- Action delegates for UI event communication (e.g., `menuGameStateData.startGameRequests += handler`) +- Mixed async patterns: both `async Task` and `IEnumerator` coroutines for async operations