Merge branch 'dev' into scene-load-fixes

This commit is contained in:
Nick 2024-04-04 18:47:13 -04:00
commit ff999c271c
46 changed files with 579 additions and 246 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -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)

View File

@ -1,6 +1,7 @@
using NewHorizons.External.Modules;
using NewHorizons.External.Modules.VariableSize;
using UnityEngine;
namespace NewHorizons.Builder.Atmosphere
{
public static class SunOverrideBuilder

View File

@ -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))

View File

@ -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)

View File

@ -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);
}

View File

@ -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<SignalFrequency>().Length;
_qmSignals = new (){ SearchUtilities.Find("QuantumMoon_Body/Signal_Quantum").GetComponent<AudioSignal>() };
_qmSignals = new () { SearchUtilities.Find("QuantumMoon_Body/Signal_Quantum").GetComponent<AudioSignal>() };
_cloakedSignals = new();
Initialized = true;
SceneManager.sceneUnloaded -= OnSceneUnloaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
Main.Instance.OnStarSystemLoaded.RemoveListener(OnStarSystemLoaded);
Main.Instance.OnStarSystemLoaded.AddListener(OnStarSystemLoaded);
}
private static HashSet<SignalFrequency> _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<SignalFrequency> _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<SignalFrequency>(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<SignalFrequency>().Length;
// This stuff happens after the signalscope is Awake so we have to change the number of frequencies now
Object.FindObjectOfType<Signalscope>()._strongestSignals = new AudioSignal[NumberOfFrequencies + 1];
GameObject.FindObjectOfType<Signalscope>()._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;
}

View File

@ -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<FogWarpVolume> _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<FogWarpVolume>(true).Append(brambleNode.GetComponent<FogWarpVolume>()))
{
_nhFogWarpVolumes.Add(fogWarpVolume);
}
var innerFogWarpVolume = brambleNode.GetComponent<InnerFogWarpVolume>();
var outerFogWarpVolume = GetOuterFogWarpVolumeFromAstroObject(go);
var fogLight = brambleNode.GetComponent<FogLight>();
@ -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<SignalInfo>(JsonConvert.SerializeObject(signalConfig));
signalConfigCopy.parentPath = null;
signalConfigCopy.isRelativeToParent = false;
var signalGO = SignalBuilder.Make(go, sector, signalConfigCopy, mod);
signalGO.GetComponent<AudioSignal>()._identificationDistance = 0;
signalGO.GetComponent<AudioSignal>()._sourceRadius = 1;
signalGO.transform.position = brambleNode.transform.position;

View File

@ -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
/// <summary>
/// Fix components that have sectors. Has a specific fix if there is a VisionTorchItem on the object.
/// </summary>
private static void FixSectoredComponent(Component component, Sector sector, HashSet<Sector> existingSectors, bool keepLoaded)
private static void FixSectoredComponent(Component component, Sector sector, HashSet<Sector> 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

View File

@ -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);

View File

@ -79,6 +79,12 @@ namespace NewHorizons.Builder.Props
item.ClearPickupConditionOnDrop = info.clearPickupConditionOnDrop;
item.PickupFact = info.pickupFact;
if (info.colliderRadius > 0f)
{
go.AddComponent<SphereCollider>().radius = info.colliderRadius;
go.GetAddComponent<OWCollider>();
}
Delay.FireOnNextUpdate(() =>
{
if (item != null && !string.IsNullOrEmpty(info.pathToInitialSocket))

View File

@ -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<Image>();
@ -156,6 +163,12 @@ namespace NewHorizons.Builder.ShipLog
Rect imageRect = astroObject._imageObj.GetComponent<RectTransform>().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

View File

@ -0,0 +1,33 @@
using NewHorizons.Utility.OWML;
using UnityEngine;
namespace NewHorizons.Components.Fixers;
/// <summary>
/// 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
/// </summary>
internal class PlayerShipAtmosphereDetectorFix : MonoBehaviour
{
private PlayerCameraFluidDetector _fluidDetector;
private SimpleFluidVolume _shipAtmosphereVolume;
public void Start()
{
_fluidDetector = Locator.GetPlayerCameraDetector().GetComponent<PlayerCameraFluidDetector>();
_shipAtmosphereVolume = Locator.GetShipBody().transform.Find("Volumes/ShipAtmosphereVolume").GetComponent<SimpleFluidVolume>();
}
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);
}
}
}

View File

@ -19,6 +19,7 @@ namespace NewHorizons.Components.Props
{
var conditionalObjectActivationGO = new GameObject($"{go.name}_{condition}");
var component = conditionalObjectActivationGO.AddComponent<ConditionalObjectActivation>();
component.transform.parent = go.transform.parent;
component.GameObject = go;
component.DialogueCondition = condition;
component.CloseEyes = closeEyes;

View File

@ -33,13 +33,16 @@ namespace NewHorizons.Components.Sectored
}
private void Start()
{
DisableRenderers();
}
private void GetRenderers()
{
_renderers = gameObject.GetComponentsInChildren<Renderer>();
_tessellatedRenderers = gameObject.GetComponentsInChildren<TessellatedRenderer>();
_colliders = gameObject.GetComponentsInChildren<Collider>();
_lights = gameObject.GetComponentsInChildren<Light>();
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;
}
}
}

