From 8aea6f7eb3960dd7272545abe22bc32ce20f6394 Mon Sep 17 00:00:00 2001 From: Sebastian Bularca Date: Thu, 21 May 2026 23:49:28 +0200 Subject: [PATCH] Wired in the encounters triggering and saving --- Assets/Code/GameState/NoxSaveData.cs | 9 +- .../GameState/PlayModes/AdventurePlayMode.cs | 13 +- .../PlayModes/Encounters/EncounterHandler.cs | 113 +++++++++++++++--- .../PlayModes/PartyMovementHandler.cs | 11 +- .../Code/GameState/PlayModes/TimeHandler.cs | 1 + .../EncounterTables/TestQuestTable.asset | 4 +- .../Database/Game/ZoneData/South_Road.asset | 4 +- Assets/Prefabs/UI/EncounterReference.prefab | 20 ++-- .../Runtime/IEncounter.cs | 3 +- 9 files changed, 140 insertions(+), 38 deletions(-) diff --git a/Assets/Code/GameState/NoxSaveData.cs b/Assets/Code/GameState/NoxSaveData.cs index bfa1a50..20889e0 100644 --- a/Assets/Code/GameState/NoxSaveData.cs +++ b/Assets/Code/GameState/NoxSaveData.cs @@ -36,7 +36,14 @@ namespace Nox.Game { gameDataState.activeSessionId = latestSession.sessionId; gameDataState.savedPartyPosition = saveData.partyPosition.ToVector3(); gameDataState.ActiveParty = saveData.activeParty; - adventureData = saveData.adventureData; + adventureData.isPartyMoving = saveData.adventureData.isPartyMoving; + adventureData.isEncounterActive = saveData.adventureData.isEncounterActive; + adventureData.currentDay = saveData.adventureData.currentDay; + adventureData.suppliesAvailable = saveData.adventureData.suppliesAvailable; + adventureData.currentTime = saveData.adventureData.currentTime; + adventureData.currentDayPhase = saveData.adventureData.currentDayPhase; + adventureData.completedEncounterIds.Clear(); + adventureData.completedEncounterIds.AddRange(saveData.adventureData.completedEncounterIds); if(gameLogStore != null && saveData.gameLogData != null) { gameLogStore.RestoreFromSaveData(saveData.gameLogData); diff --git a/Assets/Code/GameState/PlayModes/AdventurePlayMode.cs b/Assets/Code/GameState/PlayModes/AdventurePlayMode.cs index 8fa8368..b680dc6 100644 --- a/Assets/Code/GameState/PlayModes/AdventurePlayMode.cs +++ b/Assets/Code/GameState/PlayModes/AdventurePlayMode.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Jovian.Calendar; using Jovian.EncounterSystem; using Jovian.PopupSystem; @@ -8,18 +9,23 @@ using Nox.Core; using Nox.Platform; using Nox.Game.UI; using Nox.UI; +using System; using UnityEngine; using UnityEngine.AddressableAssets; using ZLinq; +using Object = UnityEngine.Object; using PlayMode = Nox.Core.PlayMode; namespace Nox.Game { + [Serializable] public class AdventureData { public bool isPartyMoving; + public bool isEncounterActive; public int currentDay = 0; public int suppliesAvailable = -1; public float currentTime = -1f; public DayPhase currentDayPhase = DayPhase.Morning; + public List completedEncounterIds = new List(); } public class AdventurePlayMode : IPlayMode { @@ -115,12 +121,15 @@ namespace Nox.Game { partyInventoryHandler ??= new PartyInventoryHandler(adventureData, adventureSettings); partyInventoryHandler.Initialize(); - var calendarSettings = Addressables.LoadAssetAsync("CalendarSettings").WaitForCompletion(); + var calendarSettings = Addressables.LoadAssetAsync("CalendarSettings").WaitForCompletion(); var worldClock = new WorldClock(calendarSettings); + var totalMinutes = adventureData.currentDay * calendarSettings.MinutesPerDay + + (int)(adventureData.currentTime * calendarSettings.MinutesPerDay); + worldClock.AdvanceMinutes(totalMinutes); timeHandler ??= new TimeHandler(adventureSettings, adventureData, worldClock); zoneSystem ??= new ZoneSystem(mapRef.zonesObjectHolder); - encounterHandler = new EncounterHandler(zoneSystem, encounterRegistry, encounterPrefabs); + encounterHandler = new EncounterHandler(zoneSystem, encounterRegistry, encounterPrefabs, adventureData); partyMovementHandler ??= new PartyMovementHandler( partyRef, cameraController, diff --git a/Assets/Code/GameState/PlayModes/Encounters/EncounterHandler.cs b/Assets/Code/GameState/PlayModes/Encounters/EncounterHandler.cs index 3265ade..818e1ca 100644 --- a/Assets/Code/GameState/PlayModes/Encounters/EncounterHandler.cs +++ b/Assets/Code/GameState/PlayModes/Encounters/EncounterHandler.cs @@ -1,5 +1,6 @@ using Jovian.EncounterSystem; using Jovian.ZoneSystem; +using System; using UnityEngine; namespace Nox.Game { @@ -7,14 +8,19 @@ namespace Nox.Game { private readonly ZoneSystem zoneSystem; private readonly EncounterRegistry encounterRegistry; private readonly EncounterView encounterView; + private readonly AdventureData adventureData; + private Vector3 lastKnownPosition; private string previousZoneId; private IEncounter activeEncounter; + private int lastCheckedDay; - public EncounterHandler(ZoneSystem zoneSystem, EncounterRegistry encounterRegistry, EncounterPrefabs encounterPrefabs) { + public EncounterHandler(ZoneSystem zoneSystem, EncounterRegistry encounterRegistry, EncounterPrefabs encounterPrefabs, AdventureData adventureData) { this.zoneSystem = zoneSystem; this.encounterRegistry = encounterRegistry; + this.adventureData = adventureData; encounterView = new EncounterView(encounterPrefabs); encounterView.OptionSelected += OnOptionSelected; + lastCheckedDay = adventureData.currentDay; } public bool AskForRandomEncounter(ZoneContext zoneContext, string encounterTableId, out IEncounter encounter) { @@ -35,7 +41,7 @@ namespace Nox.Game { return false; } - var randomChance = Random.Range(0f, 1f); + var randomChance = UnityEngine.Random.Range(0f, 1f); var shouldTrigger = randomChance <= zoneContext.finalEncounterChance; if(!shouldTrigger) { @@ -43,29 +49,72 @@ namespace Nox.Game { return false; } - encounter = encounterKind == null - ? encounterRegistry.GetRandomEncounter(encounterTableId) - : encounterRegistry.GetRandomEncounter(encounterTableId, encounterKind); + var filter = new Predicate(e => e != null && !IsAlreadyCompleted(e)); - var resultName = encounter?.EncounterDefinition?.name ?? "none"; - Debug.Log($"Rolled for encounter '{encounterTableId}': {randomChance:F2}/{zoneContext.finalEncounterChance:F2} -> {resultName}"); + if(encounterKind == null) { + encounter = encounterRegistry.GetRandomEncounter(filter, encounterTableId); + } + else { + var table = encounterRegistry.GetEncounterTable(encounterTableId); + if(table == null) { + return false; + } + + for(var attempt = 0; attempt <= table.encounters.Count; attempt++) { + encounter = encounterRegistry.GetRandomEncounter(encounterTableId, encounterKind); + if(encounter == null || !IsAlreadyCompleted(encounter)) { + break; + } + } + + if(encounter != null && IsAlreadyCompleted(encounter)) { + return false; + } + } + + var name = encounter?.EncounterDefinition?.name ?? "none"; + Debug.Log($"Rolled for encounter '{encounterTableId}': {randomChance:F2}/{zoneContext.finalEncounterChance:F2} -> {name}"); return encounter != null; } - private void VerifyZones(Vector3 position) { - var zoneContext = zoneSystem.QueryZone(position); - if(string.IsNullOrEmpty(zoneContext.resolvedZoneId)) { + private bool IsAlreadyCompleted(IEncounter encounter) { + var def = encounter?.EncounterDefinition; + if(def == null || string.IsNullOrEmpty(def.id)) { + return false; + } + + // no properties → defaults to repeatable (true). So can't be "already completed". + if(encounter.EncounterProperties is not { isRepeatable: false }) { + return false; + } + + return adventureData.completedEncounterIds.Contains(def.id); + } + + private void RecordEncounterCompleted(IEncounter encounter) { + var def = encounter?.EncounterDefinition; + if(def == null || string.IsNullOrEmpty(def.id)) { return; } + + // only record non-repeatable encounters that have explicit properties + if(encounter.EncounterProperties is not { isRepeatable: false }) { + return; + } + + if(!adventureData.completedEncounterIds.Contains(def.id)) { + adventureData.completedEncounterIds.Add(def.id); + Debug.Log($"Recorded non-repeatable encounter: {def.id}"); + } + } + + private void TrackZoneTransition(Vector3 position) { + var zoneContext = zoneSystem.QueryZone(position); var currentZoneId = zoneContext.resolvedZoneId; if(currentZoneId != previousZoneId) { if(!string.IsNullOrEmpty(currentZoneId)) { Debug.Log($"Entered zone: {currentZoneId} (encounter: {zoneContext.encounterTableId}, safe: {zoneContext.isSafe})"); - if(ResolveEncounter(zoneContext, zoneContext.encounterTableId, out var encounter)) { - TriggerEncounter(encounter); - } - } else if(!string.IsNullOrEmpty(previousZoneId)) { Debug.Log($"Left zone: {previousZoneId}"); @@ -74,18 +123,36 @@ namespace Nox.Game { } } + private void DailyEncounterCheck(Vector3 position) { + var zoneContext = zoneSystem.QueryZone(position); + if(string.IsNullOrEmpty(zoneContext.resolvedZoneId) || zoneContext.isSafe) { + return; + } + if(ResolveEncounter(zoneContext, zoneContext.encounterTableId, out var encounter)) { + TriggerEncounter(encounter); + } + } + private void TriggerEncounter(IEncounter encounter) { switch(encounter.EncounterDefinition.Kind) { case CombatKind: return; default: activeEncounter = encounter; + adventureData.isEncounterActive = true; encounterView?.SetCurrentEncounter(encounter); encounterView?.Show(); break; } } + private void FinishEncounter() { + RecordEncounterCompleted(activeEncounter); + encounterView?.Hide(); + activeEncounter = null; + adventureData.isEncounterActive = false; + } + private void OnOptionSelected(int optionIndex) { if(activeEncounter == null) { return; @@ -93,16 +160,16 @@ namespace Nox.Game { var options = activeEncounter.EncounterDialogOptionSet?.options; if(options == null || optionIndex < 0 || optionIndex >= options.Count) { + FinishEncounter(); return; } ResolveOption(activeEncounter, options[optionIndex]); - encounterView?.Hide(); - activeEncounter = null; } private void ResolveOption(IEncounter encounter, EncounterDialogOption option) { if(option?.events == null) { + FinishEncounter(); return; } @@ -114,6 +181,7 @@ namespace Nox.Game { switch(encounterEvent) { case ChainToEncounterEvent chain: if(AskForEncounter(chain.nextEncounterId, out var next)) { + RecordEncounterCompleted(activeEncounter); TriggerEncounter(next); return; } @@ -127,13 +195,21 @@ namespace Nox.Game { break; } } + + FinishEncounter(); } public void CheckForEncounters(Vector3 position) { - VerifyZones(position); + lastKnownPosition = position; + TrackZoneTransition(position); } - public void Tick() { } + public void Tick() { + if(adventureData.currentDay != lastCheckedDay) { + lastCheckedDay = adventureData.currentDay; + DailyEncounterCheck(lastKnownPosition); + } + } public void Dispose() { if(encounterView != null) { @@ -141,6 +217,7 @@ namespace Nox.Game { encounterView.Dispose(); } activeEncounter = null; + adventureData.isEncounterActive = false; } } } diff --git a/Assets/Code/GameState/PlayModes/PartyMovementHandler.cs b/Assets/Code/GameState/PlayModes/PartyMovementHandler.cs index 37250fa..48db865 100644 --- a/Assets/Code/GameState/PlayModes/PartyMovementHandler.cs +++ b/Assets/Code/GameState/PlayModes/PartyMovementHandler.cs @@ -99,6 +99,12 @@ namespace Nox.Game { HandleClickableLocation(); } + if(adventureData.isEncounterActive) { + partyCanMove = false; + adventureData.isPartyMoving = false; + return; + } + if(!partyCanMove) { adventureData.isPartyMoving = false; return; @@ -111,11 +117,12 @@ namespace Nox.Game { } adventureData.isPartyMoving = true; - partyReference.transform.position = Vector3.MoveTowards( + var pos = Vector3.MoveTowards( new Vector3(partyReference.transform.position.x, 0, partyReference.transform.position.z), targetPosition, Time.deltaTime * adventureSettings.partyBaseSpeed); - encounterHandler?.CheckForEncounters(partyReference.transform.position); + partyReference.transform.position = pos; + encounterHandler?.CheckForEncounters(pos); } private void HandleHover() { diff --git a/Assets/Code/GameState/PlayModes/TimeHandler.cs b/Assets/Code/GameState/PlayModes/TimeHandler.cs index 36bbdb8..9f3e535 100644 --- a/Assets/Code/GameState/PlayModes/TimeHandler.cs +++ b/Assets/Code/GameState/PlayModes/TimeHandler.cs @@ -44,6 +44,7 @@ namespace Nox.Game { if(localTime >= adventureSettings.dayLength) { localTime -= adventureSettings.dayLength; + adventureData.currentDay++; } var normalized = localTime / adventureSettings.dayLength; diff --git a/Assets/Database/Encounters/EncounterTables/TestQuestTable.asset b/Assets/Database/Encounters/EncounterTables/TestQuestTable.asset index 4470fce..b8faaed 100644 --- a/Assets/Database/Encounters/EncounterTables/TestQuestTable.asset +++ b/Assets/Database/Encounters/EncounterTables/TestQuestTable.asset @@ -24,6 +24,7 @@ MonoBehaviour: rid: 1352971465325281417 k__BackingField: difficulty: 0 + isRepeatable: 0 k__BackingField: icon: {fileID: 21300000, guid: ea02ea44fa86ee445be0f7ca82098b75, type: 3} encounterColor: {r: 0, g: 0, b: 0, a: 0} @@ -38,6 +39,7 @@ MonoBehaviour: rid: 1352971465325281421 k__BackingField: difficulty: 0 + isRepeatable: 0 k__BackingField: icon: {fileID: 21300000, guid: ea02ea44fa86ee445be0f7ca82098b75, type: 3} encounterColor: {r: 0, g: 0, b: 0, a: 0} @@ -52,11 +54,9 @@ MonoBehaviour: nextEncounter: table: {fileID: 11400000} internalId: 882ecaa9-29d9-452e-aa88-beb533f97882 - questTitle: - rid: 1352971465325281421 type: {class: QuestKind, ns: Jovian.EncounterSystem, asm: Jovian.EncounterSystem} data: nextEncounter: table: {fileID: 0} internalId: - questTitle: diff --git a/Assets/Database/Game/ZoneData/South_Road.asset b/Assets/Database/Game/ZoneData/South_Road.asset index 5c975ba..b36e5ee 100644 --- a/Assets/Database/Game/ZoneData/South_Road.asset +++ b/Assets/Database/Game/ZoneData/South_Road.asset @@ -12,12 +12,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8497d766078e5764a9c7c0dd5d671561, type: 3} m_Name: South_Road m_EditorClassIdentifier: Jovian.ZoneSystem::Jovian.ZoneSystem.ZoneData - zoneId: adventure_generic_! + zoneId: adventure_generic_1 zoneName: South Road role: 0 priority: 10 debugColor: {r: 0, g: 0.7563188, b: 1, a: 0.25} - encounterTableId: Adventure_Random_Easy + encounterTableId: TestEncounterTable baseDifficultyTier: 1 baseEncounterChance: 1 encounterChanceMultiplier: 1 diff --git a/Assets/Prefabs/UI/EncounterReference.prefab b/Assets/Prefabs/UI/EncounterReference.prefab index b88a863..710ef79 100644 --- a/Assets/Prefabs/UI/EncounterReference.prefab +++ b/Assets/Prefabs/UI/EncounterReference.prefab @@ -169,12 +169,12 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 15.55 + m_fontSize: 18.2 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 m_fontSizeMin: 7.8 - m_fontSizeMax: 15.55 + m_fontSizeMax: 18.2 m_fontStyle: 0 m_HorizontalAlignment: 1 m_VerticalAlignment: 256 @@ -323,7 +323,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.8264123} m_AnchorMax: {x: 0.6627312, y: 1} - m_AnchoredPosition: {x: -0.80999756, y: -15.642975} + m_AnchoredPosition: {x: -0.80999756, y: -15.64296} m_SizeDelta: {x: 1.62, y: -33.7364} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &3458576624150439327 @@ -381,12 +381,12 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 45.4 + m_fontSize: 30.36 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 m_fontSizeMin: 13.94 - m_fontSizeMax: 45.4 + m_fontSizeMax: 30.36 m_fontStyle: 1 m_HorizontalAlignment: 2 m_VerticalAlignment: 512 @@ -572,12 +572,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} m_Name: m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler - m_UiScaleMode: 0 + m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 - m_ReferenceResolution: {x: 800, y: 600} + m_ReferenceResolution: {x: 1920, y: 1080} m_ScreenMatchMode: 0 - m_MatchWidthOrHeight: 0 + m_MatchWidthOrHeight: 0.5 m_PhysicalUnit: 3 m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 @@ -654,8 +654,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: -12.132874, y: 7.188507} - m_SizeDelta: {x: -534.61, y: -277.0435} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -1120.4478, y: -616.4321} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &7810011449154573601 CanvasRenderer: diff --git a/Packages/com.jovian.encounter-system/Runtime/IEncounter.cs b/Packages/com.jovian.encounter-system/Runtime/IEncounter.cs index 8f296e1..20d0e09 100644 --- a/Packages/com.jovian.encounter-system/Runtime/IEncounter.cs +++ b/Packages/com.jovian.encounter-system/Runtime/IEncounter.cs @@ -46,8 +46,9 @@ namespace Jovian.EncounterSystem { public Sprite encounterArt; } - [Serializable] + [Serializable] public class EncounterProperties { public int difficulty; + public bool isRepeatable = true; } }