First commit
This commit is contained in:
60
Assets/Code/GameState/UI/AdventureView.cs
Normal file
60
Assets/Code/GameState/UI/AdventureView.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Jovian.Calendar;
|
||||
using Nox.Core;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class AdventureView : IMenuView {
|
||||
private readonly GameDataState gameDataState;
|
||||
private readonly GuiReferences guiReferences;
|
||||
private readonly InputSystem_Actions inputActions;
|
||||
private readonly AdventureData adventureData;
|
||||
private readonly AdventureSettings adventureSettings;
|
||||
private readonly WorldClock worldClock;
|
||||
private int currentDay;
|
||||
private int previousTime;
|
||||
|
||||
public AdventureView(GameDataState gameDataState,
|
||||
GuiReferences guiReferences,
|
||||
InputSystem_Actions inputActions,
|
||||
AdventureData adventureData,
|
||||
AdventureSettings adventureSettings,
|
||||
WorldClock worldClock) {
|
||||
this.gameDataState = gameDataState;
|
||||
this.guiReferences = guiReferences;
|
||||
this.inputActions = inputActions;
|
||||
this.adventureData = adventureData;
|
||||
this.adventureSettings = adventureSettings;
|
||||
this.worldClock = worldClock;
|
||||
}
|
||||
private void InvokePauseMenu() {
|
||||
gameDataState.ChangePlayMode(PlayMode.PauseMenu);
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
guiReferences.pauseMenuButton.onClick.AddListener(InvokePauseMenu);
|
||||
guiReferences.dayText.text = $"{worldClock.FullStringNamed()}";
|
||||
previousTime = -1;
|
||||
guiReferences.suppliesBar.fillAmount = (float)adventureData.suppliesAvailable / adventureSettings.maxSupplies;
|
||||
guiReferences.suppliesText.text = $"{adventureData.suppliesAvailable}/{adventureSettings.maxSupplies}";
|
||||
}
|
||||
|
||||
public void Show() {
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public void Hide() {
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public void Tick() {
|
||||
var time = worldClock.Now.minute;
|
||||
if (time != previousTime) {
|
||||
previousTime = time;
|
||||
}
|
||||
guiReferences.dayText.text = $"{worldClock.FullStringNamed()}";
|
||||
|
||||
if(currentDay != adventureData.currentDay) {
|
||||
currentDay = adventureData.currentDay;
|
||||
guiReferences.suppliesBar.fillAmount = (float)adventureData.suppliesAvailable / adventureSettings.maxSupplies;
|
||||
guiReferences.suppliesText.text = $"{adventureData.suppliesAvailable}/{adventureSettings.maxSupplies}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/UI/AdventureView.cs.meta
Normal file
3
Assets/Code/GameState/UI/AdventureView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd45a685ea554bd7ad15745f836a6de9
|
||||
timeCreated: 1772572300
|
||||
12
Assets/Code/GameState/UI/AttributeReference.cs
Normal file
12
Assets/Code/GameState/UI/AttributeReference.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.UI {
|
||||
public class AttributeReference : MonoBehaviour {
|
||||
public Button removePointsButton;
|
||||
public Button addPointsButton;
|
||||
public TextMeshProUGUI attributeName;
|
||||
public TextMeshProUGUI attributeValue;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/AttributeReference.cs.meta
Normal file
2
Assets/Code/GameState/UI/AttributeReference.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f51aaf11e81876845b289f5f7d310469
|
||||
34
Assets/Code/GameState/UI/CharacterCreationReference.cs
Normal file
34
Assets/Code/GameState/UI/CharacterCreationReference.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Jovian.InGameLogging.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.UI {
|
||||
public class CharacterCreationReference : MonoBehaviour {
|
||||
public Canvas canvas;
|
||||
|
||||
//top
|
||||
public Button backButton;
|
||||
public Button settingsButton;
|
||||
|
||||
//left
|
||||
public TMP_Dropdown raceDropdown;
|
||||
public TMP_Dropdown classDropdown;
|
||||
public TMP_Dropdown perksDropdown;
|
||||
public TextMeshProUGUI pointsToDistribute;
|
||||
public AttributeReference[] attributeReference;
|
||||
public StatReference[] statReference;
|
||||
|
||||
//center
|
||||
public Button backButtonCenter;
|
||||
public Button acceptButton;
|
||||
public GameLogView gameLogView;
|
||||
|
||||
//right
|
||||
public Image portraitImage;
|
||||
public Button portraitSelectionLeftButton;
|
||||
public Button portraitSelectionRightButton;
|
||||
public TMP_InputField nameInputField;
|
||||
public Button startGameButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15f5fb19d1765bb49b9dd5956f172398
|
||||
604
Assets/Code/GameState/UI/CharacterCreationView.cs
Normal file
604
Assets/Code/GameState/UI/CharacterCreationView.cs
Normal file
@@ -0,0 +1,604 @@
|
||||
using Jovian.InGameLogging;
|
||||
using Jovian.InGameLogging.UI;
|
||||
using Jovian.SaveSystem;
|
||||
using Nox.Core;
|
||||
using Nox.Game;
|
||||
using Nox.Game.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using ZLinq;
|
||||
using Attribute = Nox.Game.Attribute;
|
||||
using PlayMode = Nox.Core.PlayMode;
|
||||
|
||||
namespace Nox.UI {
|
||||
public class CharacterCreationView : IGameLifecycle, IMenuView {
|
||||
public ISaveSystem SaveSystem { get; }
|
||||
|
||||
private readonly CharacterCreationReference characterCreationReference;
|
||||
private readonly MenuGameStateData menuGameStateData;
|
||||
private readonly GameDataState gameDataState;
|
||||
private readonly PartySettings partySettings;
|
||||
private readonly ICharacterSystems characterSystems;
|
||||
private readonly PortraitsHolder portraitsHolder;
|
||||
private readonly StarterCharacterSettings starterCharacterSettings;
|
||||
|
||||
// Logger
|
||||
private GameLogView gameLogView;
|
||||
private InGameLogger inGameLogger;
|
||||
|
||||
// Working state
|
||||
private CharacterRace selectedRace;
|
||||
private CharacterClass selectedClass;
|
||||
private int currentPortraitIndex;
|
||||
private int remainingPoints;
|
||||
private readonly int[] allocatedPoints = new int[4]; // Might, Reflex, Knowledge, Perception (AttributeType 1-4)
|
||||
private int previousHealth;
|
||||
private int previousMana;
|
||||
|
||||
// Modifier source tracking
|
||||
private PerksData racialPerks = new();
|
||||
private ModifiersData racialModifiers = new();
|
||||
private PerksData classPerks = new();
|
||||
private ModifiersData classModifiers = new();
|
||||
private readonly List<PerkDefinition> playerPerks = new();
|
||||
private List<IPerk> availablePerks = new();
|
||||
|
||||
// Computed state
|
||||
private EntityAttributes workingAttributes;
|
||||
private EntityStats workingStats;
|
||||
|
||||
// Output
|
||||
private List<CharacterCreationRequest> characterCreationRequests;
|
||||
private Action canStartCheck;
|
||||
|
||||
// Back confirmation (null until popup is implemented)
|
||||
private Action confirmBackAction;
|
||||
|
||||
public CharacterCreationView(CharacterCreationReference characterCreationReference,
|
||||
MenuGameStateData menuGameStateData,
|
||||
ISaveSystem saveSystem,
|
||||
GameDataState gameDataState,
|
||||
PartySettings partySettings,
|
||||
ICharacterSystems characterSystems,
|
||||
PortraitsHolder portraitsHolder,
|
||||
StarterCharacterSettings starterCharacterSettings) {
|
||||
SaveSystem = saveSystem;
|
||||
this.characterCreationReference = characterCreationReference;
|
||||
this.menuGameStateData = menuGameStateData;
|
||||
this.gameDataState = gameDataState;
|
||||
this.partySettings = partySettings;
|
||||
this.characterSystems = characterSystems;
|
||||
this.portraitsHolder = portraitsHolder;
|
||||
this.starterCharacterSettings = starterCharacterSettings;
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
// Logger
|
||||
var store = new GameLogStore(500);
|
||||
gameLogView = characterCreationReference.gameLogView;
|
||||
gameLogView.Initialize(store);
|
||||
inGameLogger = new InGameLogger(store, LogChannel.CharacterCreation);
|
||||
inGameLogger.Enable();
|
||||
|
||||
// Start Game button
|
||||
canStartCheck = () => {
|
||||
var canStart = characterCreationRequests is { Count: > 0 };
|
||||
characterCreationReference.startGameButton.interactable = canStart;
|
||||
};
|
||||
characterCreationReference.startGameButton.onClick.AddListener(() => {
|
||||
if(characterCreationRequests == null || characterCreationRequests.Count == 0) {
|
||||
inGameLogger.Log("You must accept your character before starting the game.", "#FF4444");
|
||||
return;
|
||||
}
|
||||
Hide();
|
||||
menuGameStateData.startGameRequests?.Invoke(PlayMode.Adventure);
|
||||
});
|
||||
|
||||
// Back buttons with popup check
|
||||
characterCreationReference.backButton.onClick.AddListener(OnBackClicked);
|
||||
characterCreationReference.backButtonCenter.onClick.AddListener(OnBackClicked);
|
||||
|
||||
// Accept button
|
||||
characterCreationReference.acceptButton.onClick.AddListener(OnAcceptClicked);
|
||||
|
||||
// Race dropdown
|
||||
PopulateEnumDropdown<CharacterRace>(characterCreationReference.raceDropdown);
|
||||
characterCreationReference.raceDropdown.onValueChanged.AddListener(OnRaceChanged);
|
||||
|
||||
// Class dropdown
|
||||
PopulateEnumDropdown<CharacterClass>(characterCreationReference.classDropdown);
|
||||
characterCreationReference.classDropdown.onValueChanged.AddListener(OnClassChanged);
|
||||
|
||||
// Perks dropdown
|
||||
PopulatePerksDropdown();
|
||||
characterCreationReference.perksDropdown.onValueChanged.AddListener(OnPerkSelected);
|
||||
|
||||
// Attribute +/- buttons
|
||||
var attrTypes = new[] { AttributeType.Might, AttributeType.Reflex, AttributeType.Knowledge, AttributeType.Perception };
|
||||
var attrRefs = characterCreationReference.attributeReference;
|
||||
for(int i = 0; i < attrRefs.Length && i < attrTypes.Length; i++) {
|
||||
var type = attrTypes[i];
|
||||
attrRefs[i].attributeName.text = type.ToString();
|
||||
attrRefs[i].addPointsButton.onClick.AddListener(() => OnAttributeAdd(type));
|
||||
attrRefs[i].removePointsButton.onClick.AddListener(() => OnAttributeRemove(type));
|
||||
}
|
||||
|
||||
// Portrait navigation
|
||||
currentPortraitIndex = 0;
|
||||
if(portraitsHolder != null && portraitsHolder.portraits.Length > 0) {
|
||||
characterCreationReference.portraitImage.sprite = portraitsHolder.portraits[0];
|
||||
}
|
||||
characterCreationReference.portraitSelectionLeftButton.onClick.AddListener(OnPortraitLeft);
|
||||
characterCreationReference.portraitSelectionRightButton.onClick.AddListener(OnPortraitRight);
|
||||
|
||||
// Initial state
|
||||
selectedRace = CharacterRace.Human;
|
||||
selectedClass = CharacterClass.Warrior;
|
||||
characterCreationReference.raceDropdown.SetValueWithoutNotify(0);
|
||||
characterCreationReference.classDropdown.SetValueWithoutNotify(0);
|
||||
ResetWorkingState();
|
||||
}
|
||||
|
||||
// --- Dropdown helpers ---
|
||||
|
||||
private void PopulateEnumDropdown<T>(TMP_Dropdown dropdown) where T : Enum {
|
||||
dropdown.ClearOptions();
|
||||
var options = new List<string>();
|
||||
foreach(T value in Enum.GetValues(typeof(T))) {
|
||||
if(Convert.ToInt32(value) == 0) {
|
||||
continue; // skip None
|
||||
}
|
||||
options.Add(value.ToString());
|
||||
}
|
||||
dropdown.AddOptions(options);
|
||||
}
|
||||
|
||||
private void PopulatePerksDropdown() {
|
||||
var dropdown = characterCreationReference.perksDropdown;
|
||||
dropdown.ClearOptions();
|
||||
availablePerks = new List<IPerk>(characterSystems.PerkFactory.GetAll());
|
||||
|
||||
var options = new List<string> { "Select a perk..." };
|
||||
foreach(var perk in availablePerks) {
|
||||
options.Add(perk.Name);
|
||||
}
|
||||
dropdown.AddOptions(options);
|
||||
dropdown.SetValueWithoutNotify(0);
|
||||
}
|
||||
|
||||
// --- State management ---
|
||||
|
||||
private void ResetWorkingState() {
|
||||
Array.Clear(allocatedPoints, 0, allocatedPoints.Length);
|
||||
racialPerks = new PerksData();
|
||||
racialModifiers = new ModifiersData();
|
||||
classPerks = new PerksData();
|
||||
classModifiers = new ModifiersData();
|
||||
playerPerks.Clear();
|
||||
|
||||
ApplyRacialBonuses();
|
||||
ApplyClassBonuses();
|
||||
UpdateRemainingPoints();
|
||||
RecalculateAll();
|
||||
|
||||
// Initialize previous values so first change doesn't log a delta from 0
|
||||
previousHealth = workingStats.GetValue(StatType.Health);
|
||||
previousMana = workingStats.GetValue(StatType.Mana);
|
||||
}
|
||||
|
||||
private void ApplyRacialBonuses() {
|
||||
racialPerks = new PerksData();
|
||||
racialModifiers = new ModifiersData();
|
||||
var bonuses = starterCharacterSettings.racialBonuses;
|
||||
if(bonuses == null) {
|
||||
return;
|
||||
}
|
||||
foreach(var rb in bonuses) {
|
||||
if(rb.race == selectedRace) {
|
||||
racialPerks = rb.startingPerks ?? new PerksData();
|
||||
racialModifiers = rb.permanentModifiers ?? new ModifiersData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyClassBonuses() {
|
||||
classPerks = new PerksData();
|
||||
classModifiers = new ModifiersData();
|
||||
var bonuses = starterCharacterSettings.classBonuses;
|
||||
if(bonuses == null) {
|
||||
return;
|
||||
}
|
||||
foreach(var cb in bonuses) {
|
||||
if(cb.@class == selectedClass) {
|
||||
classPerks = cb.startingPerks ?? new PerksData();
|
||||
classModifiers = cb.permanentModifiers ?? new ModifiersData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRemainingPoints() {
|
||||
var totalPoints = 0;
|
||||
if(starterCharacterSettings.distributionPointsPerClass != null) {
|
||||
foreach(var dpc in starterCharacterSettings.distributionPointsPerClass) {
|
||||
if(dpc.@class == selectedClass) {
|
||||
totalPoints = dpc.points;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var spent = 0;
|
||||
for(int i = 0; i < allocatedPoints.Length; i++) {
|
||||
spent += allocatedPoints[i];
|
||||
}
|
||||
remainingPoints = totalPoints - spent;
|
||||
}
|
||||
|
||||
// --- Core calculation ---
|
||||
|
||||
private void RecalculateAll() {
|
||||
// 1. Start from default attributes + player allocations
|
||||
var baseAttrs = starterCharacterSettings.defaultEntityAttributes;
|
||||
var attrTypes = new[] { AttributeType.Might, AttributeType.Reflex, AttributeType.Knowledge, AttributeType.Perception };
|
||||
var finalAttrs = new Attribute[attrTypes.Length];
|
||||
for(int i = 0; i < attrTypes.Length; i++) {
|
||||
var baseVal = baseAttrs.GetValue(attrTypes[i]);
|
||||
finalAttrs[i] = new Attribute(attrTypes[i], baseVal + allocatedPoints[i]);
|
||||
}
|
||||
workingAttributes = new EntityAttributes { attributes = finalAttrs };
|
||||
|
||||
// 2. Build combined perks and modifiers (defaults + racial + class + player)
|
||||
// Racial/class attribute and stat bonuses flow through permanentModifiers
|
||||
var combinedPerks = BuildCombinedPerks();
|
||||
var combinedModifiers = BuildCombinedModifiers();
|
||||
|
||||
// 3. Build temp entity for modifier collection
|
||||
var tempEntity = new CharacterCreationRequest {
|
||||
Id = Guid.NewGuid(),
|
||||
Race = selectedRace,
|
||||
Class = selectedClass,
|
||||
Role = CharacterRole.Protagonist,
|
||||
Attributes = workingAttributes,
|
||||
Perks = combinedPerks,
|
||||
Modifiers = combinedModifiers
|
||||
};
|
||||
|
||||
// 4. Resolve attributes through modifiers (racial/class attribute bonuses come via modifiers)
|
||||
var resolver = characterSystems.ModifierResolver;
|
||||
var resolvedAttrs = new Attribute[attrTypes.Length];
|
||||
for(int i = 0; i < attrTypes.Length; i++) {
|
||||
var mods = resolver.CollectModifiers(tempEntity, attrTypes[i]);
|
||||
resolvedAttrs[i] = new Attribute(attrTypes[i], resolver.Resolve(finalAttrs[i].value, mods, tempEntity));
|
||||
}
|
||||
workingAttributes = new EntityAttributes { attributes = resolvedAttrs };
|
||||
tempEntity.Attributes = workingAttributes;
|
||||
|
||||
// 5. Calculate stats through modifiers (racial/class stat bonuses come via modifiers)
|
||||
var baseStats = starterCharacterSettings.defaultEntityStats;
|
||||
var statTypes = new[] { StatType.Health, StatType.Mana, StatType.Level, StatType.Experience };
|
||||
var resolvedStats = new Stat[statTypes.Length];
|
||||
for(int i = 0; i < statTypes.Length; i++) {
|
||||
var baseVal = baseStats.GetValue(statTypes[i]);
|
||||
var mods = resolver.CollectModifiers(tempEntity, statTypes[i]);
|
||||
resolvedStats[i] = new Stat(statTypes[i], resolver.Resolve(baseVal, mods, tempEntity));
|
||||
}
|
||||
workingStats = new EntityStats { stats = resolvedStats };
|
||||
|
||||
// 9. Update UI
|
||||
UpdateAttributeUI();
|
||||
UpdateStatUI();
|
||||
UpdatePointsDisplay();
|
||||
|
||||
// 10. Log stat deltas
|
||||
var newHealth = workingStats.GetValue(StatType.Health);
|
||||
var newMana = workingStats.GetValue(StatType.Mana);
|
||||
if(newHealth != previousHealth && previousHealth != 0) {
|
||||
var delta = newHealth - previousHealth;
|
||||
var sign = delta > 0 ? "+" : "";
|
||||
inGameLogger.Log($"Health: {previousHealth} -> {newHealth} ({sign}{delta})", "#87CEEB");
|
||||
}
|
||||
if(newMana != previousMana && previousMana != 0) {
|
||||
var delta = newMana - previousMana;
|
||||
var sign = delta > 0 ? "+" : "";
|
||||
inGameLogger.Log($"Mana: {previousMana} -> {newMana} ({sign}{delta})", "#FFFF99");
|
||||
}
|
||||
previousHealth = newHealth;
|
||||
previousMana = newMana;
|
||||
}
|
||||
|
||||
private PerksData BuildCombinedPerks() {
|
||||
// Start with defaults, add racial/class/player perks (deduplicate by Id)
|
||||
var combined = new PerksData { perks = new List<PerkDefinition>() };
|
||||
var seenIds = new HashSet<Guid>();
|
||||
|
||||
void AddPerks(PerksData source) {
|
||||
if(source?.perks == null) {
|
||||
return;
|
||||
}
|
||||
foreach(var perk in source.perks) {
|
||||
if(perk != null && seenIds.Add(perk.Id)) {
|
||||
combined.perks.Add(perk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddPerks(starterCharacterSettings.defaultPerksData);
|
||||
AddPerks(racialPerks);
|
||||
AddPerks(classPerks);
|
||||
foreach(var perk in playerPerks) {
|
||||
if(perk != null && seenIds.Add(perk.Id)) {
|
||||
combined.perks.Add(perk);
|
||||
}
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
|
||||
private ModifiersData BuildCombinedModifiers() {
|
||||
// Start with defaults. Racial/class modifiers override defaults that target the same
|
||||
// thing, but ONLY if the override's requirements are currently met. If requirements
|
||||
// are not met, the default stays and the override is still added — the resolver will
|
||||
// skip the unqualified override at resolution time, leaving the default active.
|
||||
var combined = new ModifiersData { modifiers = new List<ModifierDefinition>() };
|
||||
|
||||
// Seed with defaults
|
||||
if(starterCharacterSettings.defaultModifiersData?.modifiers != null) {
|
||||
combined.modifiers.AddRange(starterCharacterSettings.defaultModifiersData.modifiers);
|
||||
}
|
||||
|
||||
// Override with racial modifiers
|
||||
OverrideModifiers(combined, racialModifiers, workingAttributes);
|
||||
|
||||
// Override with class modifiers
|
||||
OverrideModifiers(combined, classModifiers, workingAttributes);
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
private static void OverrideModifiers(ModifiersData combined, ModifiersData overrides, EntityAttributes currentAttributes) {
|
||||
if(overrides?.modifiers == null) {
|
||||
return;
|
||||
}
|
||||
foreach(var mod in overrides.modifiers) {
|
||||
if(mod?.Target == null) {
|
||||
continue;
|
||||
}
|
||||
// Only remove the default if the override's requirements are currently met.
|
||||
// Both are added regardless — the resolver skips unqualified modifiers at
|
||||
// resolution time, so if requirements aren't met, the default still applies.
|
||||
var requirementsMet = AreRequirementsMet(mod, currentAttributes);
|
||||
if(requirementsMet) {
|
||||
for(int i = combined.modifiers.Count - 1; i >= 0; i--) {
|
||||
var existing = combined.modifiers[i];
|
||||
if(existing?.Target != null && TargetsMatch(existing.Target, mod.Target)) {
|
||||
combined.modifiers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
combined.modifiers.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AreRequirementsMet(ModifierDefinition mod, EntityAttributes attributes) {
|
||||
if(mod.Requirements == null || mod.Requirements.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
foreach(var req in mod.Requirements) {
|
||||
if(!req.IsMet(attributes)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TargetsMatch(ModifierTarget a, ModifierTarget b) {
|
||||
if(a.Type != b.Type) {
|
||||
return false;
|
||||
}
|
||||
return a.Type switch {
|
||||
ModifierTargetType.Attribute => a.AttributeType == b.AttributeType,
|
||||
ModifierTargetType.Stat => a.StatType == b.StatType,
|
||||
ModifierTargetType.CombatScore => a.CombatScoreType == b.CombatScoreType,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
// --- UI updates ---
|
||||
|
||||
private void UpdateAttributeUI() {
|
||||
var attrTypes = new[] { AttributeType.Might, AttributeType.Reflex, AttributeType.Knowledge, AttributeType.Perception };
|
||||
var attrRefs = characterCreationReference.attributeReference;
|
||||
for(int i = 0; i < attrRefs.Length && i < attrTypes.Length; i++) {
|
||||
attrRefs[i].attributeValue.text = workingAttributes.GetValue(attrTypes[i]).ToString();
|
||||
attrRefs[i].addPointsButton.interactable = remainingPoints > 0;
|
||||
attrRefs[i].removePointsButton.interactable = allocatedPoints[i] > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatUI() {
|
||||
var statTypes = new[] { StatType.Health, StatType.Mana };
|
||||
var statRefs = characterCreationReference.statReference;
|
||||
for(int i = 0; i < statRefs.Length && i < statTypes.Length; i++) {
|
||||
var value = workingStats.GetValue(statTypes[i]);
|
||||
statRefs[i].statName.text = statTypes[i].ToString();
|
||||
statRefs[i].statValue.text = value.ToString();
|
||||
statRefs[i].statBar.fillAmount = Mathf.Clamp01(value / 200f);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePointsDisplay() {
|
||||
characterCreationReference.pointsToDistribute.text = remainingPoints.ToString();
|
||||
}
|
||||
|
||||
// --- Event handlers ---
|
||||
|
||||
private void OnRaceChanged(int index) {
|
||||
selectedRace = (CharacterRace)(index + 1);
|
||||
Array.Clear(allocatedPoints, 0, allocatedPoints.Length);
|
||||
ApplyRacialBonuses();
|
||||
UpdateRemainingPoints();
|
||||
RecalculateAll();
|
||||
inGameLogger.Log($"Race changed to {selectedRace}");
|
||||
}
|
||||
|
||||
private void OnClassChanged(int index) {
|
||||
selectedClass = (CharacterClass)(index + 1);
|
||||
Array.Clear(allocatedPoints, 0, allocatedPoints.Length);
|
||||
ApplyClassBonuses();
|
||||
UpdateRemainingPoints();
|
||||
RecalculateAll();
|
||||
inGameLogger.Log($"Class changed to {selectedClass}");
|
||||
}
|
||||
|
||||
private void OnPerkSelected(int index) {
|
||||
if(index <= 0 || index > availablePerks.Count) {
|
||||
return; // "Select a perk..." placeholder
|
||||
}
|
||||
|
||||
var perkIndex = index - 1; // offset for placeholder
|
||||
var perk = availablePerks[perkIndex];
|
||||
playerPerks.Add(new PerkDefinition {
|
||||
Id = perk.Id,
|
||||
Name = perk.Name,
|
||||
Modifiers = perk.Modifiers
|
||||
});
|
||||
availablePerks.RemoveAt(perkIndex);
|
||||
PopulatePerksDropdown();
|
||||
RecalculateAll();
|
||||
inGameLogger.Log($"Perk added: {perk.Name}");
|
||||
}
|
||||
|
||||
private void OnAttributeAdd(AttributeType type) {
|
||||
if(remainingPoints <= 0) {
|
||||
return;
|
||||
}
|
||||
var idx = (int)type - 1;
|
||||
allocatedPoints[idx]++;
|
||||
remainingPoints--;
|
||||
RecalculateAll();
|
||||
}
|
||||
|
||||
private void OnAttributeRemove(AttributeType type) {
|
||||
var idx = (int)type - 1;
|
||||
if(allocatedPoints[idx] <= 0) {
|
||||
return;
|
||||
}
|
||||
allocatedPoints[idx]--;
|
||||
remainingPoints++;
|
||||
RecalculateAll();
|
||||
}
|
||||
|
||||
private void OnPortraitLeft() {
|
||||
if(portraitsHolder == null || portraitsHolder.portraits.Length == 0) {
|
||||
return;
|
||||
}
|
||||
currentPortraitIndex--;
|
||||
if(currentPortraitIndex < 0) {
|
||||
currentPortraitIndex = portraitsHolder.portraits.Length - 1;
|
||||
}
|
||||
characterCreationReference.portraitImage.sprite = portraitsHolder.portraits[currentPortraitIndex];
|
||||
}
|
||||
|
||||
private void OnPortraitRight() {
|
||||
if(portraitsHolder == null || portraitsHolder.portraits.Length == 0) {
|
||||
return;
|
||||
}
|
||||
currentPortraitIndex++;
|
||||
if(currentPortraitIndex >= portraitsHolder.portraits.Length) {
|
||||
currentPortraitIndex = 0;
|
||||
}
|
||||
characterCreationReference.portraitImage.sprite = portraitsHolder.portraits[currentPortraitIndex];
|
||||
}
|
||||
|
||||
private void OnBackClicked() {
|
||||
if(confirmBackAction != null) {
|
||||
confirmBackAction();
|
||||
return;
|
||||
}
|
||||
Hide();
|
||||
}
|
||||
|
||||
// --- Accept ---
|
||||
|
||||
private void OnAcceptClicked() {
|
||||
var errors = new List<string>();
|
||||
if(selectedRace == CharacterRace.None) {
|
||||
errors.Add("Race must be selected");
|
||||
}
|
||||
if(selectedClass == CharacterClass.None) {
|
||||
errors.Add("Class must be selected");
|
||||
}
|
||||
if(remainingPoints > 0) {
|
||||
errors.Add($"{remainingPoints} distribution points remaining");
|
||||
}
|
||||
var characterName = characterCreationReference.nameInputField.text;
|
||||
if(string.IsNullOrWhiteSpace(characterName)) {
|
||||
errors.Add("Name cannot be empty");
|
||||
}
|
||||
|
||||
if(errors.Count > 0) {
|
||||
foreach(var error in errors) {
|
||||
inGameLogger.Log(error, "#FF4444");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new CharacterCreationRequest {
|
||||
Id = Guid.NewGuid(),
|
||||
Name = characterName,
|
||||
Race = selectedRace,
|
||||
Class = selectedClass,
|
||||
Role = CharacterRole.Protagonist,
|
||||
PortraitIndex = currentPortraitIndex,
|
||||
Attributes = workingAttributes,
|
||||
Stats = workingStats,
|
||||
Perks = BuildCombinedPerks(),
|
||||
Modifiers = BuildCombinedModifiers()
|
||||
};
|
||||
|
||||
characterCreationRequests = new List<CharacterCreationRequest> { request };
|
||||
|
||||
// Log full breakdown
|
||||
inGameLogger.Log("--- Character Accepted ---");
|
||||
inGameLogger.Log($"Name: {request.Name}", "#FFBF00");
|
||||
inGameLogger.Log($"Race: {request.Race}");
|
||||
inGameLogger.Log($"Class: {request.Class}");
|
||||
inGameLogger.Log($"Portrait: #{request.PortraitIndex}");
|
||||
inGameLogger.Log($"{request.Attributes}");
|
||||
inGameLogger.Log($"{request.Stats}");
|
||||
if(request.Perks?.perks != null && request.Perks.perks.Count > 0) {
|
||||
foreach(var perk in request.Perks.perks) {
|
||||
inGameLogger.Log($"Perk: {perk.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
CreateParty();
|
||||
canStartCheck.Invoke();
|
||||
}
|
||||
|
||||
private void CreateParty() {
|
||||
var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory, characterCreationRequests, partySettings);
|
||||
var party = partyCreatorModel.CreatePartyForNewRun();
|
||||
gameDataState.ActiveParty = party;
|
||||
}
|
||||
|
||||
// --- Lifecycle ---
|
||||
|
||||
public void Tick() {
|
||||
}
|
||||
|
||||
public void Show() {
|
||||
characterCreationReference.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide() {
|
||||
characterCreationReference.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
inGameLogger.Disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/UI/CharacterCreationView.cs.meta
Normal file
3
Assets/Code/GameState/UI/CharacterCreationView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 555173dcbeb04c44b6f02f04fb1cb762
|
||||
timeCreated: 1775381234
|
||||
14
Assets/Code/GameState/UI/GuiReferences.cs
Normal file
14
Assets/Code/GameState/UI/GuiReferences.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class GuiReferences : MonoBehaviour {
|
||||
public Transform portraitsContainer;
|
||||
public PartyMemberSlot partyMemberSlotPrefab;
|
||||
public Image suppliesBar;
|
||||
public TextMeshProUGUI suppliesText;
|
||||
public Button pauseMenuButton;
|
||||
public TextMeshProUGUI dayText;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/GuiReferences.cs.meta
Normal file
2
Assets/Code/GameState/UI/GuiReferences.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ba89f0d55be1ed4bb61a320344f706d
|
||||
180
Assets/Code/GameState/UI/PartyGuiView.cs
Normal file
180
Assets/Code/GameState/UI/PartyGuiView.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Collections.Generic;
|
||||
using Jovian.PopupSystem;
|
||||
using Nox.UI;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class PartyGuiView {
|
||||
private const int PoolSize = 4;
|
||||
|
||||
private readonly Transform portraitsContainer;
|
||||
private readonly PartyMemberSlot slotPrefab;
|
||||
private readonly PortraitsHolder portraitsHolder;
|
||||
private readonly IPopupSystem popupSystem;
|
||||
private readonly PartyMemberSlot[] slotPool = new PartyMemberSlot[PoolSize];
|
||||
|
||||
private PartyDefinition trackedParty;
|
||||
private int activeCount;
|
||||
|
||||
public PartyGuiView(Transform portraitsContainer, PartyMemberSlot slotPrefab, PortraitsHolder portraitsHolder, IPopupSystem popupSystem = null) {
|
||||
this.portraitsContainer = portraitsContainer;
|
||||
this.slotPrefab = slotPrefab;
|
||||
this.portraitsHolder = portraitsHolder;
|
||||
this.popupSystem = popupSystem;
|
||||
}
|
||||
|
||||
public void Initialize(PartyDefinition party) {
|
||||
trackedParty = party;
|
||||
activeCount = 0;
|
||||
|
||||
// Pre-create all slots (deactivated)
|
||||
for(int i = 0; i < PoolSize; i++) {
|
||||
if(slotPool[i] == null) {
|
||||
slotPool[i] = Object.Instantiate(slotPrefab, portraitsContainer);
|
||||
}
|
||||
slotPool[i].gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
PopulateSlots();
|
||||
}
|
||||
|
||||
public void Tick() {
|
||||
if(trackedParty == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Rebuild if member count changed
|
||||
if(trackedParty.members.Count != activeCount) {
|
||||
PopulateSlots();
|
||||
}
|
||||
|
||||
// Update dynamic values
|
||||
var memberCount = Mathf.Min(activeCount, trackedParty.members.Count);
|
||||
for(int i = 0; i < memberCount; i++) {
|
||||
UpdateSlotStats(slotPool[i], trackedParty.members[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateSlots() {
|
||||
var memberCount = trackedParty?.members != null
|
||||
? Mathf.Min(trackedParty.members.Count, PoolSize)
|
||||
: 0;
|
||||
|
||||
// Activate slots for current members, deactivate the rest
|
||||
for(var i = 0; i < PoolSize; i++) {
|
||||
var slot = slotPool[i];
|
||||
if(i < memberCount && trackedParty?.members != null) {
|
||||
var member = trackedParty.members[i];
|
||||
if(member == null) {
|
||||
continue;
|
||||
}
|
||||
slot.gameObject.SetActive(true);
|
||||
|
||||
// Portrait
|
||||
if(portraitsHolder != null && portraitsHolder.portraits.Length > 0) {
|
||||
var idx = Mathf.Clamp(member.PortraitIndex, 0, portraitsHolder.portraits.Length - 1);
|
||||
slot.portrait.sprite = portraitsHolder.portraits[idx];
|
||||
}
|
||||
|
||||
UpdateSlotStats(slot, member);
|
||||
}
|
||||
else {
|
||||
slot.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
activeCount = memberCount;
|
||||
|
||||
// Initialize popup triggers
|
||||
popupSystem?.InitializeTriggersInChildren(portraitsContainer, (trigger, view) => {
|
||||
var slot = trigger.GetComponentInParent<PartyMemberSlot>();
|
||||
if(!slot) {
|
||||
return;
|
||||
}
|
||||
var slotIndex = System.Array.IndexOf(slotPool, slot);
|
||||
if(slotIndex < 0 || slotIndex >= activeCount) {
|
||||
return;
|
||||
}
|
||||
var member = trackedParty.members[slotIndex];
|
||||
view.SetContent(builder => BuildCharacterPopup(builder, member));
|
||||
});
|
||||
}
|
||||
|
||||
private void BuildCharacterPopup(PopupContentBuilder builder, CharacterDefinition member) {
|
||||
// Header
|
||||
builder
|
||||
.AddText(member.Name, PopupElementType.Header)
|
||||
.AddText($"{member.Race} {member.Class}", "#CCCCCC", PopupElementType.Text)
|
||||
.AddText($"Role: {member.Role}", PopupElementType.Text)
|
||||
.AddSeparator(PopupElementType.Separator);
|
||||
|
||||
// Stats
|
||||
if(member.Stats?.stats != null) {
|
||||
var level = member.Stats.GetValue(StatType.Level);
|
||||
var xp = member.Stats.GetValue(StatType.Experience);
|
||||
var health = member.Stats.GetValue(StatType.Health);
|
||||
var mana = member.Stats.GetValue(StatType.Mana);
|
||||
builder
|
||||
.AddNameValue("Level", level, PopupElementType.LabelValueText)
|
||||
.AddNameValue("XP", xp, PopupElementType.LabelValueText)
|
||||
.AddSeparator(PopupElementType.Separator)
|
||||
.AddNameValue("Health", health, PopupElementType.LabelValueText)
|
||||
.AddNameValue("Mana", mana, PopupElementType.LabelValueText)
|
||||
.AddSeparator(PopupElementType.Separator);
|
||||
}
|
||||
|
||||
// Attributes
|
||||
if(member.Attributes?.attributes != null) {
|
||||
foreach(var attr in member.Attributes.attributes) {
|
||||
if(attr.attribute == AttributeType.None) {
|
||||
continue;
|
||||
}
|
||||
builder.AddNameValue(attr.attribute.ToString(), attr.value, PopupElementType.LabelValueText);
|
||||
}
|
||||
}
|
||||
builder.AddSeparator(PopupElementType.Separator);
|
||||
|
||||
// Perks
|
||||
if(member.Perks?.perks is { Count: > 0 }) {
|
||||
builder.AddText("Perks", "#FFD700", PopupElementType.Text);
|
||||
foreach(var perk in member.Perks.perks) {
|
||||
builder.AddText($" {perk.Name}", PopupElementType.Text);
|
||||
}
|
||||
builder.AddSeparator(PopupElementType.Separator);
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
if(member.Modifiers?.modifiers is { Count: > 0 }) {
|
||||
builder.AddText("Modifiers", "#87CEEB", PopupElementType.Text);
|
||||
foreach(var mod in member.Modifiers.modifiers) {
|
||||
var target = mod.Target != null ? mod.Target.ToString() : "";
|
||||
builder.AddText($" {mod.Name} ({mod.Operation} {mod.Value} {target})", PopupElementType.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateSlotStats(PartyMemberSlot slot, CharacterDefinition member) {
|
||||
var health = member.Stats.GetValue(StatType.Health);
|
||||
var mana = member.Stats.GetValue(StatType.Mana);
|
||||
|
||||
if(slot.healthBar != null) {
|
||||
slot.healthBar.fillAmount = Mathf.Clamp01(health / 100f);
|
||||
}
|
||||
|
||||
if(slot.manaBar != null) {
|
||||
slot.manaBar.fillAmount = Mathf.Clamp01(mana / 100f);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
for(int i = 0; i < PoolSize; i++) {
|
||||
if(slotPool[i] != null) {
|
||||
Object.Destroy(slotPool[i].gameObject);
|
||||
slotPool[i] = null;
|
||||
}
|
||||
}
|
||||
activeCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/PartyGuiView.cs.meta
Normal file
2
Assets/Code/GameState/UI/PartyGuiView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a95c5f20aad5baf498f79d7d61049e06
|
||||
10
Assets/Code/GameState/UI/PartyMemberSlot.cs
Normal file
10
Assets/Code/GameState/UI/PartyMemberSlot.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class PartyMemberSlot : MonoBehaviour {
|
||||
public Image portrait;
|
||||
public Image healthBar;
|
||||
public Image manaBar;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/PartyMemberSlot.cs.meta
Normal file
2
Assets/Code/GameState/UI/PartyMemberSlot.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cc43dfd6e3e7d644a6cb462d8094164
|
||||
9
Assets/Code/GameState/UI/PauseMenuPrefabs.cs
Normal file
9
Assets/Code/GameState/UI/PauseMenuPrefabs.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Nox.Game.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.Game {
|
||||
[CreateAssetMenu(fileName = "PauseMenuPrefabs", menuName = "Nox/PauseMenuPrefabs")]
|
||||
public class PauseMenuPrefabs : ScenePrefabs {
|
||||
public PauseMenuReferences pauseMenuReferencesPrefab;
|
||||
}
|
||||
}
|
||||
3
Assets/Code/GameState/UI/PauseMenuPrefabs.cs.meta
Normal file
3
Assets/Code/GameState/UI/PauseMenuPrefabs.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d58af5e98665467fb7d66e4822775e75
|
||||
timeCreated: 1772571122
|
||||
10
Assets/Code/GameState/UI/PauseMenuReferences.cs
Normal file
10
Assets/Code/GameState/UI/PauseMenuReferences.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class PauseMenuReferences : MonoBehaviour {
|
||||
public Button resumeButton;
|
||||
public Button exitButton;
|
||||
public Button saveButton;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/PauseMenuReferences.cs.meta
Normal file
2
Assets/Code/GameState/UI/PauseMenuReferences.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c881c3395da5194b926ef10f819edd7
|
||||
68
Assets/Code/GameState/UI/PauseMenuView.cs
Normal file
68
Assets/Code/GameState/UI/PauseMenuView.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Nox.Core;
|
||||
using Jovian.SaveSystem;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using Object = UnityEngine.Object;
|
||||
using PlayMode = Nox.Core.PlayMode;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class PauseMenuView: IMenuView {
|
||||
private readonly GameDataState gameDataState;
|
||||
private readonly ISaveSystem saveSystem;
|
||||
private readonly Func<NoxSavedDataSet?> captureSaveData;
|
||||
private PauseMenuReferences? pauseMenu;
|
||||
private PauseMenuPrefabs? pauseMenuPrefabs;
|
||||
|
||||
public PauseMenuView(GameDataState gameDataState, ISaveSystem saveSystem, Func<NoxSavedDataSet?> captureSaveData) {
|
||||
this.gameDataState = gameDataState;
|
||||
this.saveSystem = saveSystem;
|
||||
this.captureSaveData = captureSaveData;
|
||||
pauseMenu ??= Object.FindFirstObjectByType<PauseMenuReferences>();
|
||||
if(pauseMenu) {
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
pauseMenuPrefabs ??= Addressables.LoadAssetAsync<PauseMenuPrefabs>("PauseMenuPrefabs").WaitForCompletion();
|
||||
if(!pauseMenu) {
|
||||
pauseMenu = Object.Instantiate(pauseMenuPrefabs.pauseMenuReferencesPrefab);
|
||||
pauseMenu?.resumeButton.onClick.AddListener(() => {gameDataState.ChangePlayMode(gameDataState.PreviousPlayMode);});
|
||||
pauseMenu?.exitButton.onClick.AddListener(() => {
|
||||
OnSaveRequested();
|
||||
gameDataState.ChangeGameState(GameState.MainMenu);
|
||||
});
|
||||
pauseMenu?.saveButton.onClick.AddListener(OnSaveRequested);
|
||||
}
|
||||
Show();
|
||||
}
|
||||
|
||||
private void OnSaveRequested() {
|
||||
if(string.IsNullOrEmpty(gameDataState.activeSessionId)) {
|
||||
Debug.LogWarning("[PauseMenuView] No active session. Cannot save.");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = captureSaveData();
|
||||
if(data == null) {
|
||||
Debug.LogWarning("[PauseMenuView] No save data to capture.");
|
||||
return;
|
||||
}
|
||||
|
||||
saveSystem.Save(gameDataState.activeSessionId, data, SaveSlotType.Auto);
|
||||
}
|
||||
|
||||
public void Show() {
|
||||
pauseMenu?.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide() {
|
||||
pauseMenu?.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void Tick() { }
|
||||
}
|
||||
|
||||
}
|
||||
3
Assets/Code/GameState/UI/PauseMenuView.cs.meta
Normal file
3
Assets/Code/GameState/UI/PauseMenuView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83affb8078c54010ab278ed4be7f15ef
|
||||
timeCreated: 1772373065
|
||||
8
Assets/Code/GameState/UI/PortraitsHolder.cs
Normal file
8
Assets/Code/GameState/UI/PortraitsHolder.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Nox.UI {
|
||||
[CreateAssetMenu(fileName = "PortraitsHolder", menuName = "Nox/Database/UI/PortraitsHolder")]
|
||||
public class PortraitsHolder : ScriptableObject {
|
||||
public Sprite[] portraits;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/PortraitsHolder.cs.meta
Normal file
2
Assets/Code/GameState/UI/PortraitsHolder.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a49ac7886b19d34e9ecb10b8543bf9f
|
||||
110
Assets/Code/GameState/UI/ScreenFadeTransition.cs
Normal file
110
Assets/Code/GameState/UI/ScreenFadeTransition.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using Nox.Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.Game.UI {
|
||||
public class ScreenFadeTransition : MonoBehaviour, ISceneTransition {
|
||||
private enum FadeState {
|
||||
Idle,
|
||||
FadingOut,
|
||||
FadingIn
|
||||
}
|
||||
|
||||
private const float DefaultFadeDuration = 0.3f;
|
||||
private const int CanvasSortOrder = 999;
|
||||
|
||||
private CanvasGroup canvasGroup;
|
||||
private FadeState fadeState = FadeState.Idle;
|
||||
private float fadeTimer;
|
||||
private float fadeDuration = DefaultFadeDuration;
|
||||
private Action onFadeComplete;
|
||||
|
||||
public bool IsTransitioning => fadeState != FadeState.Idle;
|
||||
|
||||
public void Awake() {
|
||||
DontDestroyOnLoad(gameObject);
|
||||
CreateFadeCanvas();
|
||||
canvasGroup.alpha = 0f;
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
|
||||
public void FadeOut(Action onComplete = null) {
|
||||
if(fadeState == FadeState.FadingOut) {
|
||||
return;
|
||||
}
|
||||
onFadeComplete = onComplete;
|
||||
fadeState = FadeState.FadingOut;
|
||||
fadeTimer = 0f;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
public void FadeIn(Action onComplete = null) {
|
||||
if(fadeState == FadeState.FadingIn) {
|
||||
return;
|
||||
}
|
||||
onFadeComplete = onComplete;
|
||||
fadeState = FadeState.FadingIn;
|
||||
fadeTimer = 0f;
|
||||
}
|
||||
|
||||
public void Update() {
|
||||
if(fadeState == FadeState.Idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
fadeTimer += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(fadeTimer / fadeDuration);
|
||||
|
||||
switch(fadeState) {
|
||||
case FadeState.FadingOut:
|
||||
canvasGroup.alpha = t;
|
||||
if(t >= 1f) {
|
||||
CompleteFade();
|
||||
}
|
||||
break;
|
||||
case FadeState.FadingIn:
|
||||
canvasGroup.alpha = 1f - t;
|
||||
if(t >= 1f) {
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
CompleteFade();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void CompleteFade() {
|
||||
fadeState = FadeState.Idle;
|
||||
fadeTimer = 0f;
|
||||
var callback = onFadeComplete;
|
||||
onFadeComplete = null;
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
private void CreateFadeCanvas() {
|
||||
var canvasObject = new GameObject("FadeCanvas");
|
||||
canvasObject.transform.SetParent(transform);
|
||||
|
||||
var canvas = canvasObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = CanvasSortOrder;
|
||||
|
||||
canvasObject.AddComponent<CanvasScaler>();
|
||||
|
||||
canvasGroup = canvasObject.AddComponent<CanvasGroup>();
|
||||
|
||||
var imageObject = new GameObject("FadeImage");
|
||||
imageObject.transform.SetParent(canvasObject.transform, false);
|
||||
|
||||
var image = imageObject.AddComponent<Image>();
|
||||
image.color = Color.black;
|
||||
image.raycastTarget = true;
|
||||
|
||||
var rectTransform = image.rectTransform;
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.offsetMin = Vector2.zero;
|
||||
rectTransform.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/ScreenFadeTransition.cs.meta
Normal file
2
Assets/Code/GameState/UI/ScreenFadeTransition.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64d1d810d0e4d0d45bebf3cc276703af
|
||||
11
Assets/Code/GameState/UI/StatReference.cs
Normal file
11
Assets/Code/GameState/UI/StatReference.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Nox.UI {
|
||||
public class StatReference : MonoBehaviour {
|
||||
public TextMeshProUGUI statName;
|
||||
public TextMeshProUGUI statValue;
|
||||
public Image statBar;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/GameState/UI/StatReference.cs.meta
Normal file
2
Assets/Code/GameState/UI/StatReference.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e5d16e1d2077f948bd891ec5b3a40c4
|
||||
Reference in New Issue
Block a user