diff --git a/NewHorizons/Assets/DefaultMapModeStar.png b/NewHorizons/Assets/DefaultMapModeStar.png index 7bc0ac1b..bdcedc5b 100644 Binary files a/NewHorizons/Assets/DefaultMapModeStar.png and b/NewHorizons/Assets/DefaultMapModeStar.png differ diff --git a/NewHorizons/Builder/Atmosphere/FogBuilder.cs b/NewHorizons/Builder/Atmosphere/FogBuilder.cs index 9f1d7da8..f47e3e29 100644 --- a/NewHorizons/Builder/Atmosphere/FogBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/FogBuilder.cs @@ -1,10 +1,10 @@ using NewHorizons.External.Modules; -using NewHorizons.External.Modules.Props; using NewHorizons.Utility; using NewHorizons.Utility.Files; using OWML.Common; using System; using UnityEngine; + namespace NewHorizons.Builder.Atmosphere { public static class FogBuilder @@ -26,7 +26,9 @@ namespace NewHorizons.Builder.Atmosphere internal static void InitPrefabs() { - if (_ramp == null) _ramp = ImageUtilities.GetTexture(Main.Instance, "Assets/textures/FogColorRamp.png"); + // Checking null here it was getting destroyed and wouldnt reload and never worked outside of the first loop + // GetTexture caches itself anyway so it doesn't matter that this gets called multiple times + _ramp = ImageUtilities.GetTexture(Main.Instance, "Assets/textures/FogColorRamp.png"); if (_isInit) return; @@ -73,6 +75,7 @@ namespace NewHorizons.Builder.Atmosphere atmo.fogRampPath != null ? ImageUtilities.GetTexture(mod, atmo.fogRampPath) : atmo.fogTint != null ? ImageUtilities.TintImage(_ramp, atmo.fogTint.ToColor()) : _ramp; + PFC.fogColorRampTexture = colorRampTexture; PFC.fogColorRampIntensity = 1f; if (atmo.fogTint != null) diff --git a/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs b/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs index df696f4d..9ee0e365 100644 --- a/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs @@ -1,6 +1,7 @@ using NewHorizons.External.Modules; using NewHorizons.External.Modules.VariableSize; using UnityEngine; + namespace NewHorizons.Builder.Atmosphere { public static class SunOverrideBuilder diff --git a/NewHorizons/Builder/Body/StarBuilder.cs b/NewHorizons/Builder/Body/StarBuilder.cs index cdb68c7e..32839346 100644 --- a/NewHorizons/Builder/Body/StarBuilder.cs +++ b/NewHorizons/Builder/Body/StarBuilder.cs @@ -408,10 +408,15 @@ namespace NewHorizons.Builder.Body if (starModule.endTint != null) { var endColour = starModule.endTint.ToColor(); - darkenedColor = new Color(endColour.r * modifier, endColour.g * modifier, endColour.b * modifier); + var adjustedEndColour = new Color(endColour.r * modifier, endColour.g * modifier, endColour.b * modifier); + Color.RGBToHSV(adjustedEndColour, out var hEnd, out var sEnd, out var vEnd); + var darkenedEndColor = Color.HSVToRGB(hEnd, sEnd * 1.2f, vEnd * 0.1f); + surface.sharedMaterial.SetTexture(ColorRamp, ImageUtilities.LerpGreyscaleImageAlongX(_colorOverTime, adjustedColour, darkenedColor, adjustedEndColour, darkenedEndColor)); + } + else + { + surface.sharedMaterial.SetTexture(ColorRamp, ImageUtilities.LerpGreyscaleImage(_colorOverTime, adjustedColour, darkenedColor)); } - - surface.sharedMaterial.SetTexture(ColorRamp, ImageUtilities.LerpGreyscaleImage(_colorOverTime, adjustedColour, darkenedColor)); } if (!string.IsNullOrEmpty(starModule.starRampTexture)) diff --git a/NewHorizons/Builder/Body/WaterBuilder.cs b/NewHorizons/Builder/Body/WaterBuilder.cs index 4a636c08..8eba73c0 100644 --- a/NewHorizons/Builder/Body/WaterBuilder.cs +++ b/NewHorizons/Builder/Body/WaterBuilder.cs @@ -135,7 +135,8 @@ namespace NewHorizons.Builder.Body var fogGO = Object.Instantiate(_oceanFog, waterGO.transform); fogGO.name = "OceanFog"; fogGO.transform.localPosition = Vector3.zero; - fogGO.transform.localScale = Vector3.one; + // In base game GD ocean fog is 550 while the water volume is 500 + fogGO.transform.localScale = Vector3.one * 550f / 500f; fogGO.SetActive(true); if (module.tint != null) diff --git a/NewHorizons/Builder/General/RFVolumeBuilder.cs b/NewHorizons/Builder/General/RFVolumeBuilder.cs index f359a7e0..f8fa8aa8 100644 --- a/NewHorizons/Builder/General/RFVolumeBuilder.cs +++ b/NewHorizons/Builder/General/RFVolumeBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.General { // We can't not build a reference frame volume, Cloak requires one to be there module.maxTargetDistance = 0f; + module.targetWhenClose = true; + module.targetColliderRadius = 0f; module.hideInMap = true; owrb.SetIsTargetable(false); } diff --git a/NewHorizons/Builder/Props/Audio/SignalBuilder.cs b/NewHorizons/Builder/Props/Audio/SignalBuilder.cs index 66a86061..b5fbfaf0 100644 --- a/NewHorizons/Builder/Props/Audio/SignalBuilder.cs +++ b/NewHorizons/Builder/Props/Audio/SignalBuilder.cs @@ -1,9 +1,11 @@ using HarmonyLib; +using NewHorizons.External; using NewHorizons.External.Modules.Props.Audio; using NewHorizons.Utility; using NewHorizons.Utility.OWML; using OWML.Common; using OWML.Utils; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -36,27 +38,16 @@ namespace NewHorizons.Builder.Props.Audio }; NumberOfFrequencies = EnumUtils.GetValues().Length; - _qmSignals = new (){ SearchUtilities.Find("QuantumMoon_Body/Signal_Quantum").GetComponent() }; + _qmSignals = new () { SearchUtilities.Find("QuantumMoon_Body/Signal_Quantum").GetComponent() }; _cloakedSignals = new(); Initialized = true; SceneManager.sceneUnloaded -= OnSceneUnloaded; SceneManager.sceneUnloaded += OnSceneUnloaded; - Main.Instance.OnStarSystemLoaded.RemoveListener(OnStarSystemLoaded); - Main.Instance.OnStarSystemLoaded.AddListener(OnStarSystemLoaded); - } - private static HashSet _frequenciesInUse = new(); - - private static void OnSceneUnloaded(Scene _) - { - _frequenciesInUse.Clear(); - } - - private static void OnStarSystemLoaded(string starSystem) - { // If its the base game solar system or eye we get all the main frequencies + var starSystem = Main.Instance.CurrentStarSystem; if (starSystem == "SolarSystem" || starSystem == "EyeOfTheUniverse") { _frequenciesInUse.Add(SignalFrequency.Quantum); @@ -69,19 +60,43 @@ namespace NewHorizons.Builder.Props.Audio // We don't want a scenario where the player knows no frequencies _frequenciesInUse.Add(SignalFrequency.Traveler); + // Make sure the NH save file has all the right frequencies + // Skip "default" + for (int i = 1; i < PlayerData._currentGameSave.knownFrequencies.Length; i++) + { + if (PlayerData._currentGameSave.knownFrequencies[i]) + { + NewHorizonsData.LearnFrequency(AudioSignal.IndexToFrequency(i).ToString()); + } + } + NHLogger.LogVerbose($"Frequencies in use in {starSystem}: {_frequenciesInUse.Join(x => x.ToString())}"); } + private static HashSet _frequenciesInUse = new(); + + private static void OnSceneUnloaded(Scene _) + { + _frequenciesInUse.Clear(); + } + public static bool IsFrequencyInUse(SignalFrequency freq) => _frequenciesInUse.Contains(freq); + public static bool IsFrequencyInUse(string freqString) + { + if (Enum.TryParse(freqString, out var freq)) + { + return IsFrequencyInUse(freq); + } + return false; + } + public static bool IsCloaked(this AudioSignal signal) => _cloakedSignals.Contains(signal); public static bool IsOnQuantumMoon(this AudioSignal signal) => _qmSignals.Contains(signal); public static SignalFrequency AddFrequency(string str) { - if (_customFrequencyNames == null) Init(); - var freq = CollectionUtilities.KeyByValue(_customFrequencyNames, str); if (freq != default) return freq; @@ -99,23 +114,19 @@ namespace NewHorizons.Builder.Props.Audio NumberOfFrequencies = EnumUtils.GetValues().Length; // This stuff happens after the signalscope is Awake so we have to change the number of frequencies now - Object.FindObjectOfType()._strongestSignals = new AudioSignal[NumberOfFrequencies + 1]; + GameObject.FindObjectOfType()._strongestSignals = new AudioSignal[NumberOfFrequencies + 1]; return freq; } public static string GetCustomFrequencyName(SignalFrequency frequencyName) { - if (_customFrequencyNames == null) Init(); - _customFrequencyNames.TryGetValue(frequencyName, out string name); return name; } public static SignalName AddSignalName(string str) { - if (_customSignalNames == null) Init(); - var name = CollectionUtilities.KeyByValue(_customSignalNames, str); if (name != default) return name; @@ -129,8 +140,6 @@ namespace NewHorizons.Builder.Props.Audio public static string GetCustomSignalName(SignalName signalName) { - if (_customSignalNames == null) Init(); - _customSignalNames.TryGetValue(signalName, out string name); return name; } diff --git a/NewHorizons/Builder/Props/BrambleNodeBuilder.cs b/NewHorizons/Builder/Props/BrambleNodeBuilder.cs index 3e5437e7..82e85a12 100644 --- a/NewHorizons/Builder/Props/BrambleNodeBuilder.cs +++ b/NewHorizons/Builder/Props/BrambleNodeBuilder.cs @@ -6,6 +6,7 @@ using NewHorizons.Handlers; using NewHorizons.Utility; using NewHorizons.Utility.OuterWilds; using NewHorizons.Utility.OWML; +using Newtonsoft.Json; using OWML.Common; using System.Collections.Generic; using System.Linq; @@ -33,12 +34,17 @@ namespace NewHorizons.Builder.Props private static GameObject _brambleSeedPrefab; private static GameObject _brambleNodePrefab; + private static HashSet _nhFogWarpVolumes = new(); + + public static bool IsNHFogWarpVolume(FogWarpVolume volume) => _nhFogWarpVolumes.Contains(volume); + public static void Init(PlanetConfig[] dimensionConfigs) { _unpairedNodes.Clear(); _propagatedSignals.Clear(); namedNodes.Clear(); builtBrambleNodes.Clear(); + _nhFogWarpVolumes.Clear(); PropagateSignals(dimensionConfigs); } @@ -190,6 +196,12 @@ namespace NewHorizons.Builder.Props collider.enabled = true; } + // We track all the fog warp volumes that NH created so we can only effect those in patches, this way we leave base game stuff alone. + foreach (var fogWarpVolume in brambleNode.GetComponentsInChildren(true).Append(brambleNode.GetComponent())) + { + _nhFogWarpVolumes.Add(fogWarpVolume); + } + var innerFogWarpVolume = brambleNode.GetComponent(); var outerFogWarpVolume = GetOuterFogWarpVolumeFromAstroObject(go); var fogLight = brambleNode.GetComponent(); @@ -239,6 +251,12 @@ namespace NewHorizons.Builder.Props foreach(Transform child in brambleNode.transform) { child.localScale = Vector3.one * config.scale; + + // The fog on bramble seeds has a specific scale we need to copy over + if (child.name == "VolumetricFogSphere (2)") + { + child.localScale *= 6.3809f; + } } innerFogWarpVolume._warpRadius *= config.scale; innerFogWarpVolume._exitRadius *= config.scale; @@ -397,7 +415,13 @@ namespace NewHorizons.Builder.Props { foreach (var signalConfig in connectedSignals) { - var signalGO = SignalBuilder.Make(go, sector, signalConfig, mod); + // Have to ensure that this new signal doesn't use parent path, else it looks for a parent that only exists on the original body + // Have to make a copy of it as well to avoid modifying the old body's info + var signalConfigCopy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(signalConfig)); + signalConfigCopy.parentPath = null; + signalConfigCopy.isRelativeToParent = false; + + var signalGO = SignalBuilder.Make(go, sector, signalConfigCopy, mod); signalGO.GetComponent()._identificationDistance = 0; signalGO.GetComponent()._sourceRadius = 1; signalGO.transform.position = brambleNode.transform.position; diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs index 054f53d3..6a05cb09 100644 --- a/NewHorizons/Builder/Props/DetailBuilder.cs +++ b/NewHorizons/Builder/Props/DetailBuilder.cs @@ -101,6 +101,7 @@ namespace NewHorizons.Builder.Props GameObject prop; bool isItem; bool invalidComponentFound = false; + bool isFromAssetBundle = !string.IsNullOrEmpty(detail.assetBundle); // We save copies with all their components fixed, good if the user is placing the same detail more than once if (detail?.path != null && _fixedPrefabCache.TryGetValue((sector, detail.path), out var storedPrefab)) @@ -139,7 +140,23 @@ namespace NewHorizons.Builder.Props } else { - FixSectoredComponent(component, sector, existingSectors, detail.keepLoaded); + // Fix cull groups only when not from an asset bundle (because then they're there on purpose!) + // keepLoaded should remove existing groups + // renderers/colliders get enabled later so we dont have to do that here + if (detail.keepLoaded && !isFromAssetBundle && component is SectorCullGroup or SectorCollisionGroup or SectorLightsCullGroup) + { + UnityEngine.Object.DestroyImmediate(component); + continue; + } + + FixSectoredComponent(component, sector, existingSectors); + } + + // Asset bundle is a real string -> Object loaded from unity + // If they're adding dialogue we have to manually register the xml text + if (isFromAssetBundle && component is CharacterDialogueTree dialogue) + { + DialogueBuilder.AddTranslation(dialogue._xmlCharacterDialogueAsset.text, null); } FixComponent(component, go, detail.ignoreSun); @@ -171,6 +188,11 @@ 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) @@ -266,16 +288,8 @@ namespace NewHorizons.Builder.Props /// /// Fix components that have sectors. Has a specific fix if there is a VisionTorchItem on the object. /// - private static void FixSectoredComponent(Component component, Sector sector, HashSet existingSectors, bool keepLoaded) + private static void FixSectoredComponent(Component component, Sector sector, HashSet existingSectors) { - // keepLoaded should remove existing groups - // renderers/colliders get enabled later so we dont have to do that here - if (keepLoaded && component is SectorCullGroup or SectorCollisionGroup or SectorLightsCullGroup) - { - UnityEngine.Object.DestroyImmediate(component); - return; - } - // fix Sector stuff, eg SectorCullGroup (without this, props that have a SectorCullGroup component will become invisible inappropriately) if (component is ISectorGroup sectorGroup && !existingSectors.Contains(sectorGroup.GetSector())) { @@ -283,26 +297,8 @@ namespace NewHorizons.Builder.Props } // Not doing else if here because idk if any of the classes below implement ISectorGroup - - // Null check else shuttles controls break - // parent sector is always null before Awake so this code actually never runs lol - if (component is Sector s && s.GetParentSector() != null && !existingSectors.Contains(s.GetParentSector())) - { - s.SetParentSector(sector); - } - else if (component is SectorCullGroup sectorCullGroup) - { - sectorCullGroup._controllingProxy = null; - - // fixes sector cull group deactivating renderers on map view enter and fast foward - // TODO: does this actually work? what? how? - sectorCullGroup._inMapView = false; - sectorCullGroup._isFastForwarding = false; - sectorCullGroup.SetVisible(sectorCullGroup.ShouldBeVisible(), true, false); - } - - else if(component is SectoredMonoBehaviour behaviour && !existingSectors.Contains(behaviour._sector)) + if(component is SectoredMonoBehaviour behaviour && !existingSectors.Contains(behaviour._sector)) { // not using SetSector here because it registers the events twice // perhaps this happens with ISectorGroup.SetSector or Sector.SetParentSector too? idk and nothing seems to break because of it yet diff --git a/NewHorizons/Builder/Props/DialogueBuilder.cs b/NewHorizons/Builder/Props/DialogueBuilder.cs index ca6ceadb..4daabe4a 100644 --- a/NewHorizons/Builder/Props/DialogueBuilder.cs +++ b/NewHorizons/Builder/Props/DialogueBuilder.cs @@ -376,7 +376,7 @@ namespace NewHorizons.Builder.Props } } - private static void AddTranslation(string xml, string characterName = null) + public static void AddTranslation(string xml, string characterName = null) { var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xml); diff --git a/NewHorizons/Builder/Props/ItemBuilder.cs b/NewHorizons/Builder/Props/ItemBuilder.cs index 825eafce..78a866be 100644 --- a/NewHorizons/Builder/Props/ItemBuilder.cs +++ b/NewHorizons/Builder/Props/ItemBuilder.cs @@ -79,6 +79,12 @@ namespace NewHorizons.Builder.Props item.ClearPickupConditionOnDrop = info.clearPickupConditionOnDrop; item.PickupFact = info.pickupFact; + if (info.colliderRadius > 0f) + { + go.AddComponent().radius = info.colliderRadius; + go.GetAddComponent(); + } + Delay.FireOnNextUpdate(() => { if (item != null && !string.IsNullOrEmpty(info.pathToInitialSocket)) diff --git a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs index a36c6003..60e0e879 100644 --- a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs +++ b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs @@ -30,7 +30,7 @@ namespace NewHorizons.Builder.ShipLog { if (body.Config.ShipLog == null) continue; - if (body.Config.ShipLog?.mapMode?.manualPosition == null) + if (body.Config.ShipLog.mapMode?.manualPosition == null) { flagAutoPositionUsed = true; } @@ -45,6 +45,12 @@ namespace NewHorizons.Builder.ShipLog } } + // If they're both false, just default to auto (this means that no planets even have ship log info) + if (!flagManualPositionUsed && !flagAutoPositionUsed) + { + flagAutoPositionUsed = true; + } + var isBaseSolarSystem = systemName == "SolarSystem"; // Default to MANUAL in Base Solar System (we can't automatically fix them so it might just break, but AUTO breaks even more!) @@ -142,6 +148,7 @@ namespace NewHorizons.Builder.ShipLog astroObject._imageObj = CreateImage(gameObject, image, body.Config.name + " Revealed", layer); astroObject._outlineObj = CreateImage(gameObject, outline, body.Config.name + " Outline", layer); + if (ShipLogHandler.BodyHasEntries(body)) { Image revealedImage = astroObject._imageObj.GetComponent(); @@ -156,6 +163,12 @@ namespace NewHorizons.Builder.ShipLog Rect imageRect = astroObject._imageObj.GetComponent().rect; astroObject._unviewedObj.transform.localPosition = new Vector3(imageRect.width / 2 + unviewedIconOffset, imageRect.height / 2 + unviewedIconOffset, 0); + + // Set all icons inactive, they will be conditionally activated when the map mode is opened for the first time + astroObject._unviewedObj.SetActive(false); + astroObject._imageObj.SetActive(false); + astroObject._outlineObj.SetActive(false); + return astroObject; } #endregion diff --git a/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs b/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs new file mode 100644 index 00000000..25d135c1 --- /dev/null +++ b/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs @@ -0,0 +1,33 @@ +using NewHorizons.Utility.OWML; +using UnityEngine; + +namespace NewHorizons.Components.Fixers; + +/// +/// Fixes a bug where spawning into the ship would not trigger the hatch entryway, so the player could drown if the ship flew into water +/// +internal class PlayerShipAtmosphereDetectorFix : MonoBehaviour +{ + private PlayerCameraFluidDetector _fluidDetector; + private SimpleFluidVolume _shipAtmosphereVolume; + + public void Start() + { + _fluidDetector = Locator.GetPlayerCameraDetector().GetComponent(); + _shipAtmosphereVolume = Locator.GetShipBody().transform.Find("Volumes/ShipAtmosphereVolume").GetComponent(); + } + + public void Update() + { + if (PlayerState.IsInsideShip()) + { + if (!_fluidDetector._activeVolumes.Contains(_shipAtmosphereVolume)) + { + NHLogger.LogVerbose($"{nameof(PlayerShipAtmosphereDetectorFix)} had to add the ship atmosphere volume [{_shipAtmosphereVolume}] to the fluid detector"); + _fluidDetector.AddVolume(_shipAtmosphereVolume); + } + NHLogger.LogVerbose($"{nameof(PlayerShipAtmosphereDetectorFix)} applied its fix"); + Component.Destroy(this); + } + } +} diff --git a/NewHorizons/Components/Props/ConditionalObjectActivation.cs b/NewHorizons/Components/Props/ConditionalObjectActivation.cs index 2fd0cde1..122302cf 100644 --- a/NewHorizons/Components/Props/ConditionalObjectActivation.cs +++ b/NewHorizons/Components/Props/ConditionalObjectActivation.cs @@ -19,6 +19,7 @@ namespace NewHorizons.Components.Props { var conditionalObjectActivationGO = new GameObject($"{go.name}_{condition}"); var component = conditionalObjectActivationGO.AddComponent(); + component.transform.parent = go.transform.parent; component.GameObject = go; component.DialogueCondition = condition; component.CloseEyes = closeEyes; diff --git a/NewHorizons/Components/Sectored/BrambleSectorController.cs b/NewHorizons/Components/Sectored/BrambleSectorController.cs index 3d48e53a..00077c25 100644 --- a/NewHorizons/Components/Sectored/BrambleSectorController.cs +++ b/NewHorizons/Components/Sectored/BrambleSectorController.cs @@ -33,13 +33,16 @@ namespace NewHorizons.Components.Sectored } private void Start() + { + DisableRenderers(); + } + + private void GetRenderers() { _renderers = gameObject.GetComponentsInChildren(); _tessellatedRenderers = gameObject.GetComponentsInChildren(); _colliders = gameObject.GetComponentsInChildren(); _lights = gameObject.GetComponentsInChildren(); - - DisableRenderers(); } private void OnSectorOccupantsUpdated() @@ -54,54 +57,35 @@ namespace NewHorizons.Components.Sectored } } - private void EnableRenderers() + private void EnableRenderers() => ToggleRenderers(true); + + private void DisableRenderers() => ToggleRenderers(false); + + private void ToggleRenderers(bool visible) { + GetRenderers(); + foreach (var renderer in _renderers) { - renderer.forceRenderingOff = false; + renderer.forceRenderingOff = !visible; } foreach (var tessellatedRenderer in _tessellatedRenderers) { - tessellatedRenderer.enabled = true; + tessellatedRenderer.enabled = visible; } foreach (var collider in _colliders) { - collider.enabled = true; + collider.enabled = visible; } foreach (var light in _lights) { - light.enabled = true; + light.enabled = visible; } - _renderersShown = true; - } - - private void DisableRenderers() - { - foreach (var renderer in _renderers) - { - renderer.forceRenderingOff = true; - } - - foreach (var tessellatedRenderer in _tessellatedRenderers) - { - tessellatedRenderer.enabled = false; - } - - foreach (var collider in _colliders) - { - collider.enabled = false; - } - - foreach (var light in _lights) - { - light.enabled = false; - } - - _renderersShown = false; + _renderersShown = visible; } } } diff --git a/NewHorizons/Components/TimeLoopController.cs b/NewHorizons/Components/TimeLoopController.cs index 7110e6db..fd38ab3c 100644 --- a/NewHorizons/Components/TimeLoopController.cs +++ b/NewHorizons/Components/TimeLoopController.cs @@ -13,6 +13,9 @@ namespace NewHorizons.Components public void Update() { + // So that mods can turn the time loop on/off using the TimLoop.SetTimeLoopEnabled method + if (!TimeLoop._timeLoopEnabled) return; + // Stock gives like 33 seconds after the sun collapses if (_supernovaHappened && Time.time > _supernovaTime + 50f) { diff --git a/NewHorizons/Components/Volumes/NHInnerFogWarpVolume.cs b/NewHorizons/Components/Volumes/NHInnerFogWarpVolume.cs deleted file mode 100644 index 70d74979..00000000 --- a/NewHorizons/Components/Volumes/NHInnerFogWarpVolume.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NewHorizons.Components.Volumes -{ - public class NHInnerFogWarpVolume : InnerFogWarpVolume - { - public override bool IsProbeOnly() => _exitRadius <= 6; - public override float GetFogThickness() => _exitRadius; - } -} diff --git a/NewHorizons/Components/Volumes/StreamingWarpVolume.cs b/NewHorizons/Components/Volumes/StreamingWarpVolume.cs index 25616c48..163a9e78 100644 --- a/NewHorizons/Components/Volumes/StreamingWarpVolume.cs +++ b/NewHorizons/Components/Volumes/StreamingWarpVolume.cs @@ -1,3 +1,4 @@ +using NewHorizons.Utility.OWML; using UnityEngine; namespace NewHorizons.Components.Volumes @@ -23,6 +24,23 @@ namespace NewHorizons.Components.Volumes public void FixedUpdate() { + // Bug report on Astral Codec mod page - Huge lag inside streaming warp volume, possible NRE? + if (_probe == null) + { + _probe = Locator.GetProbe(); + if (_probe == null) + { + NHLogger.LogError($"How is your scout probe null? Destroying {nameof(StreamingWarpVolume)}"); + GameObject.DestroyImmediate(gameObject); + } + } + + if (streamingGroup == null) + { + NHLogger.LogError($"{nameof(StreamingWarpVolume)} has no streaming group. Destroying {nameof(StreamingWarpVolume)}"); + GameObject.DestroyImmediate(gameObject); + } + bool probeActive = _probe.IsLaunched() && !_probe.IsAnchored(); bool shouldBeLoadingRequiredAssets = _playerInVolume || (_probeInVolume && probeActive); diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs index 40a9f7f5..f29c511a 100644 --- a/NewHorizons/External/Configs/PlanetConfig.cs +++ b/NewHorizons/External/Configs/PlanetConfig.cs @@ -213,7 +213,6 @@ namespace NewHorizons.External.Configs // Always have to have a base module if (Base == null) Base = new BaseModule(); if (Orbit == null) Orbit = new OrbitModule(); - if (ShipLog == null) ShipLog = new ShipLogModule(); if (ReferenceFrame == null) ReferenceFrame = new ReferenceFrameModule(); } diff --git a/NewHorizons/External/Modules/Props/DetailInfo.cs b/NewHorizons/External/Modules/Props/DetailInfo.cs index d9654562..171f1b64 100644 --- a/NewHorizons/External/Modules/Props/DetailInfo.cs +++ b/NewHorizons/External/Modules/Props/DetailInfo.cs @@ -54,7 +54,9 @@ namespace NewHorizons.External.Modules.Props public string quantumGroupID; /// - /// Should this detail stay loaded even if you're outside the sector (good for very large props) + /// Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)? + /// Also makes this detail visible on the map. + /// Most logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided. /// public bool keepLoaded; diff --git a/NewHorizons/External/Modules/Props/Item/ItemInfo.cs b/NewHorizons/External/Modules/Props/Item/ItemInfo.cs index 6876bd4c..6307db11 100644 --- a/NewHorizons/External/Modules/Props/Item/ItemInfo.cs +++ b/NewHorizons/External/Modules/Props/Item/ItemInfo.cs @@ -19,7 +19,7 @@ namespace NewHorizons.External.Modules.Props.Item /// public string name; /// - /// The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name. + /// The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCore, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name. /// public string itemType; /// @@ -27,6 +27,11 @@ namespace NewHorizons.External.Modules.Props.Item /// [DefaultValue(2f)] public float interactRange = 2f; /// + /// The radius that the added sphere collider will use for collision and hover detection. + /// If there's already a collider on the detail, you can make this 0. + /// + [DefaultValue(0.5f)] public float colliderRadius = 0.5f; + /// /// Whether the item can be dropped. Defaults to true. /// [DefaultValue(true)] public bool droppable = true; diff --git a/NewHorizons/External/NewHorizonsData.cs b/NewHorizons/External/NewHorizonsData.cs index b691b622..63a62392 100644 --- a/NewHorizons/External/NewHorizonsData.cs +++ b/NewHorizons/External/NewHorizonsData.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using NewHorizons.Builder.Props.Audio; using NewHorizons.Utility.OWML; namespace NewHorizons.External @@ -124,7 +126,6 @@ namespace NewHorizons.External if (!KnowsFrequency(frequency)) { _activeProfile.KnownFrequencies.Add(frequency); - Save(); } } @@ -134,13 +135,12 @@ namespace NewHorizons.External if (KnowsFrequency(frequency)) { _activeProfile.KnownFrequencies.Remove(frequency); - Save(); } } public static bool KnowsMultipleFrequencies() { - return _activeProfile != null && _activeProfile.KnownFrequencies.Count > 0; + return _activeProfile?.KnownFrequencies != null && _activeProfile.KnownFrequencies.Count(SignalBuilder.IsFrequencyInUse) > 1; } #endregion @@ -159,7 +159,6 @@ namespace NewHorizons.External if (!KnowsSignal(signal)) { _activeProfile.KnownSignals.Add(signal); - Save(); } } @@ -170,7 +169,6 @@ namespace NewHorizons.External public static void AddNewlyRevealedFactID(string id) { _activeProfile?.NewlyRevealedFactIDs.Add(id); - Save(); } public static List GetNewlyRevealedFactIDs() @@ -181,7 +179,6 @@ namespace NewHorizons.External public static void ClearNewlyRevealedFactIDs() { _activeProfile?.NewlyRevealedFactIDs.Clear(); - Save(); } #endregion @@ -191,7 +188,6 @@ namespace NewHorizons.External public static void ReadOneTimePopup(string id) { _activeProfile?.PopupsRead.Add(id); - Save(); } public static bool HasReadOneTimePopup(string id) @@ -208,7 +204,6 @@ namespace NewHorizons.External { if (name == CharacterDialogueTree.RECORDING_NAME || name == CharacterDialogueTree.SIGN_NAME) return; _activeProfile?.CharactersTalkedTo.SafeAdd(name); - Save(); } public static bool HasTalkedToFiveCharacters() diff --git a/NewHorizons/Handlers/PlayerSpawnHandler.cs b/NewHorizons/Handlers/PlayerSpawnHandler.cs index 22380665..40670ad8 100644 --- a/NewHorizons/Handlers/PlayerSpawnHandler.cs +++ b/NewHorizons/Handlers/PlayerSpawnHandler.cs @@ -115,6 +115,17 @@ namespace NewHorizons.Handlers } // For some reason none of this seems to apply to the Player. // If somebody ever makes a sound volume thats somehow always applying to the player tho then itd probably be this + + // Sometimes the ship isn't added to the volumes it's meant to now be in + foreach (var volume in SpawnPointBuilder.ShipSpawn.GetAttachedOWRigidbody().GetComponentsInChildren()) + { + if (volume.GetOWTriggerVolume().GetPenetrationDistance(ship.transform.position) > 0) + { + // Add ship to volume + // If it's already tracking it it will complain here but thats fine + volume.GetOWTriggerVolume().AddObjectToVolume(Locator.GetShipDetector()); + } + } } } else if (Main.Instance.CurrentStarSystem != "SolarSystem" && !Main.Instance.IsWarpingFromShip) diff --git a/NewHorizons/Handlers/SubtitlesHandler.cs b/NewHorizons/Handlers/SubtitlesHandler.cs index de283b54..d3fe3fb8 100644 --- a/NewHorizons/Handlers/SubtitlesHandler.cs +++ b/NewHorizons/Handlers/SubtitlesHandler.cs @@ -11,11 +11,8 @@ namespace NewHorizons.Handlers { class SubtitlesHandler : MonoBehaviour { - public static int SUBTITLE_HEIGHT = 97; - public static int SUBTITLE_WIDTH = 669; // nice - - public Graphic graphic; - public Image image; + public static float SUBTITLE_HEIGHT = 97; + public static float SUBTITLE_WIDTH = 669; // nice public float fadeSpeed = 0.005f; public float fade = 1; @@ -29,13 +26,27 @@ namespace NewHorizons.Handlers public static readonly int PAUSE_TIMER_MAX = 50; public int pauseTimer = PAUSE_TIMER_MAX; + private Image _subtitleDisplay; + private Graphic _graphic; + + private static List<(IModBehaviour mod, string filePath)> _additionalSubtitles = new(); + + public static void RegisterAdditionalSubtitle(IModBehaviour mod, string filePath) + { + _additionalSubtitles.Add((mod, filePath)); + } + public void CheckForEOTE() { if (!eoteSubtitleHasBeenInserted) { if (Main.HasDLC) { - if (eoteSprite != null) possibleSubtitles.Insert(0, eoteSprite); // ensure that the Echoes of the Eye subtitle always appears first + if (eoteSprite != null) + { + // Don't make it appear first actually because we have mods to display! + possibleSubtitles.Add(eoteSprite); + } eoteSubtitleHasBeenInserted = true; } } @@ -43,18 +54,25 @@ namespace NewHorizons.Handlers public void Start() { + // We preserve the current image to add it to our custom subtitle + // We also need this element to preserve its size GetComponent().alpha = 1; - graphic = GetComponent(); - image = GetComponent(); - - graphic.enabled = true; - image.enabled = true; - + var image = GetComponent(); eoteSprite = image.sprite; + image.sprite = null; + image.enabled = false; + var layout = GetComponent(); + layout.minHeight = SUBTITLE_HEIGHT; CheckForEOTE(); - image.sprite = null; // Just in case. I don't know how not having the dlc changes the subtitle game object + // We add our subtitles as a child object so that their sizing doesnt shift the layout of the main menu + _subtitleDisplay = new GameObject("SubtitleDisplay").AddComponent(); + _subtitleDisplay.transform.parent = transform; + _subtitleDisplay.transform.localPosition = new Vector3(0, 0, 0); + _subtitleDisplay.transform.localScale = new Vector3(0.75f, 0.75f, 0.75f); + _graphic = _subtitleDisplay.gameObject.GetAddComponent(); + _subtitleDisplay.gameObject.GetAddComponent().minWidth = SUBTITLE_WIDTH; AddSubtitles(); } @@ -73,6 +91,10 @@ namespace NewHorizons.Handlers AddSubtitle(mod, "subtitle.png"); } } + foreach (var pair in _additionalSubtitles) + { + AddSubtitle(pair.mod, pair.filePath); + } } public void AddSubtitle(IModBehaviour mod, string filepath) @@ -82,7 +104,7 @@ namespace NewHorizons.Handlers var tex = ImageUtilities.GetTexture(mod, filepath, false); if (tex == null) return; - var sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, SUBTITLE_HEIGHT), new Vector2(0.5f, 0.5f), 100.0f); + var sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, Mathf.Max(SUBTITLE_HEIGHT, tex.height)), new Vector2(0.5f, 0.5f), 100.0f); AddSubtitle(sprite); } @@ -95,12 +117,25 @@ namespace NewHorizons.Handlers { CheckForEOTE(); - if (possibleSubtitles.Count == 0) return; + if (possibleSubtitles.Count == 0) + { + return; + } - if (image.sprite == null) image.sprite = possibleSubtitles[0]; + _subtitleDisplay.transform.localPosition = new Vector3(0, -36, 0); + + if (_subtitleDisplay.sprite == null) + { + _subtitleDisplay.sprite = possibleSubtitles[0]; + // Always call this in case we stop changing subtitles after + ChangeSubtitle(); + } // don't fade transition subtitles if there's only one subtitle - if (possibleSubtitles.Count <= 1) return; + if (possibleSubtitles.Count <= 1) + { + return; + } if (pauseTimer > 0) { @@ -111,7 +146,7 @@ namespace NewHorizons.Handlers if (fadingAway) { fade -= fadeSpeed; - + if (fade <= 0) { fade = 0; @@ -122,7 +157,7 @@ namespace NewHorizons.Handlers else { fade += fadeSpeed; - + if (fade >= 1) { fade = 1; @@ -131,14 +166,19 @@ namespace NewHorizons.Handlers } } - graphic.color = new Color(1, 1, 1, fade); + _graphic.color = new Color(1, 1, 1, fade); } public void ChangeSubtitle() { subtitleIndex = (subtitleIndex + 1) % possibleSubtitles.Count; - - image.sprite = possibleSubtitles[subtitleIndex]; + + var subtitle = possibleSubtitles[subtitleIndex]; + _subtitleDisplay.sprite = subtitle; + var width = subtitle.texture.width; + var height = subtitle.texture.height; + var ratio = SUBTITLE_WIDTH / width; // one of these needs to be a float so that compiler doesn't think "oh 2 integers! let's round to nearest whole" + _subtitleDisplay.rectTransform.sizeDelta = new Vector2(width, height) * ratio; } } } diff --git a/NewHorizons/Handlers/SystemCreationHandler.cs b/NewHorizons/Handlers/SystemCreationHandler.cs index debd14e7..5936434a 100644 --- a/NewHorizons/Handlers/SystemCreationHandler.cs +++ b/NewHorizons/Handlers/SystemCreationHandler.cs @@ -34,7 +34,8 @@ namespace NewHorizons.Handlers if (Main.Instance.CurrentStarSystem == "EyeOfTheUniverse") return; // Small mod compat change for StopTime - do nothing if it's enabled - if (system.Config.enableTimeLoop && !OtherModUtil.IsEnabled("_nebula.StopTime")) + // Do not add our custom time loop controller in the base game system: It will handle itself + if (Main.Instance.CurrentStarSystem != "SolarSystem" && system.Config.enableTimeLoop && !OtherModUtil.IsEnabled("_nebula.StopTime")) { var timeLoopController = new GameObject("TimeLoopController"); timeLoopController.AddComponent(); diff --git a/NewHorizons/Handlers/TranslationHandler.cs b/NewHorizons/Handlers/TranslationHandler.cs index f2e75075..c2f49ebf 100644 --- a/NewHorizons/Handlers/TranslationHandler.cs +++ b/NewHorizons/Handlers/TranslationHandler.cs @@ -2,9 +2,11 @@ using NewHorizons.External.Configs; using NewHorizons.Utility; using NewHorizons.Utility.OWML; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using static TextTranslation; namespace NewHorizons.Handlers { @@ -50,30 +52,46 @@ namespace NewHorizons.Handlers } // Get the translated text - if (dictionary.TryGetValue(language, out var table)) + if (TryGetTranslatedText(dictionary, language, text, out var translatedText)) { - if (table.TryGetValue(text, out var translatedText)) + return translatedText; + } + + if (warn) + { + NHLogger.LogVerbose($"Defaulting to english for {text}"); + } + + if (TryGetTranslatedText(dictionary, Language.ENGLISH, text, out translatedText)) + { + return translatedText; + } + + if (warn) + { + NHLogger.LogVerbose($"Defaulting to key for {text}"); + } + + return text; + } + + private static bool TryGetTranslatedText(Dictionary> dict, Language language, string text, out string translatedText) + { + if (dict.TryGetValue(language, out var table)) + { + if (table.TryGetValue(text, out translatedText)) { - return translatedText; + return true; } // Try without whitespace if its missing - else if (table.TryGetValue(text.TruncateWhitespace(), out translatedText)) + else if (table.TryGetValue(text.TruncateWhitespaceAndToLower(), out translatedText)) { - return translatedText; + return true; } } - if (warn) NHLogger.LogVerbose($"Defaulting to english for {text}"); - - // Try to default to English - if (dictionary.TryGetValue(TextTranslation.Language.ENGLISH, out var englishTable)) - if (englishTable.TryGetValue(text, out var englishText)) - return englishText; - - if (warn) NHLogger.LogVerbose($"Defaulting to key for {text}"); - - // Default to the key - return text; + translatedText = null; + return false; } public static void RegisterTranslation(TextTranslation.Language language, TranslationConfig config) @@ -99,7 +117,7 @@ namespace NewHorizons.Handlers // Fix new lines in dialogue translations, remove whitespace from keys else if the dialogue has weird whitespace and line breaks it gets really annoying // to write translation keys for (can't just copy paste out of xml, have to start adding \\n and \\r and stuff // If any of these issues become relevant to other dictionaries we can bring this code over, but for now why fix what isnt broke - var key = originalKey.Replace("\\n", "\n").TruncateWhitespace().Replace("<", "<").Replace(">", ">").Replace("", ""); + var key = originalKey.Replace("\\n", "\n").Replace("<", "<").Replace(">", ">").Replace("", "").TruncateWhitespaceAndToLower(); var value = config.DialogueDictionary[originalKey].Replace("\\n", "\n").Replace("<", "<").Replace(">", ">").Replace("", ""); if (!_dialogueTranslationDictionary[language].ContainsKey(key)) _dialogueTranslationDictionary[language].Add(key, value); diff --git a/NewHorizons/INewHorizons.cs b/NewHorizons/INewHorizons.cs index 22132568..4dfd2d1f 100644 --- a/NewHorizons/INewHorizons.cs +++ b/NewHorizons/INewHorizons.cs @@ -207,5 +207,13 @@ namespace NewHorizons /// string GetTranslationForOtherText(string text); #endregion + + /// + /// Registers a subtitle for the main menu. + /// Call this once before the main menu finishes loading + /// + /// + /// + void AddSubtitle(IModBehaviour mod, string filePath); } } diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index bc393776..1415a54c 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -263,7 +263,6 @@ namespace NewHorizons // Call this from the menu since we hadn't hooked onto the event yet Delay.FireOnNextUpdate(() => OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single)); Delay.FireOnNextUpdate(() => _firstLoad = false); - Instance.ModHelper.Menus.PauseMenu.OnInit += DebugReload.InitializePauseMenu; MenuHandler.Init(); AchievementHandler.Init(); @@ -275,6 +274,13 @@ namespace NewHorizons LoadAddonManifest("Assets/addon-manifest.json", this); } + public override void SetupPauseMenu(IPauseMenuManager pauseMenu) + { + base.SetupPauseMenu(pauseMenu); + DebugReload.InitializePauseMenu(pauseMenu); + DebugMenu.InitializePauseMenu(pauseMenu); + } + public void OnDestroy() { NHLogger.Log($"Destroying NewHorizons"); @@ -512,8 +518,10 @@ namespace NewHorizons // We are in a custom system on the first loop -> The time loop isn't active, that's not very good // TimeLoop uses the launch codes condition to know if the loop is active or not + // We also skip them to loop 2, else if they enter a credits volume in this loop they get reset if (CurrentStarSystem != "SolarSystem" && PlayerData.LoadLoopCount() == 1) { + PlayerData.SaveLoopCount(2); PlayerData.SetPersistentCondition("LAUNCH_CODES_GIVEN", true); } } @@ -595,6 +603,7 @@ namespace NewHorizons Locator.GetPlayerBody().gameObject.AddComponent(); Locator.GetPlayerBody().gameObject.AddComponent(); Locator.GetPlayerBody().gameObject.AddComponent(); + Locator.GetPlayerBody().gameObject.AddComponent(); PlayerSpawnHandler.OnSystemReady(shouldWarpInFromShip, shouldWarpInFromVessel); diff --git a/NewHorizons/NewHorizons.csproj b/NewHorizons/NewHorizons.csproj index c1e195b3..e8abb757 100644 --- a/NewHorizons/NewHorizons.csproj +++ b/NewHorizons/NewHorizons.csproj @@ -16,7 +16,7 @@ - + diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index a96673d5..6a165fa2 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -336,5 +336,7 @@ namespace NewHorizons public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI); public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER); + + public void AddSubtitle(IModBehaviour mod, string filePath) => SubtitlesHandler.RegisterAdditionalSubtitle(mod, filePath); } } diff --git a/NewHorizons/OtherMods/MenuFramework/IMenuAPI.cs b/NewHorizons/OtherMods/MenuFramework/IMenuAPI.cs deleted file mode 100644 index f44aecdf..00000000 --- a/NewHorizons/OtherMods/MenuFramework/IMenuAPI.cs +++ /dev/null @@ -1,20 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; - -namespace NewHorizons.OtherMods.MenuFramework -{ - public interface IMenuAPI - { - GameObject TitleScreen_MakeMenuOpenButton(string name, int index, Menu menuToOpen); - GameObject TitleScreen_MakeSceneLoadButton(string name, int index, SubmitActionLoadScene.LoadableScenes sceneToLoad, PopupMenu confirmPopup = null); - Button TitleScreen_MakeSimpleButton(string name, int index); - GameObject PauseMenu_MakeMenuOpenButton(string name, Menu menuToOpen, Menu customMenu = null); - GameObject PauseMenu_MakeSceneLoadButton(string name, SubmitActionLoadScene.LoadableScenes sceneToLoad, PopupMenu confirmPopup = null, Menu customMenu = null); - Button PauseMenu_MakeSimpleButton(string name, Menu customMenu = null); - Menu PauseMenu_MakePauseListMenu(string title); - PopupMenu MakeTwoChoicePopup(string message, string confirmText, string cancelText); - PopupInputMenu MakeInputFieldPopup(string message, string placeholderMessage, string confirmText, string cancelText); - PopupMenu MakeInfoPopup(string message, string continueButtonText); - void RegisterStartupPopup(string message); - } -} diff --git a/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs b/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs index 101a0789..1a54fc46 100644 --- a/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs +++ b/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs @@ -11,15 +11,11 @@ namespace NewHorizons.OtherMods.MenuFramework { public static class MenuHandler { - private static IMenuAPI _menuApi; - private static List<(IModBehaviour mod, string message, bool repeat)> _registeredPopups = new(); private static List _failedFiles = new(); public static void Init() { - _menuApi = Main.Instance.ModHelper.Interaction.TryGetModApi("_nebula.MenuFramework"); - TextTranslation.Get().OnLanguageChanged += OnLanguageChanged; } @@ -35,14 +31,14 @@ namespace NewHorizons.OtherMods.MenuFramework Application.version); NHLogger.LogError(warning); - _menuApi.RegisterStartupPopup(warning); + Main.Instance.ModHelper.MenuHelper.PopupMenuManager.RegisterStartupPopup(warning); } foreach(var (mod, message, repeat) in _registeredPopups) { if (repeat || !NewHorizonsData.HasReadOneTimePopup(mod.ModHelper.Manifest.UniqueName)) { - _menuApi.RegisterStartupPopup(TranslationHandler.GetTranslation(message, TranslationHandler.TextType.UI)); + Main.Instance.ModHelper.MenuHelper.PopupMenuManager.RegisterStartupPopup(TranslationHandler.GetTranslation(message, TranslationHandler.TextType.UI)); NewHorizonsData.ReadOneTimePopup(mod.ModHelper.Manifest.UniqueName); } } @@ -52,7 +48,7 @@ namespace NewHorizons.OtherMods.MenuFramework var message = TranslationHandler.GetTranslation("JSON_FAILED_TO_LOAD", TranslationHandler.TextType.UI); var mods = string.Join(",", _failedFiles.Take(10)); if (_failedFiles.Count > 10) mods += "..."; - _menuApi.RegisterStartupPopup(string.Format(message, mods)); + Main.Instance.ModHelper.MenuHelper.PopupMenuManager.RegisterStartupPopup(string.Format(message, mods)); } _registeredPopups.Clear(); diff --git a/NewHorizons/Patches/BrambleProjectionFixPatches.cs b/NewHorizons/Patches/BrambleProjectionFixPatches.cs new file mode 100644 index 00000000..9d41a251 --- /dev/null +++ b/NewHorizons/Patches/BrambleProjectionFixPatches.cs @@ -0,0 +1,26 @@ +using HarmonyLib; + +namespace NewHorizons.Patches; + +/// +/// Bug fix from the Outsider +/// +[HarmonyPatch] +internal class BrambleProjectionFixPatches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(FogWarpVolume), nameof(FogWarpVolume.WarpDetector))] + public static bool FogWarpVolume_WarpDetector() + { + // Do not warp the player if they have entered the fog via a projection + return !PlayerState.UsingNomaiRemoteCamera(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(FogWarpDetector), nameof(FogWarpDetector.FixedUpdate))] + public static bool FogWarpDetector_FixedUpdate() + { + // Do not warp the player if they have entered the fog via a projection + return !PlayerState.UsingNomaiRemoteCamera(); + } +} diff --git a/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs b/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs index 330e578e..c01240f2 100644 --- a/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs +++ b/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using System.Collections; +using UnityEngine.InputSystem; namespace NewHorizons.Patches.DialoguePatches { @@ -7,6 +9,29 @@ namespace NewHorizons.Patches.DialoguePatches { private static bool _wasLastDialogueInactive = false; + [HarmonyPostfix] + [HarmonyPatch(nameof(RemoteDialogueTrigger.Awake))] + public static void RemoteDialogueTrigger_Awake(RemoteDialogueTrigger __instance) + { + // Wait for player to be up and moving before allowing them to trigger remote dialogue + // Stops you getting locked into dialogue while waking up + if (OWInput.GetInputMode() != InputMode.Character) + { + __instance._collider.enabled = false; + __instance.StartCoroutine(AwakeCoroutine(__instance)); + } + } + + private static IEnumerator AwakeCoroutine(RemoteDialogueTrigger instance) + { + while (OWInput.GetInputMode() != InputMode.Character) + { + yield return null; + } + + instance._collider.enabled = true; + } + /// /// Should fix a bug where disabled a CharacterDialogueTree makes its related RemoteDialogueTriggers softlock your game /// diff --git a/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs b/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs index 15da0b12..b0c31daa 100644 --- a/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs +++ b/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs @@ -1,6 +1,7 @@ using HarmonyLib; using NewHorizons.Components.Sectored; using NewHorizons.Handlers; +using NewHorizons.Utility.OWML; namespace NewHorizons.Patches.HUDPatches { @@ -27,15 +28,21 @@ namespace NewHorizons.Patches.HUDPatches [HarmonyPatch(nameof(ProbeHUDMarker.RefreshOwnVisibility))] public static bool ProbeHUDMarker_RefreshOwnVisibility(ProbeHUDMarker __instance) { + // Probe marker seems to never appear in the eye or QM in base game (inside eye being past the vortex) ?? at least thats what its code implies bool insideEYE = Locator.GetEyeStateManager() != null && Locator.GetEyeStateManager().IsInsideTheEye(); bool insideQM = __instance._quantumMoon != null && (__instance._quantumMoon.IsPlayerInside() || __instance._quantumMoon.IsProbeInside()); - bool insideRW = Locator.GetRingWorldController() != null && Locator.GetRingWorldController().isPlayerInside == Locator.GetRingWorldController().isProbeInside; - bool insideIP = Locator.GetCloakFieldController() != null && Locator.GetCloakFieldController().isPlayerInsideCloak == Locator.GetCloakFieldController().isProbeInsideCloak; - bool insideCloak = CloakSectorController.isPlayerInside == CloakSectorController.isProbeInside; + + // Either the controllers wtv are null or the player and probe state are the same + bool sameRW = Locator.GetRingWorldController() == null || Locator.GetRingWorldController().isPlayerInside == Locator.GetRingWorldController().isProbeInside; + bool sameIP = Locator.GetCloakFieldController() == null || Locator.GetCloakFieldController().isPlayerInsideCloak == Locator.GetCloakFieldController().isProbeInsideCloak; + bool sameCloak = CloakSectorController.isPlayerInside == CloakSectorController.isProbeInside; bool sameInterference = InterferenceHandler.IsPlayerSameAsProbe(); + bool isActive = __instance.gameObject.activeInHierarchy || __instance._isTLCDuplicate; - __instance._isVisible = isActive && !insideEYE && !insideQM && !__instance._translatorEquipped && !__instance._inConversation && __instance._launched && (__instance._isWearingHelmet || __instance._atFlightConsole) && insideRW && insideIP && insideCloak && sameInterference; + __instance._isVisible = isActive && !insideEYE && !insideQM && !__instance._translatorEquipped + && !__instance._inConversation && __instance._launched && (__instance._isWearingHelmet || __instance._atFlightConsole) + && sameRW && sameIP && sameCloak && sameInterference; if (__instance._canvasMarker != null) __instance._canvasMarker.SetVisibility(__instance._isVisible); diff --git a/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs b/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs index 16056c7b..6785a8a2 100644 --- a/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs +++ b/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs @@ -85,12 +85,8 @@ namespace NewHorizons.Patches.PlayerPatches [HarmonyPatch(nameof(PlayerData.KnowsMultipleFrequencies))] public static bool PlayerData_KnowsMultipleFrequencies(ref bool __result) { - if (NewHorizonsData.KnowsMultipleFrequencies()) - { - __result = true; - return false; - } - return true; + __result = NewHorizonsData.KnowsMultipleFrequencies(); + return false; } [HarmonyPrefix] @@ -140,5 +136,12 @@ namespace NewHorizons.Patches.PlayerPatches { NewHorizonsData.Reset(); } + + [HarmonyPostfix] + [HarmonyPatch(nameof(PlayerData.SaveCurrentGame))] + public static void PlayerData_SaveCurrentGame() + { + NewHorizonsData.Save(); + } } } diff --git a/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs b/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs index ba3c0e33..abfc67a8 100644 --- a/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs +++ b/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs @@ -25,9 +25,32 @@ namespace NewHorizons.Patches.ShipLogPatches } } + [HarmonyPrefix] + [HarmonyPatch(nameof(ShipLogAstroObject.UpdateState))] + public static bool ShipLogAstroObject_UpdateState_Pre(ShipLogAstroObject __instance) + { + // Custom astro objects might have no entries, in this case they will be permanently hidden + // Just treat it as if it were revealed + if (__instance._entries.Count == 0) + { + __instance._state = ShipLogEntry.State.Explored; + __instance._imageObj.SetActive(true); + __instance._outlineObj?.SetActive(false); + if (__instance._image != null) + { + __instance.SetMaterialGreyscale(false); + __instance._image.color = Color.white; + } + + return false; + } + + return true; + } + [HarmonyPostfix] [HarmonyPatch(nameof(ShipLogAstroObject.UpdateState))] - public static void ShipLogAstroObject_UpdateState(ShipLogAstroObject __instance) + public static void ShipLogAstroObject_UpdateState_Post(ShipLogAstroObject __instance) { Transform detailsParent = __instance.transform.Find("Details"); if (detailsParent != null) diff --git a/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs b/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs index 12c54db5..2b91dc72 100644 --- a/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs +++ b/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs @@ -1,5 +1,6 @@ using HarmonyLib; using NewHorizons.Builder.Props.Audio; +using NewHorizons.Utility.OWML; namespace NewHorizons.Patches.SignalPatches { @@ -19,13 +20,18 @@ namespace NewHorizons.Patches.SignalPatches { var count = SignalBuilder.NumberOfFrequencies; __instance._frequencyFilterIndex += increment; + // Base game does 1 here but we use frequency index 0 as "default" or "???" __instance._frequencyFilterIndex = __instance._frequencyFilterIndex >= count ? 0 : __instance._frequencyFilterIndex; __instance._frequencyFilterIndex = __instance._frequencyFilterIndex < 0 ? count - 1 : __instance._frequencyFilterIndex; var signalFrequency = AudioSignal.IndexToFrequency(__instance._frequencyFilterIndex); + NHLogger.Log($"Changed freq to {signalFrequency} at {__instance._frequencyFilterIndex}"); + // Skip over this frequency - var isUnknown = !PlayerData.KnowsFrequency(signalFrequency) && !(__instance._isUnknownFreqNearby && __instance._unknownFrequency == signalFrequency); - if (isUnknown || !SignalBuilder.IsFrequencyInUse(signalFrequency)) + // Never skip traveler (always known) + var isTraveler = __instance._frequencyFilterIndex == 1; + var isUnknown = !PlayerData.KnowsFrequency(signalFrequency) && (!__instance._isUnknownFreqNearby || __instance._unknownFrequency != signalFrequency); + if (!isTraveler && (isUnknown || !SignalBuilder.IsFrequencyInUse(signalFrequency))) { __instance.SwitchFrequencyFilter(increment); } diff --git a/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs b/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs index 0dd86a57..3091d1fd 100644 --- a/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs +++ b/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs @@ -26,5 +26,25 @@ namespace NewHorizons.Patches.VolumePatches return false; } + + /// + /// This method detects Nomai shuttles that are inactive + /// When active, it swaps the position of the NomaiShuttleController and the Rigidbody, so its not found as a child here and explodes continuously forever + /// Just ignore the shuttle if its inactive + /// + [HarmonyPrefix] + [HarmonyPatch(nameof(DestructionVolume.VanishNomaiShuttle))] + public static bool DestructionVolume_VanishNomaiShuttle(DestructionVolume __instance, OWRigidbody shuttleBody, RelativeLocationData entryLocation) + { + if (shuttleBody.GetComponentInChildren() == null) + { + if (__instance._nomaiShuttleBody == shuttleBody) + { + __instance._nomaiShuttleBody = null; + } + return false; + } + return true; + } } } diff --git a/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs b/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs index f63bad81..41f3e652 100644 --- a/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs +++ b/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using NewHorizons.Builder.Props; using UnityEngine; namespace NewHorizons.Patches.VolumePatches @@ -8,20 +9,38 @@ namespace NewHorizons.Patches.VolumePatches { [HarmonyPrefix] [HarmonyPatch(typeof(SphericalFogWarpVolume), nameof(SphericalFogWarpVolume.IsProbeOnly))] - public static bool SphericalFogWarpVolume_IsProbeOnly(SphericalFogWarpVolume __instance, out bool __result) + public static bool SphericalFogWarpVolume_IsProbeOnly(SphericalFogWarpVolume __instance, ref bool __result) { - __result = Mathf.Approximately(__instance._exitRadius / __instance._warpRadius, 2f); // Check the ratio between these to determine if seed, instead of just < 10 + // Do not affect base game volumes + if (!BrambleNodeBuilder.IsNHFogWarpVolume(__instance)) + { + return true; + } + + // Check the ratio between these to determine if seed, instead of just < 10 + __result = Mathf.Approximately(__instance._exitRadius / __instance._warpRadius, 2f); return false; } [HarmonyPrefix] [HarmonyPatch(typeof(FogWarpVolume), nameof(FogWarpVolume.GetFogThickness))] - public static bool FogWarpVolume_GetFogThickness(FogWarpVolume __instance, out float __result) + public static bool FogWarpVolume_GetFogThickness(FogWarpVolume __instance, ref float __result) { - if (__instance is InnerFogWarpVolume sph) __result = sph._exitRadius; - else __result = 50; // 50f is hardcoded as the return value in the base game + // Do not affect base game volumes + if (!BrambleNodeBuilder.IsNHFogWarpVolume(__instance)) + { + return true; + } - return false; + if (__instance is InnerFogWarpVolume sph) + { + __result = sph._exitRadius; + return false; + } + else + { + return true; + } } } } diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 833b1a89..d6356f75 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -1342,7 +1342,7 @@ }, "keepLoaded": { "type": "boolean", - "description": "Should this detail stay loaded even if you're outside the sector (good for very large props)" + "description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided." }, "hasPhysics": { "type": "boolean", @@ -1402,7 +1402,7 @@ }, "itemType": { "type": "string", - "description": "The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name." + "description": "The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCore, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name." }, "interactRange": { "type": "number", @@ -1410,6 +1410,12 @@ "format": "float", "default": 2.0 }, + "colliderRadius": { + "type": "number", + "description": "The radius that the added sphere collider will use for collision and hover detection.\nIf there's already a collider on the detail, you can make this 0.", + "format": "float", + "default": 0.5 + }, "droppable": { "type": "boolean", "description": "Whether the item can be dropped. Defaults to true.", diff --git a/NewHorizons/Utility/DebugTools/DebugReload.cs b/NewHorizons/Utility/DebugTools/DebugReload.cs index 87dc99cf..cd067d98 100644 --- a/NewHorizons/Utility/DebugTools/DebugReload.cs +++ b/NewHorizons/Utility/DebugTools/DebugReload.cs @@ -3,6 +3,7 @@ using NewHorizons.Utility.Files; using NewHorizons.Utility.OWML; using OWML.Common; using OWML.Common.Menus; +using OWML.Utils; using System; namespace NewHorizons.Utility.DebugTools @@ -10,22 +11,18 @@ namespace NewHorizons.Utility.DebugTools public static class DebugReload { - private static IModButton _reloadButton; + private static SubmitAction _reloadButton; - public static void InitializePauseMenu() + public static void InitializePauseMenu(IPauseMenuManager pauseMenu) { - _reloadButton = Main.Instance.ModHelper.Menus.PauseMenu.OptionsButton.Duplicate(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpper()); - _reloadButton.OnClick += ReloadConfigs; + _reloadButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpper(), 3, true); + _reloadButton.OnSubmitAction += ReloadConfigs; UpdateReloadButton(); } public static void UpdateReloadButton() { - if (_reloadButton != null) - { - if (Main.Debug) _reloadButton.Show(); - else _reloadButton.Hide(); - } + _reloadButton?.SetButtonVisible(Main.Debug); } private static void ReloadConfigs() diff --git a/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs b/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs index 07bd39f6..92e77893 100644 --- a/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs +++ b/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs @@ -5,6 +5,7 @@ using NewHorizons.Utility.OWML; using Newtonsoft.Json; using OWML.Common; using OWML.Common.Menus; +using OWML.Utils; using System; using System.Collections.Generic; using System.IO; @@ -15,7 +16,7 @@ namespace NewHorizons.Utility.DebugTools.Menu { class DebugMenu : MonoBehaviour { - private static IModButton pauseMenuButton; + private static SubmitAction pauseMenuButton; public GUIStyle _editorMenuStyle; public GUIStyle _tabBarStyle; @@ -23,7 +24,6 @@ namespace NewHorizons.Utility.DebugTools.Menu internal Vector2 EditorMenuSize = new Vector2(600, 900); bool menuOpen = false; static bool openMenuOnPause; - static bool staticInitialized; // Menu params internal static IModBehaviour loadedMod = null; @@ -34,6 +34,8 @@ namespace NewHorizons.Utility.DebugTools.Menu // Submenus private List submenus; private int activeSubmenu = 0; + + private static DebugMenu _instance; internal static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { @@ -55,28 +57,13 @@ namespace NewHorizons.Utility.DebugTools.Menu private void Start() { - if (!staticInitialized) - { - staticInitialized = true; + _instance = this; - Main.Instance.ModHelper.Menus.PauseMenu.OnInit += PauseMenuInitHook; - Main.Instance.ModHelper.Menus.PauseMenu.OnClosed += CloseMenu; - Main.Instance.ModHelper.Menus.PauseMenu.OnOpened += RestoreMenuOpennessState; + Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuOpened += OnOpenMenu; + Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuClosed += OnCloseMenu; + Main.Instance.OnChangeStarSystem.AddListener(OnChangeStarSystem); - PauseMenuInitHook(); - - Main.Instance.OnChangeStarSystem.AddListener((string s) => { - if (saveButtonUnlocked) - { - SaveLoadedConfigsForRecentSystem(); - saveButtonUnlocked = false; - } - }); - } - else - { - InitMenu(); - } + InitMenu(); if (loadedMod != null) { @@ -84,26 +71,38 @@ namespace NewHorizons.Utility.DebugTools.Menu } } - private void PauseMenuInitHook() + public void OnDestroy() { - pauseMenuButton = Main.Instance.ModHelper.Menus.PauseMenu.OptionsButton.Duplicate(TranslationHandler.GetTranslation("Toggle Dev Tools Menu", TranslationHandler.TextType.UI).ToUpper()); - InitMenu(); + Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuOpened -= OnOpenMenu; + Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuClosed -= OnCloseMenu; + Main.Instance.OnChangeStarSystem.RemoveListener(OnChangeStarSystem); + } + + private void OnChangeStarSystem(string _) + { + if (saveButtonUnlocked) + { + SaveLoadedConfigsForRecentSystem(); + saveButtonUnlocked = false; + } + } + + public static void InitializePauseMenu(IPauseMenuManager pauseMenu) + { + pauseMenuButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Toggle Dev Tools Menu", TranslationHandler.TextType.UI).ToUpper(), 3, true); + _instance?.InitMenu(); } public static void UpdatePauseMenuButton() { - if (pauseMenuButton != null) - { - if (Main.Debug) pauseMenuButton.Show(); - else pauseMenuButton.Hide(); - } + pauseMenuButton?.SetButtonVisible(Main.Debug); } - private void RestoreMenuOpennessState() { menuOpen = openMenuOnPause; } + private void OnOpenMenu() { menuOpen = openMenuOnPause; } private void ToggleMenu() { menuOpen = !menuOpen; openMenuOnPause = !openMenuOnPause; } - private void CloseMenu() { menuOpen = false; } + private void OnCloseMenu() { menuOpen = false; } private void OnGUI() { @@ -284,7 +283,7 @@ namespace NewHorizons.Utility.DebugTools.Menu UpdatePauseMenuButton(); // TODO: figure out how to clear this event list so that we don't pile up useless instances of the DebugMenu that can't get garbage collected - pauseMenuButton.OnClick += ToggleMenu; + pauseMenuButton.OnSubmitAction += ToggleMenu; submenus.ForEach(submenu => submenu.OnInit(this)); diff --git a/NewHorizons/Utility/Files/ImageUtilities.cs b/NewHorizons/Utility/Files/ImageUtilities.cs index 742e5310..ea0ceaf6 100644 --- a/NewHorizons/Utility/Files/ImageUtilities.cs +++ b/NewHorizons/Utility/Files/ImageUtilities.cs @@ -95,6 +95,10 @@ namespace NewHorizons.Utility.Files _textureCache.Clear(); } + /// + /// used specifically for projected slides. + /// also adds a border (to prevent weird visual bug) and makes the texture linear (otherwise the projected image is too bright). + /// public static Texture2D Invert(Texture2D texture) { var key = $"{texture.name} > invert"; @@ -122,7 +126,7 @@ namespace NewHorizons.Utility.Files } } - var newTexture = new Texture2D(texture.width, texture.height, texture.format, texture.mipmapCount != 1); + var newTexture = new Texture2D(texture.width, texture.height, texture.format, texture.mipmapCount != 1, true); newTexture.name = key; newTexture.SetPixels(pixels); newTexture.Apply(); @@ -297,6 +301,40 @@ namespace NewHorizons.Utility.Files return newImage; } + public static Color LerpColor(Color start, Color end, float amount) + { + return new Color(Mathf.Lerp(start.r, end.r, amount), Mathf.Lerp(start.g, end.g, amount), Mathf.Lerp(start.b, end.b, amount)); + } + + public static Texture2D LerpGreyscaleImageAlongX(Texture2D image, Color lightTintStart, Color darkTintStart, Color lightTintEnd, Color darkTintEnd) + { + var key = $"{image.name} > lerp greyscale {lightTintStart} {darkTintStart} {lightTintEnd} {darkTintEnd}"; + if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture; + + var pixels = image.GetPixels(); + for (int i = 0; i < pixels.Length; i++) + { + var amount = (i % image.width) / (float) image.width; + var lightTint = LerpColor(lightTintStart, lightTintEnd, amount); + var darkTint = LerpColor(darkTintStart, darkTintEnd, amount); + + pixels[i].r = Mathf.Lerp(darkTint.r, lightTint.r, pixels[i].r); + pixels[i].g = Mathf.Lerp(darkTint.g, lightTint.g, pixels[i].g); + pixels[i].b = Mathf.Lerp(darkTint.b, lightTint.b, pixels[i].b); + } + + var newImage = new Texture2D(image.width, image.height, image.format, image.mipmapCount != 1); + newImage.name = key; + newImage.SetPixels(pixels); + newImage.Apply(); + + newImage.wrapMode = image.wrapMode; + + _textureCache.Add(key, newImage); + + return newImage; + } + public static Texture2D ClearTexture(int width, int height, bool wrap = false) { var key = $"Clear {width} {height} {wrap}"; diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs index 1815ed87..9ded956b 100644 --- a/NewHorizons/Utility/NewHorizonExtensions.cs +++ b/NewHorizons/Utility/NewHorizonExtensions.cs @@ -351,7 +351,7 @@ namespace NewHorizons.Utility return parentNode.ChildNodes.Cast().First(node => node.LocalName == tagName); } - public static string TruncateWhitespace(this string text) + public static string TruncateWhitespaceAndToLower(this string text) { // return Regex.Replace(text.Trim(), @"[^\S\r\n]+", "GUH"); return Regex.Replace(text.Trim(), @"\s+", " ").ToLowerInvariant(); diff --git a/NewHorizons/Utility/OWML/NHLogger.cs b/NewHorizons/Utility/OWML/NHLogger.cs index 080cdcad..c539f43f 100644 --- a/NewHorizons/Utility/OWML/NHLogger.cs +++ b/NewHorizons/Utility/OWML/NHLogger.cs @@ -19,10 +19,17 @@ namespace NewHorizons.Utility.OWML Main.Instance.ModHelper.Console.WriteLine($"{Enum.GetName(typeof(LogType), type)} : {text}", LogTypeToMessageType(type)); } + public static void LogVerbose(params object[] obj) => LogVerbose(string.Join(", ", obj)); public static void LogVerbose(object text) => Log(text, LogType.Verbose); + public static void Log(object text) => Log(text, LogType.Log); + public static void Log(params object[] obj) => Log(string.Join(", ", obj)); + public static void LogWarning(object text) => Log(text, LogType.Warning); + public static void LogWarning(params object[] obj) => LogWarning(string.Join(", ", obj)); + public static void LogError(object text) => Log(text, LogType.Error); + public static void LogError(params object[] obj) => LogError(string.Join(", ", obj)); public enum LogType {