diff --git a/NewHorizons/Assets/textures/CloudEntry_GD_Island_d.png b/NewHorizons/Assets/textures/CloudEntry_GD_Island_d.png new file mode 100644 index 00000000..5ba6654b Binary files /dev/null and b/NewHorizons/Assets/textures/CloudEntry_GD_Island_d.png differ diff --git a/NewHorizons/Assets/textures/CloudExit__PlayerShip_d.png b/NewHorizons/Assets/textures/CloudExit__PlayerShip_d.png new file mode 100644 index 00000000..5e83973d Binary files /dev/null and b/NewHorizons/Assets/textures/CloudExit__PlayerShip_d.png differ diff --git a/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d.png b/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d.png index ece877d5..fc670c87 100644 Binary files a/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d.png and b/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d.png differ diff --git a/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d_greyscale.png b/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d_greyscale.png deleted file mode 100644 index e05049b1..00000000 Binary files a/NewHorizons/Assets/textures/OceanEntry_PlayerShip_d_greyscale.png and /dev/null differ diff --git a/NewHorizons/Assets/textures/OceanExit_PlayerShip_d.png b/NewHorizons/Assets/textures/OceanExit_PlayerShip_d.png index d8e33ac1..7acfe355 100644 Binary files a/NewHorizons/Assets/textures/OceanExit_PlayerShip_d.png and b/NewHorizons/Assets/textures/OceanExit_PlayerShip_d.png differ diff --git a/NewHorizons/Assets/textures/OceanExit_PlayerShip_d_greyscale.png b/NewHorizons/Assets/textures/OceanExit_PlayerShip_d_greyscale.png deleted file mode 100644 index bba05cc4..00000000 Binary files a/NewHorizons/Assets/textures/OceanExit_PlayerShip_d_greyscale.png and /dev/null differ diff --git a/NewHorizons/Components/SplashColourizer.cs b/NewHorizons/Components/SplashColourizer.cs new file mode 100644 index 00000000..140704b4 --- /dev/null +++ b/NewHorizons/Components/SplashColourizer.cs @@ -0,0 +1,265 @@ +using NewHorizons.External.Configs; +using NewHorizons.External.SerializableData; +using NewHorizons.Utility; +using NewHorizons.Utility.Files; +using NewHorizons.Utility.OuterWilds; +using NewHorizons.Utility.OWML; +using System.Collections.Generic; +using UnityEngine; + +namespace NewHorizons.Components; + +/// +/// When a Fluid Detector enters this volume, it's splash effects will get colourized to match whats on this planet +/// +public class SplashColourizer : MonoBehaviour +{ + public float _radius; + + private SphereShape _sphereShape; + + private Dictionary _cachedOriginalPrefabs = new(); + private Dictionary _cachedModifiedPrefabs = new(); + + private FluidDetector _playerDetector, _shipDetector, _probeDetector; + + private MColor _waterColour, _cloudColour, _plasmaColour, _sandColour; + + private GameObject _prefabHolder; + + private bool _probeInsideVolume; + + private List _customTextures = new(); + + public void Awake() + { + var volume = new GameObject("Volume"); + volume.transform.parent = transform; + volume.transform.localPosition = Vector3.zero; + volume.layer = Layer.BasicEffectVolume; + + _sphereShape = gameObject.AddComponent(); + _sphereShape.radius = _radius; + + volume.AddComponent(); + + _prefabHolder = new GameObject("Prefabs"); + _prefabHolder.SetActive(false); + } + + public static void Make(GameObject planet, PlanetConfig config, float soi) + { + var water = config.Water?.tint; + var cloud = config.Atmosphere?.clouds?.tint; + var plasma = config.Lava?.tint ?? config.Star?.tint; + var sand = config.Sand?.tint; + + if (water != null || cloud != null || plasma != null || sand != null) + { + var size = Mathf.Max( + soi / 1.5f, + config.Water?.size ?? 0f, + config.Atmosphere?.clouds?.outerCloudRadius ?? 0f, + config.Lava?.size ?? 0f, + config.Star?.size ?? 0f, + config.Sand?.size ?? 0f + ) * 1.5f; + + var colourizer = planet.AddComponent(); + + colourizer._radius = size; + if (colourizer._sphereShape != null) colourizer._sphereShape.radius = size; + + colourizer._waterColour = water; + colourizer._cloudColour = cloud; + colourizer._plasmaColour = plasma; + colourizer._sandColour = sand; + } + } + + public void Start() + { + // Cache all prefabs + CachePrefabs(_playerDetector = Locator.GetPlayerDetector().GetComponent()); + CachePrefabs(_shipDetector = Locator.GetShipDetector().GetComponent()); + CachePrefabs(_probeDetector = Locator.GetProbe().GetDetectorObject().GetComponent()); + + GlobalMessenger.AddListener("RetrieveProbe", OnRetrieveProbe); + + // Check if player/ship are already inside + if ((_playerDetector.transform.position - transform.position).magnitude < _radius) + { + SetSplashEffects(_playerDetector, true); + } + if ((_shipDetector.transform.position - transform.position).magnitude < _radius) + { + SetSplashEffects(_shipDetector, true); + } + } + + public void OnDestroy() + { + GlobalMessenger.RemoveListener("RetrieveProbe", OnRetrieveProbe); + } + + private void OnRetrieveProbe(SurveyorProbe probe) + { + if (_probeInsideVolume) + { + // Else it never leaves the volume + SetProbeSplashEffects(false); + } + } + + public void OnTriggerEnter(Collider hitCollider) => OnEnterExit(hitCollider, true); + + public void OnTriggerExit(Collider hitCollider) => OnEnterExit(hitCollider, false); + + /// + /// The probe keeps being null idgi + /// + /// + private bool IsProbeLaunched() + { + return Locator.GetProbe()?.IsLaunched() ?? false; + } + + private void OnEnterExit(Collider hitCollider, bool entering) + { + if (!enabled) return; + + if (hitCollider.attachedRigidbody != null) + { + var isPlayer = hitCollider.attachedRigidbody.CompareTag("Player"); + var isShip = hitCollider.attachedRigidbody.CompareTag("Ship"); + var isProbe = hitCollider.attachedRigidbody.CompareTag("Probe"); + + if (isPlayer) + { + SetSplashEffects(_playerDetector, entering); + if (IsProbeLaunched()) + { + SetProbeSplashEffects(entering); + } + } + else if (isShip) + { + SetSplashEffects(_shipDetector, entering); + if (PlayerState.IsInsideShip()) + { + SetSplashEffects(_playerDetector, entering); + } + if (IsProbeLaunched()) + { + SetProbeSplashEffects(entering); + } + } + else if (isProbe) + { + SetProbeSplashEffects(entering); + } + + // If the probe isn't launched we always consider it as being inside the volume + if (isProbe || !IsProbeLaunched()) + { + _probeInsideVolume = entering; + } + } + } + + public void CachePrefabs(FluidDetector detector) + { + foreach (var splashEffect in detector._splashEffects) + { + if (!_cachedOriginalPrefabs.ContainsKey(splashEffect)) + { + _cachedOriginalPrefabs[splashEffect] = splashEffect.splashPrefab; + } + if (!_cachedModifiedPrefabs.ContainsKey(splashEffect)) + { + Color? colour = null; + if (splashEffect.fluidType == FluidVolume.Type.CLOUD) + { + colour = _cloudColour?.ToColor(); + } + switch(splashEffect.fluidType) + { + case FluidVolume.Type.CLOUD: + colour = _cloudColour?.ToColor(); + break; + case FluidVolume.Type.WATER: + colour = _waterColour?.ToColor(); + break; + case FluidVolume.Type.PLASMA: + colour = _plasmaColour?.ToColor(); + break; + case FluidVolume.Type.SAND: + colour = _sandColour?.ToColor(); + break; + } + + if (colour is Color tint) + { + var flagError = false; + var prefab = splashEffect.splashPrefab.InstantiateInactive(); + var meshRenderers = prefab.GetComponentsInChildren(true); + foreach (var meshRenderer in meshRenderers) + { + if (_customTextures.Contains(meshRenderer.material.mainTexture)) + { + // Might be some shared material stuff? This image is already tinted, so skip it + continue; + } + + // Can't access the textures in memory so we need to have our own copies + var texture = ImageUtilities.GetTexture(Main.Instance, $"Assets/textures/{meshRenderer.material.mainTexture.name}.png"); + if (texture == null) + { + NHLogger.LogError($"Go tell an NH dev to add this image texture to the mod because I can't be bothered to until somebody complains: {meshRenderer.material.mainTexture.name}"); + GameObject.Destroy(prefab); + flagError = true; + } + + _customTextures.Add(texture); + + meshRenderer.material = new(meshRenderer.material) + { + color = Color.white, + mainTexture = ImageUtilities.TintImage(texture, tint) + }; + } + + if (flagError) continue; + + // Have to be active when being used by the base game classes but can't be seen + // Keep them active as children of an inactive game object + prefab.transform.parent = _prefabHolder.transform; + prefab.SetActive(true); + + _cachedModifiedPrefabs[splashEffect] = prefab; + } + } + } + } + + public void SetSplashEffects(FluidDetector detector, bool entering) + { + NHLogger.LogVerbose($"Body {detector.name} {(entering ? "entered" : "left")} colourizing volume on {name}"); + + foreach (var splashEffect in detector._splashEffects) + { + var prefabs = entering ? _cachedModifiedPrefabs : _cachedOriginalPrefabs; + if (prefabs.TryGetValue(splashEffect, out var prefab)) + { + splashEffect.splashPrefab = prefab; + } + } + } + + public void SetProbeSplashEffects(bool entering) + { + _probeInsideVolume = entering; + + SetSplashEffects(_probeDetector, entering); + } +} diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index aaeec71e..8fe48397 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -20,6 +20,10 @@ using System; using System.Collections.Generic; using System.Linq; using UnityEngine; +using NewHorizons.Streaming; +using Newtonsoft.Json; +using NewHorizons.External.Modules.VariableSize; +using NewHorizons.Components; namespace NewHorizons.Handlers { @@ -764,6 +768,8 @@ namespace NewHorizons.Handlers SpawnPointBuilder.Make(go, body.Config.Spawn, rb); } + SplashColourizer.Make(go, body.Config, sphereOfInfluence); + // We allow removing children afterwards so you can also take bits off of the modules you used if (body.Config.removeChildren != null) RemoveChildren(go, body); diff --git a/NewHorizons/Utility/Files/ImageUtilities.cs b/NewHorizons/Utility/Files/ImageUtilities.cs index 09ff831f..e124fe89 100644 --- a/NewHorizons/Utility/Files/ImageUtilities.cs +++ b/NewHorizons/Utility/Files/ImageUtilities.cs @@ -43,7 +43,17 @@ namespace NewHorizons.Utility.Files return null; } // Copied from OWML but without the print statement lol - var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename); + string path; + try + { + path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename); + } + catch (Exception e) + { + NHLogger.LogError($"Invalid path: Couldn't combine {mod.ModHelper.Manifest.ModFolderPath} {filename} - {e}"); + return null; + } + var key = GetKey(path); if (_textureCache.TryGetValue(key, out var existingTexture)) {