From 678c1e13ea6706a67b86554c1863a2111b9a005e Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 16:44:51 -0600 Subject: [PATCH 01/11] Conditional check data structures --- .../External/Configs/StarSystemConfig.cs | 6 ++ .../Conditionals/ConditionalCheckInfo.cs | 86 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 NewHorizons/External/Modules/Conditionals/ConditionalCheckInfo.cs diff --git a/NewHorizons/External/Configs/StarSystemConfig.cs b/NewHorizons/External/Configs/StarSystemConfig.cs index 08d0d968..d2afdfd2 100644 --- a/NewHorizons/External/Configs/StarSystemConfig.cs +++ b/NewHorizons/External/Configs/StarSystemConfig.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Xml; using NewHorizons.External.Modules; +using NewHorizons.External.Modules.Conditionals; using NewHorizons.External.SerializableData; using Newtonsoft.Json; using static NewHorizons.External.Modules.ShipLogModule; @@ -155,6 +156,11 @@ namespace NewHorizons.External.Configs /// public CuriosityColorInfo[] curiosities; + /// + /// A list of conditional checks to be performed while in this star system. + /// + public ConditionalCheckInfo[] conditionalChecks; + /// /// Extra data that may be used by extension mods /// diff --git a/NewHorizons/External/Modules/Conditionals/ConditionalCheckInfo.cs b/NewHorizons/External/Modules/Conditionals/ConditionalCheckInfo.cs new file mode 100644 index 00000000..c80a0728 --- /dev/null +++ b/NewHorizons/External/Modules/Conditionals/ConditionalCheckInfo.cs @@ -0,0 +1,86 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Conditionals +{ + [JsonObject] + public class ConditionalCheckInfo + { + /// + /// The conditions that must be met for the check to pass + /// + public ConditionalCheckConditionsInfo check; + /// + /// The effects of the check if it passes + /// + public ConditionalCheckEffectsInfo then; + } + + [JsonObject] + public class ConditionalCheckConditionsInfo + { + /// + /// The check will only pass if all of these dialogue conditions are set + /// + public string[] allConditionsSet; + /// + /// The check will only pass if any of these dialogue conditions are set + /// + public string[] anyConditionsSet; + /// + /// The check will only pass if all of these persistent conditions are set + /// + public string[] allPersistentConditionsSet; + /// + /// The check will only pass if any of these persistent conditions are set + /// + public string[] anyPersistentConditionsSet; + /// + /// The check will only pass if all of these ship log facts are revealed + /// + public string[] allFactsRevealed; + /// + /// The check will only pass if any of these ship log facts are revealed + /// + public string[] anyFactsRevealed; + + /// + /// If the check should pass only if the conditions are not met + /// + public bool invert; + } + + [JsonObject] + public class ConditionalCheckEffectsInfo + { + /// + /// The check will set these dialogue conditions if it passes + /// + public string[] setConditions; + /// + /// The check will unset these dialogue conditions if it passes + /// + public string[] unsetConditions; + /// + /// The check will set these persistent conditions if it passes + /// + public string[] setPersistentConditions; + /// + /// The check will unset these persistent conditions if it passes + /// + public string[] unsetPersistentConditions; + /// + /// The check will reveal these ship log facts if it passes + /// + public string[] revealFacts; + + /// + /// If the check should undo its effects if the conditions are not met anymore (unset the set conditions, etc.). Note: ship log facts cannot currently be unrevealed. + /// + public bool reversible; + } +} From ad3c18517e808cff1d30d95bd290f1fee8639983 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 16:45:17 -0600 Subject: [PATCH 02/11] Helper methods to calculate and apply checks --- NewHorizons/Handlers/ConditionalsHandler.cs | 103 ++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 NewHorizons/Handlers/ConditionalsHandler.cs diff --git a/NewHorizons/Handlers/ConditionalsHandler.cs b/NewHorizons/Handlers/ConditionalsHandler.cs new file mode 100644 index 00000000..a53263d4 --- /dev/null +++ b/NewHorizons/Handlers/ConditionalsHandler.cs @@ -0,0 +1,103 @@ +using NewHorizons.External.Modules.Conditionals; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.Handlers +{ + public static class ConditionalsHandler + { + public static bool Check(ConditionalCheckConditionsInfo check) + { + var dcm = DialogueConditionManager.SharedInstance; + + var passed = true; + if (check.allConditionsSet != null && check.allConditionsSet.Length > 0) + { + passed = passed && check.allConditionsSet.All(c => dcm.GetConditionState(c)); + } + if (check.anyConditionsSet != null && check.anyConditionsSet.Length > 0) + { + passed = passed && check.anyConditionsSet.Any(c => dcm.GetConditionState(c)); + } + if (check.allPersistentConditionsSet != null && check.allPersistentConditionsSet.Length > 0) + { + passed = passed && check.allPersistentConditionsSet.All(c => PlayerData.GetPersistentCondition(c)); + } + if (check.anyPersistentConditionsSet != null && check.anyPersistentConditionsSet.Length > 0) + { + passed = passed && check.anyPersistentConditionsSet.Any(c => PlayerData.GetPersistentCondition(c)); + } + if (check.allFactsRevealed != null && check.allFactsRevealed.Length > 0) + { + passed = passed && check.allFactsRevealed.All(f => ShipLogHandler.KnowsFact(f)); + } + if (check.anyFactsRevealed != null && check.anyFactsRevealed.Length > 0) + { + passed = passed && check.anyFactsRevealed.Any(f => ShipLogHandler.KnowsFact(f)); + } + if (check.invert) + { + passed = !passed; + } + + return passed; + } + + public static void ApplyEffects(ConditionalCheckEffectsInfo effects, bool checkPassed) + { + if ((checkPassed || effects.reversible) && effects.setConditions != null) + { + foreach (var condition in effects.setConditions) + { + if (DialogueConditionManager.SharedInstance.GetConditionState(condition) != checkPassed) + { + DialogueConditionManager.SharedInstance.SetConditionState(condition, checkPassed); + } + } + } + else if ((!checkPassed || effects.reversible) && effects.unsetConditions != null) + { + foreach (var condition in effects.unsetConditions) + { + if (DialogueConditionManager.SharedInstance.GetConditionState(condition) != !checkPassed) + { + DialogueConditionManager.SharedInstance.SetConditionState(condition, !checkPassed); + } + } + } + if ((checkPassed || effects.reversible) && effects.setPersistentConditions != null) + { + foreach (var condition in effects.setPersistentConditions) + { + if (!PlayerData.GetPersistentCondition(condition) != checkPassed) + { + PlayerData.SetPersistentCondition(condition, checkPassed); + } + } + } + else if ((!checkPassed || effects.reversible) && effects.unsetPersistentConditions != null) + { + foreach (var condition in effects.unsetPersistentConditions) + { + if (PlayerData.GetPersistentCondition(condition) != !checkPassed) + { + PlayerData.SetPersistentCondition(condition, !checkPassed); + } + } + } + if (checkPassed && effects.revealFacts != null) + { + foreach (var fact in effects.revealFacts) + { + if (!ShipLogHandler.KnowsFact(fact)) + { + Locator.GetShipLogManager().RevealFact(fact); + } + } + } + } + } +} From 27ff63d400a662ffeb0ca5e883d026fa0c5f324b Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 16:46:47 -0600 Subject: [PATCH 03/11] Component to process conditional checks --- .../Conditionals/ConditionalsManager.cs | 83 +++++++++++++++++++ .../PlayerPatches/PlayerDataPatches.cs | 8 ++ 2 files changed, 91 insertions(+) create mode 100644 NewHorizons/Components/Conditionals/ConditionalsManager.cs diff --git a/NewHorizons/Components/Conditionals/ConditionalsManager.cs b/NewHorizons/Components/Conditionals/ConditionalsManager.cs new file mode 100644 index 00000000..0f301e7b --- /dev/null +++ b/NewHorizons/Components/Conditionals/ConditionalsManager.cs @@ -0,0 +1,83 @@ +using NewHorizons.External.Modules.Conditionals; +using NewHorizons.Handlers; +using NewHorizons.Utility.OWML; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Components.Conditionals +{ + public class ConditionalsManager : MonoBehaviour + { + public const int MAX_RECURSION = 100; + + List _checks = new(); + int _recursionCount = 0; + bool _avoidRecursionLogSpam = false; + + public void AddCheck(ConditionalCheckInfo check) + { + _checks.Add(check); + } + + public void RemoveCheck(ConditionalCheckInfo check) + { + _checks.Remove(check); + } + + protected void Awake() + { + GlobalMessenger.AddListener("DialogueConditionsReset", CalculateChecks); + GlobalMessenger.AddListener("DialogueConditionChanged", CalculateChecks); + GlobalMessenger.AddListener("NHPersistentConditionChanged", CalculateChecks); + GlobalMessenger.AddListener("ShipLogUpdated", CalculateChecks); + } + + protected void OnDestroy() + { + GlobalMessenger.RemoveListener("DialogueConditionsReset", CalculateChecks); + GlobalMessenger.RemoveListener("DialogueConditionChanged", CalculateChecks); + GlobalMessenger.RemoveListener("NHPersistentConditionChanged", CalculateChecks); + GlobalMessenger.RemoveListener("ShipLogUpdated", CalculateChecks); + } + + protected void Update() + { + _recursionCount = 0; + enabled = false; + } + + void CalculateChecks() + { + if (_recursionCount >= MAX_RECURSION) + { + if (!_avoidRecursionLogSpam) + { + NHLogger.LogError($"Possible infinite loop detected while processing conditional checks. This is likely caused by a mod using conflicting conditional checks that both set and unset the same condition."); + _avoidRecursionLogSpam = true; + } + return; + } + + foreach (var check in _checks) + { + bool checkPassed = ConditionalsHandler.Check(check.check); + if (checkPassed) + { + ConditionalsHandler.ApplyEffects(check.then, checkPassed); + } + } + _recursionCount++; + enabled = true; + } + + // We could theoretically filter checks by conditionName here and only do the checks that matter, but the performance gain is likely not significant enough to be worth the extra complexity + void CalculateChecks(string conditionName, bool conditionState) + { + CalculateChecks(); + } + } +} diff --git a/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs b/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs index 6785a8a2..bcc352a9 100644 --- a/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs +++ b/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs @@ -143,5 +143,13 @@ namespace NewHorizons.Patches.PlayerPatches { NewHorizonsData.Save(); } + + [HarmonyPostfix] + [HarmonyPatch(nameof(PlayerData.SetPersistentCondition))] + public static void PlayerData_SetPersistentCondition(string condition, bool state) + { + // Firing off a custom event for the Conditionals system to use. This could've been done with direct calls or a Unity event but it felt cleaner to mirror the vanilla "DialogueConditionChanged" event. + GlobalMessenger.FireEvent("NHPersistentConditionChanged", condition, state); + } } } From 25f36dcbb72e4eecdb32acb7ff570024bd2428fc Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 16:50:59 -0600 Subject: [PATCH 04/11] Handle merging conditionals lists in config --- NewHorizons/External/Configs/StarSystemConfig.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NewHorizons/External/Configs/StarSystemConfig.cs b/NewHorizons/External/Configs/StarSystemConfig.cs index d2afdfd2..257d98e3 100644 --- a/NewHorizons/External/Configs/StarSystemConfig.cs +++ b/NewHorizons/External/Configs/StarSystemConfig.cs @@ -389,6 +389,15 @@ namespace NewHorizons.External.Configs GlobalMusic ??= otherConfig.GlobalMusic; } + if (conditionalChecks != null && otherConfig.conditionalChecks != null) + { + conditionalChecks = Concatenate(conditionalChecks, otherConfig.conditionalChecks); + } + else + { + conditionalChecks ??= otherConfig.conditionalChecks; + } + entryPositions = Concatenate(entryPositions, otherConfig.entryPositions); curiosities = Concatenate(curiosities, otherConfig.curiosities); initialReveal = Concatenate(initialReveal, otherConfig.initialReveal); From 327058042283df3880c058ac638989188d645cfe Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 16:51:13 -0600 Subject: [PATCH 05/11] Generate conditionals handler --- NewHorizons/Handlers/ConditionalsHandler.cs | 12 ++++++------ NewHorizons/Handlers/SystemCreationHandler.cs | 11 ++++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/NewHorizons/Handlers/ConditionalsHandler.cs b/NewHorizons/Handlers/ConditionalsHandler.cs index a53263d4..90392fa8 100644 --- a/NewHorizons/Handlers/ConditionalsHandler.cs +++ b/NewHorizons/Handlers/ConditionalsHandler.cs @@ -16,27 +16,27 @@ namespace NewHorizons.Handlers var passed = true; if (check.allConditionsSet != null && check.allConditionsSet.Length > 0) { - passed = passed && check.allConditionsSet.All(c => dcm.GetConditionState(c)); + passed = passed && check.allConditionsSet.All(dcm.GetConditionState); } if (check.anyConditionsSet != null && check.anyConditionsSet.Length > 0) { - passed = passed && check.anyConditionsSet.Any(c => dcm.GetConditionState(c)); + passed = passed && check.anyConditionsSet.Any(dcm.GetConditionState); } if (check.allPersistentConditionsSet != null && check.allPersistentConditionsSet.Length > 0) { - passed = passed && check.allPersistentConditionsSet.All(c => PlayerData.GetPersistentCondition(c)); + passed = passed && check.allPersistentConditionsSet.All(PlayerData.GetPersistentCondition); } if (check.anyPersistentConditionsSet != null && check.anyPersistentConditionsSet.Length > 0) { - passed = passed && check.anyPersistentConditionsSet.Any(c => PlayerData.GetPersistentCondition(c)); + passed = passed && check.anyPersistentConditionsSet.Any(PlayerData.GetPersistentCondition); } if (check.allFactsRevealed != null && check.allFactsRevealed.Length > 0) { - passed = passed && check.allFactsRevealed.All(f => ShipLogHandler.KnowsFact(f)); + passed = passed && check.allFactsRevealed.All(ShipLogHandler.KnowsFact); } if (check.anyFactsRevealed != null && check.anyFactsRevealed.Length > 0) { - passed = passed && check.anyFactsRevealed.Any(f => ShipLogHandler.KnowsFact(f)); + passed = passed && check.anyFactsRevealed.Any(ShipLogHandler.KnowsFact); } if (check.invert) { diff --git a/NewHorizons/Handlers/SystemCreationHandler.cs b/NewHorizons/Handlers/SystemCreationHandler.cs index db2951a3..8d8a4fdd 100644 --- a/NewHorizons/Handlers/SystemCreationHandler.cs +++ b/NewHorizons/Handlers/SystemCreationHandler.cs @@ -9,7 +9,7 @@ using UnityEngine; using Object = UnityEngine.Object; using NewHorizons.OtherMods; using NewHorizons.Components.EOTE; -using Epic.OnlineServices.Presence; +using NewHorizons.Components.Conditionals; namespace NewHorizons.Handlers { @@ -32,6 +32,15 @@ namespace NewHorizons.Handlers SkyboxBuilder.Make(system.Config.Skybox, system.Mod); } + if (system.Config.conditionalChecks != null) + { + var conditionalsManager = new GameObject("ConditionalsManager").AddComponent(); + foreach (var check in system.Config.conditionalChecks) + { + conditionalsManager.AddCheck(check); + } + } + // No time loop or travel audio at the eye if (Main.Instance.CurrentStarSystem == "EyeOfTheUniverse") return; From bbb25d99f641bcaa24cadb9aa6a527af679e869c Mon Sep 17 00:00:00 2001 From: Ben C Date: Sun, 16 Feb 2025 22:53:14 +0000 Subject: [PATCH 06/11] Updated Schemas --- NewHorizons/Schemas/star_system_schema.json | 118 ++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json index d1466958..048bb732 100644 --- a/NewHorizons/Schemas/star_system_schema.json +++ b/NewHorizons/Schemas/star_system_schema.json @@ -110,6 +110,13 @@ "$ref": "#/definitions/CuriosityColorInfo" } }, + "conditionalChecks": { + "type": "array", + "description": "A list of conditional checks to be performed while in this star system.", + "items": { + "$ref": "#/definitions/ConditionalCheckInfo" + } + }, "extras": { "type": "object", "description": "Extra data that may be used by extension mods", @@ -451,6 +458,117 @@ "minimum": 0.0 } } + }, + "ConditionalCheckInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "check": { + "description": "The conditions that must be met for the check to pass", + "$ref": "#/definitions/ConditionalCheckConditionsInfo" + }, + "then": { + "description": "The effects of the check if it passes", + "$ref": "#/definitions/ConditionalCheckEffectsInfo" + } + } + }, + "ConditionalCheckConditionsInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "allConditionsSet": { + "type": "array", + "description": "The check will only pass if all of these dialogue conditions are set", + "items": { + "type": "string" + } + }, + "anyConditionsSet": { + "type": "array", + "description": "The check will only pass if any of these dialogue conditions are set", + "items": { + "type": "string" + } + }, + "allPersistentConditionsSet": { + "type": "array", + "description": "The check will only pass if all of these persistent conditions are set", + "items": { + "type": "string" + } + }, + "anyPersistentConditionsSet": { + "type": "array", + "description": "The check will only pass if any of these persistent conditions are set", + "items": { + "type": "string" + } + }, + "allFactsRevealed": { + "type": "array", + "description": "The check will only pass if all of these ship log facts are revealed", + "items": { + "type": "string" + } + }, + "anyFactsRevealed": { + "type": "array", + "description": "The check will only pass if any of these ship log facts are revealed", + "items": { + "type": "string" + } + }, + "invert": { + "type": "boolean", + "description": "If the check should pass only if the conditions are not met" + } + } + }, + "ConditionalCheckEffectsInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "setConditions": { + "type": "array", + "description": "The check will set these dialogue conditions if it passes", + "items": { + "type": "string" + } + }, + "unsetConditions": { + "type": "array", + "description": "The check will unset these dialogue conditions if it passes", + "items": { + "type": "string" + } + }, + "setPersistentConditions": { + "type": "array", + "description": "The check will set these persistent conditions if it passes", + "items": { + "type": "string" + } + }, + "unsetPersistentConditions": { + "type": "array", + "description": "The check will unset these persistent conditions if it passes", + "items": { + "type": "string" + } + }, + "revealFacts": { + "type": "array", + "description": "The check will reveal these ship log facts if it passes", + "items": { + "type": "string" + } + }, + "reversible": { + "type": "boolean", + "description": "If the check should undo its effects if the conditions are not met anymore (unset the set conditions, etc.). Note: ship log facts cannot currently be unrevealed." + } + } } }, "$docs": { From 8b49f94bd46b0392b520536f9f5778fabf6dbbeb Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 23:25:46 -0600 Subject: [PATCH 07/11] Fix null item audio types breaking custom items --- NewHorizons/Builder/Props/ItemBuilder.cs | 8 ++++++++ NewHorizons/Components/Props/NHItem.cs | 1 + NewHorizons/External/Modules/Props/Item/ItemInfo.cs | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/NewHorizons/Builder/Props/ItemBuilder.cs b/NewHorizons/Builder/Props/ItemBuilder.cs index 5d4dc7ba..f4992e8a 100644 --- a/NewHorizons/Builder/Props/ItemBuilder.cs +++ b/NewHorizons/Builder/Props/ItemBuilder.cs @@ -59,10 +59,18 @@ namespace NewHorizons.Builder.Props { item.PickupAudio = AudioTypeHandler.GetAudioType(info.pickupAudio, mod); } + else + { + item.PickupAudio = AudioType.ToolItemWarpCorePickUp; + } if (!string.IsNullOrEmpty(info.dropAudio)) { item.DropAudio = AudioTypeHandler.GetAudioType(info.dropAudio, mod); } + else + { + item.DropAudio = AudioType.ToolItemWarpCoreDrop; + } if (!string.IsNullOrEmpty(info.socketAudio)) { item.SocketAudio = AudioTypeHandler.GetAudioType(info.socketAudio, mod); diff --git a/NewHorizons/Components/Props/NHItem.cs b/NewHorizons/Components/Props/NHItem.cs index cdc783b5..98aed5d2 100644 --- a/NewHorizons/Components/Props/NHItem.cs +++ b/NewHorizons/Components/Props/NHItem.cs @@ -90,6 +90,7 @@ namespace NewHorizons.Components.Props void PlayCustomSound(AudioType audioType) { + if (audioType == AudioType.None) return; if (ItemBuilder.IsCustomItemType(ItemType)) { Locator.GetPlayerAudioController()._oneShotExternalSource.PlayOneShot(audioType); diff --git a/NewHorizons/External/Modules/Props/Item/ItemInfo.cs b/NewHorizons/External/Modules/Props/Item/ItemInfo.cs index 23253119..f837443a 100644 --- a/NewHorizons/External/Modules/Props/Item/ItemInfo.cs +++ b/NewHorizons/External/Modules/Props/Item/ItemInfo.cs @@ -62,21 +62,25 @@ namespace NewHorizons.External.Modules.Props.Item /// /// The audio to play when this item is picked up. Only applies to custom/non-vanilla item types. /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. + /// Defaults to "ToolItemWarpCorePickUp". Set to "None" to disable the sound entirely. /// public string pickupAudio; /// /// The audio to play when this item is dropped. Only applies to custom/non-vanilla item types. /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. + /// Defaults to "ToolItemWarpCoreDrop". Set to "None" to disable the sound entirely. /// public string dropAudio; /// /// The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types. /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. + /// Defaults to the pickup audio. Set to "None" to disable the sound entirely. /// public string socketAudio; /// /// The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types. /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. + /// Defaults to the drop audio. Set to "None" to disable the sound entirely. /// public string unsocketAudio; /// From dcbc834e933c4dfb178a4771e3c6a34e689e5b38 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 23:26:03 -0600 Subject: [PATCH 08/11] Fix removeComponents deleting NH item components --- NewHorizons/Builder/Props/DetailBuilder.cs | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs index a395e080..022acd74 100644 --- a/NewHorizons/Builder/Props/DetailBuilder.cs +++ b/NewHorizons/Builder/Props/DetailBuilder.cs @@ -209,24 +209,6 @@ namespace NewHorizons.Builder.Props } } - if (detail.item != null) - { - ItemBuilder.MakeItem(prop, go, sector, detail.item, mod); - isItem = true; - if (detail.hasPhysics) - { - NHLogger.LogWarning($"An item with the path {detail.path} has both '{nameof(DetailInfo.hasPhysics)}' and '{nameof(DetailInfo.item)}' set. This will usually result in undesirable behavior."); - } - } - - if (detail.itemSocket != null) - { - ItemBuilder.MakeSocket(prop, go, sector, detail.itemSocket); - } - - // Items should always be kept loaded else they will vanish in your hand as you leave the sector - if (isItem) detail.keepLoaded = true; - prop.transform.localScale = detail.stretch ?? (detail.scale != 0 ? Vector3.one * detail.scale : prefab.transform.localScale); if (detail.removeChildren != null) @@ -271,11 +253,29 @@ namespace NewHorizons.Builder.Props prop = newDetailGO; } + if (detail.item != null) + { + ItemBuilder.MakeItem(prop, go, sector, detail.item, mod); + isItem = true; + if (detail.hasPhysics) + { + NHLogger.LogWarning($"An item with the path {detail.path} has both '{nameof(DetailInfo.hasPhysics)}' and '{nameof(DetailInfo.item)}' set. This will usually result in undesirable behavior."); + } + } + + if (detail.itemSocket != null) + { + ItemBuilder.MakeSocket(prop, go, sector, detail.itemSocket); + } + if (isItem) { // Else when you put them down you can't pick them back up var col = prop.GetComponent(); if (col != null) col._physicsRemoved = false; + + // Items should always be kept loaded else they will vanish in your hand as you leave the sector + detail.keepLoaded = true; } if (!detail.keepLoaded) GroupsBuilder.Make(prop, sector); From b1dbbf2eea38afbc026e11b25d6ad57388a5c696 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sun, 16 Feb 2025 23:26:26 -0600 Subject: [PATCH 09/11] Run conditional checks in LateUpdate() instead of immediately --- .../Conditionals/ConditionalsManager.cs | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/NewHorizons/Components/Conditionals/ConditionalsManager.cs b/NewHorizons/Components/Conditionals/ConditionalsManager.cs index 0f301e7b..0d0e74f8 100644 --- a/NewHorizons/Components/Conditionals/ConditionalsManager.cs +++ b/NewHorizons/Components/Conditionals/ConditionalsManager.cs @@ -12,9 +12,10 @@ namespace NewHorizons.Components.Conditionals { public class ConditionalsManager : MonoBehaviour { - public const int MAX_RECURSION = 100; + public const int MAX_RECURSION = 120; List _checks = new(); + bool _checksScheduled; int _recursionCount = 0; bool _avoidRecursionLogSpam = false; @@ -30,33 +31,42 @@ namespace NewHorizons.Components.Conditionals protected void Awake() { - GlobalMessenger.AddListener("DialogueConditionsReset", CalculateChecks); - GlobalMessenger.AddListener("DialogueConditionChanged", CalculateChecks); - GlobalMessenger.AddListener("NHPersistentConditionChanged", CalculateChecks); - GlobalMessenger.AddListener("ShipLogUpdated", CalculateChecks); + GlobalMessenger.AddListener("DialogueConditionsReset", ScheduleChecks); + GlobalMessenger.AddListener("DialogueConditionChanged", ScheduleChecks); + GlobalMessenger.AddListener("NHPersistentConditionChanged", ScheduleChecks); + GlobalMessenger.AddListener("ShipLogUpdated", ScheduleChecks); } protected void OnDestroy() { - GlobalMessenger.RemoveListener("DialogueConditionsReset", CalculateChecks); - GlobalMessenger.RemoveListener("DialogueConditionChanged", CalculateChecks); - GlobalMessenger.RemoveListener("NHPersistentConditionChanged", CalculateChecks); - GlobalMessenger.RemoveListener("ShipLogUpdated", CalculateChecks); + GlobalMessenger.RemoveListener("DialogueConditionsReset", ScheduleChecks); + GlobalMessenger.RemoveListener("DialogueConditionChanged", ScheduleChecks); + GlobalMessenger.RemoveListener("NHPersistentConditionChanged", ScheduleChecks); + GlobalMessenger.RemoveListener("ShipLogUpdated", ScheduleChecks); } - protected void Update() + protected void LateUpdate() { - _recursionCount = 0; - enabled = false; + if (_checksScheduled) + { + _checksScheduled = false; + DoChecks(); + } + else + { + // We had a frame without any checks being scheduled, so reset the recursion count and disable Update() again + _recursionCount = 0; + enabled = false; + } } - void CalculateChecks() + void DoChecks() { if (_recursionCount >= MAX_RECURSION) { if (!_avoidRecursionLogSpam) { - NHLogger.LogError($"Possible infinite loop detected while processing conditional checks. This is likely caused by a mod using conflicting conditional checks that both set and unset the same condition."); + NHLogger.LogError($"Possible infinite loop detected while processing conditional checks; conditions were changed every single frame for {MAX_RECURSION} frames. This is likely caused by a mod using conflicting conditional checks that both set and unset the same condition."); _avoidRecursionLogSpam = true; } return; @@ -65,19 +75,25 @@ namespace NewHorizons.Components.Conditionals foreach (var check in _checks) { bool checkPassed = ConditionalsHandler.Check(check.check); - if (checkPassed) - { - ConditionalsHandler.ApplyEffects(check.then, checkPassed); - } + ConditionalsHandler.ApplyEffects(check.then, checkPassed); } _recursionCount++; + // Allow Update() to run + enabled = true; + } + + // We schedule checks for the end of the frame instead of doing them immediately because GlobalMessenger doesn't support recursion and will throw an error if we update a condition that fires off another GlobalMessenger event + void ScheduleChecks() + { + _checksScheduled = true; + // Allow Update() to run enabled = true; } // We could theoretically filter checks by conditionName here and only do the checks that matter, but the performance gain is likely not significant enough to be worth the extra complexity - void CalculateChecks(string conditionName, bool conditionState) + void ScheduleChecks(string conditionName, bool conditionState) { - CalculateChecks(); + ScheduleChecks(); } } } From bda6af32152b28bd5519bde74d7db0bb9166a5fd Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 17 Feb 2025 05:28:50 +0000 Subject: [PATCH 10/11] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index f5ed593d..82ee3dda 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -1152,19 +1152,19 @@ }, "pickupAudio": { "type": "string", - "description": "The audio to play when this item is picked up. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." + "description": "The audio to play when this item is picked up. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.\nDefaults to \"ToolItemWarpCorePickUp\". Set to \"None\" to disable the sound entirely." }, "dropAudio": { "type": "string", - "description": "The audio to play when this item is dropped. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." + "description": "The audio to play when this item is dropped. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.\nDefaults to \"ToolItemWarpCoreDrop\". Set to \"None\" to disable the sound entirely." }, "socketAudio": { "type": "string", - "description": "The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." + "description": "The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.\nDefaults to the pickup audio. Set to \"None\" to disable the sound entirely." }, "unsocketAudio": { "type": "string", - "description": "The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." + "description": "The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.\nDefaults to the drop audio. Set to \"None\" to disable the sound entirely." }, "pickupCondition": { "type": "string", From fec5ae1c92f1fd494530082c23b6d9f9b3b60de6 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Mon, 17 Feb 2025 11:58:37 -0600 Subject: [PATCH 11/11] Cache translated item name --- NewHorizons/Components/Props/NHItem.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Components/Props/NHItem.cs b/NewHorizons/Components/Props/NHItem.cs index 98aed5d2..90105887 100644 --- a/NewHorizons/Components/Props/NHItem.cs +++ b/NewHorizons/Components/Props/NHItem.cs @@ -20,6 +20,8 @@ namespace NewHorizons.Components.Props public bool ClearPickupConditionOnDrop; public string PickupFact; + string _translatedName; + public ItemType ItemType { get => _type; @@ -28,7 +30,11 @@ namespace NewHorizons.Components.Props public override string GetDisplayName() { - return TranslationHandler.GetTranslation(DisplayName, TranslationHandler.TextType.UI); + if (_translatedName == null) + { + _translatedName = TranslationHandler.GetTranslation(DisplayName, TranslationHandler.TextType.UI); + } + return _translatedName; } public override bool CheckIsDroppable()