View File

@ -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)
{

View File

@ -1,8 +0,0 @@
namespace NewHorizons.Components.Volumes
{
public class NHInnerFogWarpVolume : InnerFogWarpVolume
{
public override bool IsProbeOnly() => _exitRadius <= 6;
public override float GetFogThickness() => _exitRadius;
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -54,7 +54,9 @@ namespace NewHorizons.External.Modules.Props
public string quantumGroupID;
/// <summary>
/// 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.
/// </summary>
public bool keepLoaded;

View File

@ -19,7 +19,7 @@ namespace NewHorizons.External.Modules.Props.Item
/// </summary>
public string name;
/// <summary>
/// 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.
/// </summary>
public string itemType;
/// <summary>
@ -27,6 +27,11 @@ namespace NewHorizons.External.Modules.Props.Item
/// </summary>
[DefaultValue(2f)] public float interactRange = 2f;
/// <summary>
/// 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.
/// </summary>
[DefaultValue(0.5f)] public float colliderRadius = 0.5f;
/// <summary>
/// Whether the item can be dropped. Defaults to true.
/// </summary>
[DefaultValue(true)] public bool droppable = true;

View File

@ -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<string> 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()

View File

@ -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<EffectVolume>())
{
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)

View File

@ -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<CanvasGroup>().alpha = 1;
graphic = GetComponent<Graphic>();
image = GetComponent<UnityEngine.UI.Image>();
graphic.enabled = true;
image.enabled = true;
var image = GetComponent<Image>();
eoteSprite = image.sprite;
image.sprite = null;
image.enabled = false;
var layout = GetComponent<LayoutElement>();
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<Image>();
_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<Graphic>();
_subtitleDisplay.gameObject.GetAddComponent<LayoutElement>().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;
}
}
}

View File

@ -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<TimeLoopController>();

View File

@ -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<Language, Dictionary<string, string>> 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("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", "");
var key = originalKey.Replace("\\n", "\n").Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", "").TruncateWhitespaceAndToLower();
var value = config.DialogueDictionary[originalKey].Replace("\\n", "\n").Replace("&lt;", "<").Replace("&gt;", ">").Replace("<![CDATA[", "").Replace("]]>", "");
if (!_dialogueTranslationDictionary[language].ContainsKey(key)) _dialogueTranslationDictionary[language].Add(key, value);

View File

@ -207,5 +207,13 @@ namespace NewHorizons
/// <returns></returns>
string GetTranslationForOtherText(string text);
#endregion
/// <summary>
/// Registers a subtitle for the main menu.
/// Call this once before the main menu finishes loading
/// </summary>
/// <param name="mod"></param>
/// <param name="filePath"></param>
void AddSubtitle(IModBehaviour mod, string filePath);
}
}

View File

@ -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<DebugRaycaster>();
Locator.GetPlayerBody().gameObject.AddComponent<DebugPropPlacer>();
Locator.GetPlayerBody().gameObject.AddComponent<DebugMenu>();
Locator.GetPlayerBody().gameObject.AddComponent<PlayerShipAtmosphereDetectorFix>();
PlayerSpawnHandler.OnSystemReady(shouldWarpInFromShip, shouldWarpInFromVessel);

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" />
<PackageReference Include="OWML" Version="2.9.8" />
<PackageReference Include="OWML" Version="2.11.1" />
<Reference Include="../Lib/System.ComponentModel.Annotations.dll" />
</ItemGroup>
<ItemGroup>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<string> _failedFiles = new();
public static void Init()
{
_menuApi = Main.Instance.ModHelper.Interaction.TryGetModApi<IMenuAPI>("_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();

View File

@ -0,0 +1,26 @@
using HarmonyLib;
namespace NewHorizons.Patches;
/// <summary>
/// Bug fix from the Outsider
/// </summary>
[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();
}
}

View File

@ -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;
}
/// <summary>
/// Should fix a bug where disabled a CharacterDialogueTree makes its related RemoteDialogueTriggers softlock your game
/// </summary>

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -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);
}

View File

@ -26,5 +26,25 @@ namespace NewHorizons.Patches.VolumePatches
return false;
}
/// <summary>
/// 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
/// </summary>
[HarmonyPrefix]
[HarmonyPatch(nameof(DestructionVolume.VanishNomaiShuttle))]
public static bool DestructionVolume_VanishNomaiShuttle(DestructionVolume __instance, OWRigidbody shuttleBody, RelativeLocationData entryLocation)
{
if (shuttleBody.GetComponentInChildren<NomaiShuttleController>() == null)
{
if (__instance._nomaiShuttleBody == shuttleBody)
{
__instance._nomaiShuttleBody = null;
}
return false;
}
return true;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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.",

View File

@ -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()

View File

@ -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<DebugSubmenu> 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));

View File

@ -95,6 +95,10 @@ namespace NewHorizons.Utility.Files
_textureCache.Clear();
}
/// <summary>
/// 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).
/// </summary>
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}";

View File

@ -351,7 +351,7 @@ namespace NewHorizons.Utility
return parentNode.ChildNodes.Cast<XmlNode>().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();

View File

@ -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
{