Merge branch 'dev' into title-screen-config
BIN
NewHorizons/Assets/DefaultMapModNoAtmoOutline.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
NewHorizons/Assets/DefaultMapModePlanetOutline.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
NewHorizons/Assets/DefaultMapModeStarOutline.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
NewHorizons/Assets/textures/CloudEntry_GD_Island_d.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
NewHorizons/Assets/textures/CloudExit__PlayerShip_d.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
NewHorizons/Assets/textures/Ice.png
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.4 MiB |
BIN
NewHorizons/Assets/textures/Quantum.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
NewHorizons/Assets/textures/Rocks.png
Normal file
|
After Width: | Height: | Size: 564 KiB |
@ -87,6 +87,9 @@ namespace NewHorizons.Builder.Atmosphere
|
||||
MR.material.SetFloat(Radius, atmo.fogSize);
|
||||
MR.material.SetFloat(Density, atmo.fogDensity);
|
||||
MR.material.SetFloat(DensityExponent, 1);
|
||||
// We apply fogTint to the material and tint the fog ramp, which means the ramp and tint get multiplied together in the shader, so tint is applied twice
|
||||
// However nobody has visually complained about this, so we don't want to change it until maybe somebody does
|
||||
// Was previously documented by issue #747.
|
||||
MR.material.SetTexture(ColorRampTexture, colorRampTexture);
|
||||
|
||||
fogGO.transform.position = planetGO.transform.position;
|
||||
|
||||
@ -39,7 +39,8 @@ namespace NewHorizons.Builder.Body
|
||||
{
|
||||
surfaceGravity = belt.gravity,
|
||||
surfaceSize = size,
|
||||
gravityFallOff = GravityFallOff.InverseSquared
|
||||
gravityFallOff = GravityFallOff.InverseSquared,
|
||||
hasFluidDetector = false
|
||||
};
|
||||
|
||||
config.Orbit = new OrbitModule()
|
||||
@ -95,6 +96,7 @@ namespace NewHorizons.Builder.Body
|
||||
color = new MColor(126, 94, 73)
|
||||
};
|
||||
}
|
||||
config.AmbientLights = new[] { new AmbientLightModule() { outerRadius = size * 1.2f, intensity = 0.1f } };
|
||||
|
||||
var asteroid = new NewHorizonsBody(config, mod);
|
||||
PlanetCreationHandler.GenerateBody(asteroid);
|
||||
|
||||
@ -100,6 +100,11 @@ namespace NewHorizons.Builder.Body.Geometry
|
||||
mesh.normals = normals;
|
||||
mesh.uv = uvs;
|
||||
|
||||
mesh.RecalculateBounds();
|
||||
// Unity recalculate normals does not support smooth normals
|
||||
//mesh.RecalculateNormals();
|
||||
mesh.RecalculateTangents();
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ namespace NewHorizons.Builder.Body
|
||||
{
|
||||
public static class HeightMapBuilder
|
||||
{
|
||||
public static Shader PlanetShader;
|
||||
private static Shader _planetShader;
|
||||
|
||||
// I hate nested functions okay
|
||||
private static IModBehaviour _currentMod;
|
||||
@ -62,7 +62,7 @@ namespace NewHorizons.Builder.Body
|
||||
cubeSphere.SetActive(false);
|
||||
cubeSphere.transform.SetParent(sector?.transform ?? planetGO.transform, false);
|
||||
|
||||
if (PlanetShader == null) PlanetShader = AssetBundleUtilities.NHAssetBundle.LoadAsset<Shader>("Assets/Shaders/SphereTextureWrapperTriplanar.shader");
|
||||
if (_planetShader == null) _planetShader = AssetBundleUtilities.NHAssetBundle.LoadAsset<Shader>("Assets/Shaders/SphereTextureWrapperTriplanar.shader");
|
||||
|
||||
var stretch = module.stretch != null ? (Vector3)module.stretch : Vector3.one;
|
||||
|
||||
@ -115,7 +115,7 @@ namespace NewHorizons.Builder.Body
|
||||
LODCubeSphere.AddComponent<MeshFilter>().mesh = CubeSphere.Build(resolution, heightMap, module.minHeight, module.maxHeight, stretch);
|
||||
|
||||
var cubeSphereMR = LODCubeSphere.AddComponent<MeshRenderer>();
|
||||
var material = new Material(PlanetShader);
|
||||
var material = new Material(_planetShader);
|
||||
cubeSphereMR.material = material;
|
||||
material.name = textureMap.name;
|
||||
|
||||
|
||||
@ -1,21 +1,50 @@
|
||||
using NewHorizons.Builder.Body.Geometry;
|
||||
using NewHorizons.External.Modules;
|
||||
using NewHorizons.Utility;
|
||||
using NewHorizons.Utility.Files;
|
||||
using OWML.Common;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NewHorizons.Builder.Body
|
||||
{
|
||||
public static class ProcGenBuilder
|
||||
{
|
||||
private static Material quantumMaterial;
|
||||
private static Material iceMaterial;
|
||||
private static Material _material;
|
||||
private static Shader _planetShader;
|
||||
|
||||
public static GameObject Make(GameObject planetGO, Sector sector, ProcGenModule module)
|
||||
private static Dictionary<ProcGenModule, Material> _materialCache = new();
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
if (quantumMaterial == null) quantumMaterial = SearchUtilities.FindResourceOfTypeAndName<Material>("Rock_QM_EyeRock_mat");
|
||||
if (iceMaterial == null) iceMaterial = SearchUtilities.FindResourceOfTypeAndName<Material>("Rock_BH_IceSpike_mat");
|
||||
foreach (var material in _materialCache.Values)
|
||||
{
|
||||
Object.Destroy(material);
|
||||
}
|
||||
_materialCache.Clear();
|
||||
}
|
||||
|
||||
private static Material MakeMaterial()
|
||||
{
|
||||
var material = new Material(_planetShader);
|
||||
|
||||
GameObject icosphere = new GameObject("Icosphere");
|
||||
var keyword = "BASE_TILE";
|
||||
var prefix = "_BaseTile";
|
||||
|
||||
material.SetFloat(prefix, 1);
|
||||
material.EnableKeyword(keyword);
|
||||
|
||||
material.SetTexture("_BlendMap", ImageUtilities.MakeSolidColorTexture(1, 1, Color.white));
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
public static GameObject Make(IModBehaviour mod, GameObject planetGO, Sector sector, ProcGenModule module)
|
||||
{
|
||||
if (_planetShader == null) _planetShader = AssetBundleUtilities.NHAssetBundle.LoadAsset<Shader>("Assets/Shaders/SphereTextureWrapperTriplanar.shader");
|
||||
if (_material == null) _material = MakeMaterial();
|
||||
|
||||
var icosphere = new GameObject("Icosphere");
|
||||
icosphere.SetActive(false);
|
||||
icosphere.transform.parent = sector?.transform ?? planetGO.transform;
|
||||
icosphere.transform.rotation = Quaternion.Euler(90, 0, 0);
|
||||
@ -26,8 +55,61 @@ namespace NewHorizons.Builder.Body
|
||||
icosphere.AddComponent<MeshFilter>().mesh = mesh;
|
||||
|
||||
var cubeSphereMR = icosphere.AddComponent<MeshRenderer>();
|
||||
cubeSphereMR.material = new Material(Shader.Find("Standard"));
|
||||
cubeSphereMR.material.color = module.color != null ? module.color.ToColor() : Color.white;
|
||||
|
||||
if (!_materialCache.TryGetValue(module, out var material))
|
||||
{
|
||||
material = new Material(_material);
|
||||
material.name = planetGO.name;
|
||||
if (module.material == ProcGenModule.Material.Default)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(module.texture))
|
||||
{
|
||||
material.SetTexture($"_BaseTileAlbedo", ImageUtilities.GetTexture(mod, module.texture, wrap: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
material.mainTexture = ImageUtilities.MakeSolidColorTexture(1, 1, module.color?.ToColor() ?? Color.white);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(module.smoothnessMap))
|
||||
{
|
||||
material.SetTexture($"_BaseTileSmoothnessMap", ImageUtilities.GetTexture(mod, module.smoothnessMap, wrap: true));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(module.normalMap))
|
||||
{
|
||||
material.SetFloat($"_BaseTileBumpStrength", module.normalStrength);
|
||||
material.SetTexture($"_BaseTileBumpMap", ImageUtilities.GetTexture(mod, module.normalMap, wrap: true));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (module.material)
|
||||
{
|
||||
case ProcGenModule.Material.Ice:
|
||||
material.SetTexture($"_BaseTileAlbedo", ImageUtilities.GetTexture(Main.Instance, "Assets/textures/Ice.png", wrap: true));
|
||||
break;
|
||||
case ProcGenModule.Material.Quantum:
|
||||
material.SetTexture($"_BaseTileAlbedo", ImageUtilities.GetTexture(Main.Instance, "Assets/textures/Quantum.png", wrap: true));
|
||||
break;
|
||||
case ProcGenModule.Material.Rock:
|
||||
material.SetTexture($"_BaseTileAlbedo", ImageUtilities.GetTexture(Main.Instance, "Assets/textures/Rocks.png", wrap: true));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
material.SetFloat($"_BaseTileScale", 5 / module.scale);
|
||||
if (module.color != null)
|
||||
{
|
||||
material.color = module.color.ToColor();
|
||||
}
|
||||
}
|
||||
|
||||
material.SetFloat("_Smoothness", module.smoothness);
|
||||
material.SetFloat("_Metallic", module.metallic);
|
||||
|
||||
_materialCache[module] = material;
|
||||
}
|
||||
|
||||
cubeSphereMR.sharedMaterial = material;
|
||||
|
||||
var cubeSphereMC = icosphere.AddComponent<MeshCollider>();
|
||||
cubeSphereMC.sharedMesh = mesh;
|
||||
|
||||
@ -162,7 +162,7 @@ namespace NewHorizons.Builder.Body
|
||||
GameObject procGen = null;
|
||||
if (body.Config.ProcGen != null)
|
||||
{
|
||||
procGen = ProcGenBuilder.Make(proxy, null, body.Config.ProcGen);
|
||||
procGen = ProcGenBuilder.Make(body.Mod, proxy, null, body.Config.ProcGen);
|
||||
if (realSize < body.Config.ProcGen.scale) realSize = body.Config.ProcGen.scale;
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ namespace NewHorizons.Builder.Props
|
||||
|
||||
private static void SceneManager_sceneUnloaded(Scene scene)
|
||||
{
|
||||
// would be nice to only clear when system changes, but fixed prefabs rely on stuff in the scene
|
||||
foreach (var prefab in _fixedPrefabCache.Values)
|
||||
{
|
||||
UnityEngine.Object.Destroy(prefab.prefab);
|
||||
@ -155,6 +156,17 @@ namespace NewHorizons.Builder.Props
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We used to set SectorCullGroup._controllingProxy to null. Now we do not.
|
||||
* This may break things on copied details because it prevents SetSector from doing anything,
|
||||
* so that part of the detail might be culled by wrong sector.
|
||||
* So if you copy something from e.g. Giants Deep it might turn off the detail if you arent in
|
||||
* the sector of the thing you copied from (since it's still pointing to the original proxy,
|
||||
* which has the original sector at giants deep there)
|
||||
*
|
||||
* Anyway nobody has complained about this for the year it's been like that so closing issue #831 until
|
||||
* this affects somebody
|
||||
*/
|
||||
|
||||
FixSectoredComponent(component, sector, existingSectors);
|
||||
}
|
||||
|
||||
@ -219,15 +231,10 @@ namespace NewHorizons.Builder.Props
|
||||
|
||||
if (detail.removeChildren != null)
|
||||
{
|
||||
var detailPath = prop.transform.GetPath();
|
||||
var transforms = prop.GetComponentsInChildren<Transform>(true);
|
||||
foreach (var childPath in detail.removeChildren)
|
||||
{
|
||||
// Multiple children can have the same path so we delete all that match
|
||||
var path = $"{detailPath}/{childPath}";
|
||||
|
||||
var flag = true;
|
||||
foreach (var childObj in transforms.Where(x => x.GetPath() == path))
|
||||
foreach (var childObj in prop.transform.FindAll(childPath))
|
||||
{
|
||||
flag = false;
|
||||
childObj.gameObject.SetActive(false);
|
||||
@ -263,7 +270,7 @@ namespace NewHorizons.Builder.Props
|
||||
UnityEngine.Object.DestroyImmediate(prop);
|
||||
prop = newDetailGO;
|
||||
}
|
||||
|
||||
|
||||
if (isItem)
|
||||
{
|
||||
// Else when you put them down you can't pick them back up
|
||||
@ -275,7 +282,7 @@ namespace NewHorizons.Builder.Props
|
||||
|
||||
// For DLC related props
|
||||
// Make sure to do this before its set active
|
||||
if (!string.IsNullOrEmpty(detail?.path) &&
|
||||
if (!string.IsNullOrEmpty(detail?.path) &&
|
||||
(detail.path.ToLowerInvariant().StartsWith("ringworld") || detail.path.ToLowerInvariant().StartsWith("dreamworld")))
|
||||
{
|
||||
prop.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
|
||||
@ -294,7 +301,7 @@ namespace NewHorizons.Builder.Props
|
||||
|
||||
if (!string.IsNullOrEmpty(detail.activationCondition))
|
||||
{
|
||||
ConditionalObjectActivation.SetUp(prop, detail.activationCondition, detail.blinkWhenActiveChanged, true);
|
||||
ConditionalObjectActivation.SetUp(prop, detail.activationCondition, detail.blinkWhenActiveChanged, true);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(detail.deactivationCondition))
|
||||
{
|
||||
@ -568,22 +575,22 @@ namespace NewHorizons.Builder.Props
|
||||
|
||||
// Manually copied these values from a artifact lantern so that we don't have to find it (works in Eye)
|
||||
lantern._origLensFlareBrightness = 0f;
|
||||
lantern._focuserPetalsBaseEulerAngles = new Vector3[]
|
||||
{
|
||||
new Vector3(0.7f, 270.0f, 357.5f),
|
||||
new Vector3(288.7f, 270.1f, 357.4f),
|
||||
lantern._focuserPetalsBaseEulerAngles = new Vector3[]
|
||||
{
|
||||
new Vector3(0.7f, 270.0f, 357.5f),
|
||||
new Vector3(288.7f, 270.1f, 357.4f),
|
||||
new Vector3(323.3f, 90.0f, 177.5f),
|
||||
new Vector3(35.3f, 90.0f, 177.5f),
|
||||
new Vector3(72.7f, 270.1f, 357.5f)
|
||||
new Vector3(35.3f, 90.0f, 177.5f),
|
||||
new Vector3(72.7f, 270.1f, 357.5f)
|
||||
};
|
||||
lantern._dirtyFlag_focus = true;
|
||||
lantern._concealerRootsBaseScale = new Vector3[]
|
||||
lantern._concealerRootsBaseScale = new Vector3[]
|
||||
{
|
||||
Vector3.one,
|
||||
Vector3.one,
|
||||
Vector3.one
|
||||
};
|
||||
lantern._concealerCoversStartPos = new Vector3[]
|
||||
lantern._concealerCoversStartPos = new Vector3[]
|
||||
{
|
||||
new Vector3(0.0f, 0.0f, 0.0f),
|
||||
new Vector3(0.0f, -0.1f, 0.0f),
|
||||
@ -594,7 +601,7 @@ namespace NewHorizons.Builder.Props
|
||||
};
|
||||
lantern._dirtyFlag_concealment = true;
|
||||
lantern.UpdateVisuals();
|
||||
|
||||
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ namespace NewHorizons.Builder.Props
|
||||
travelerData.requirementsMet = false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(info.requiredPersistentCondition) && !DialogueConditionManager.SharedInstance.GetConditionState(info.requiredPersistentCondition))
|
||||
if (!string.IsNullOrEmpty(info.requiredPersistentCondition) && !PlayerData.GetPersistentCondition(info.requiredPersistentCondition))
|
||||
{
|
||||
travelerData.requirementsMet = false;
|
||||
}
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
using NewHorizons.Components.ShipLog;
|
||||
using NewHorizons.External;
|
||||
using NewHorizons.External.Modules;
|
||||
using NewHorizons.External.Modules.VariableSize;
|
||||
using NewHorizons.Handlers;
|
||||
using NewHorizons.Utility;
|
||||
using NewHorizons.Utility.Files;
|
||||
using NewHorizons.Utility.OuterWilds;
|
||||
using NewHorizons.Utility.OWML;
|
||||
using OWML.ModHelper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
@ -28,7 +26,7 @@ namespace NewHorizons.Builder.ShipLog
|
||||
if (_astroObjectToMapModeInfo.TryGetValue(slao, out var mapModeInfo))
|
||||
{
|
||||
return mapModeInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
@ -149,7 +147,7 @@ namespace NewHorizons.Builder.ShipLog
|
||||
|
||||
Rect rect = new Rect(0, 0, texture.width, texture.height);
|
||||
Vector2 pivot = new Vector2(texture.width / 2, texture.height / 2);
|
||||
newImage.sprite = Sprite.Create(texture, rect, pivot);
|
||||
newImage.sprite = Sprite.Create(texture, rect, pivot, 100, 0, SpriteMeshType.FullRect, Vector4.zero, false);
|
||||
|
||||
return newImageGO;
|
||||
}
|
||||
@ -190,15 +188,15 @@ namespace NewHorizons.Builder.ShipLog
|
||||
|
||||
Texture2D image = null;
|
||||
Texture2D outline = null;
|
||||
|
||||
|
||||
string imagePath = body.Config.ShipLog?.mapMode?.revealedSprite;
|
||||
string outlinePath = body.Config.ShipLog?.mapMode?.outlineSprite;
|
||||
|
||||
if (imagePath != null) image = ImageUtilities.GetTexture(body.Mod, imagePath);
|
||||
if (image == null) image = AutoGenerateMapModePicture(body);
|
||||
if (image == null) image = ImageUtilities.AutoGenerateMapModePicture(body);
|
||||
|
||||
if (outlinePath != null) outline = ImageUtilities.GetTexture(body.Mod, outlinePath);
|
||||
if (outline == null) outline = ImageUtilities.MakeOutline(image, Color.white, 10);
|
||||
if (outline == null) outline = ImageUtilities.GetCachedOutlineOrCreate(body, image, imagePath);
|
||||
|
||||
astroObject._imageObj = CreateImage(gameObject, image, body.Config.name + " Revealed", layer);
|
||||
astroObject._outlineObj = CreateImage(gameObject, outline, body.Config.name + " Outline", layer);
|
||||
@ -246,10 +244,10 @@ namespace NewHorizons.Builder.ShipLog
|
||||
string outlinePath = info.outlineSprite;
|
||||
|
||||
if (imagePath != null) image = ImageUtilities.GetTexture(body.Mod, imagePath);
|
||||
else image = AutoGenerateMapModePicture(body);
|
||||
else image = ImageUtilities.AutoGenerateMapModePicture(body);
|
||||
|
||||
if (outlinePath != null) outline = ImageUtilities.GetTexture(body.Mod, outlinePath);
|
||||
else outline = ImageUtilities.MakeOutline(image, Color.white, 10);
|
||||
else outline = ImageUtilities.GetCachedOutlineOrCreate(body, image, imagePath);
|
||||
|
||||
Image revealedImage = CreateImage(detailGameObject, image, "Detail Revealed", parent.gameObject.layer).GetComponent<Image>();
|
||||
Image outlineImage = CreateImage(detailGameObject, outline, "Detail Outline", parent.gameObject.layer).GetComponent<Image>();
|
||||
@ -591,7 +589,7 @@ namespace NewHorizons.Builder.ShipLog
|
||||
GameObject newNodeGO = CreateMapModeGameObject(node.mainBody, parent, layer, position);
|
||||
ShipLogAstroObject astroObject = AddShipLogAstroObject(newNodeGO, node.mainBody, greyScaleMaterial, layer);
|
||||
if (node.mainBody.Config.FocalPoint != null)
|
||||
{
|
||||
{
|
||||
astroObject._imageObj.GetComponent<Image>().enabled = false;
|
||||
astroObject._outlineObj.GetComponent<Image>().enabled = false;
|
||||
astroObject._unviewedObj.GetComponent<Image>().enabled = false;
|
||||
@ -605,68 +603,6 @@ namespace NewHorizons.Builder.ShipLog
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static Texture2D AutoGenerateMapModePicture(NewHorizonsBody body)
|
||||
{
|
||||
Texture2D texture;
|
||||
|
||||
if (body.Config.Star != null) texture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModeStar.png");
|
||||
else if (body.Config.Atmosphere != null) texture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModNoAtmo.png");
|
||||
else texture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModePlanet.png");
|
||||
|
||||
var color = GetDominantPlanetColor(body);
|
||||
var darkColor = new Color(color.r / 3f, color.g / 3f, color.b / 3f);
|
||||
|
||||
texture = ImageUtilities.LerpGreyscaleImage(texture, color, darkColor);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static Color GetDominantPlanetColor(NewHorizonsBody body)
|
||||
{
|
||||
try
|
||||
{
|
||||
var starColor = body.Config?.Star?.tint;
|
||||
if (starColor != null) return starColor.ToColor();
|
||||
|
||||
var atmoColor = body.Config.Atmosphere?.atmosphereTint;
|
||||
if (body.Config.Atmosphere?.clouds != null && atmoColor != null) return atmoColor.ToColor();
|
||||
|
||||
if (body.Config?.HeightMap?.textureMap != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var texture = ImageUtilities.GetTexture(body.Mod, body.Config.HeightMap.textureMap);
|
||||
var landColor = ImageUtilities.GetAverageColor(texture);
|
||||
if (landColor != null) return landColor;
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
var waterColor = body.Config.Water?.tint;
|
||||
if (waterColor != null) return waterColor.ToColor();
|
||||
|
||||
var lavaColor = body.Config.Lava?.tint;
|
||||
if (lavaColor != null) return lavaColor.ToColor();
|
||||
|
||||
var sandColor = body.Config.Sand?.tint;
|
||||
if (sandColor != null) return sandColor.ToColor();
|
||||
|
||||
switch (body.Config?.Props?.singularities?.FirstOrDefault()?.type)
|
||||
{
|
||||
case SingularityModule.SingularityType.BlackHole:
|
||||
return Color.black;
|
||||
case SingularityModule.SingularityType.WhiteHole:
|
||||
return Color.white;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
NHLogger.LogWarning($"Something went wrong trying to pick the colour for {body.Config.name} but I'm too lazy to fix it.");
|
||||
}
|
||||
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
#region Replacement
|
||||
private static List<(NewHorizonsBody, ModBehaviour, MapModeInfo)> _mapModIconsToUpdate = new();
|
||||
public static void TryReplaceExistingMapModeIcon(NewHorizonsBody body, ModBehaviour mod, MapModeInfo info)
|
||||
@ -687,7 +623,7 @@ namespace NewHorizons.Builder.ShipLog
|
||||
}
|
||||
|
||||
private static void ReplaceExistingMapModeIcon(NewHorizonsBody body, ModBehaviour mod, MapModeInfo info)
|
||||
{
|
||||
{
|
||||
var astroObject = _astroObjectToShipLog[body.Object];
|
||||
var gameObject = astroObject.gameObject;
|
||||
var layer = gameObject.layer;
|
||||
|
||||
@ -246,7 +246,7 @@ namespace NewHorizons.Builder.ShipLog
|
||||
Texture2D newTexture = ImageUtilities.GetTexture(body.Mod, relativePath);
|
||||
Rect rect = new Rect(0, 0, newTexture.width, newTexture.height);
|
||||
Vector2 pivot = new Vector2(newTexture.width / 2, newTexture.height / 2);
|
||||
return Sprite.Create(newTexture, rect, pivot);
|
||||
return Sprite.Create(newTexture, rect, pivot, 100, 0, SpriteMeshType.FullRect, Vector4.zero, false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@ -11,9 +11,8 @@ namespace NewHorizons.Builder.Volumes
|
||||
{
|
||||
var volume = VolumeBuilder.Make<LoadCreditsVolume>(planetGO, sector, info);
|
||||
|
||||
volume.creditsType = info.creditsType;
|
||||
volume.gameOverText = info.gameOverText;
|
||||
volume.deathType = EnumUtils.Parse(info.deathType.ToString(), DeathType.Default);
|
||||
volume.gameOver = info.gameOver;
|
||||
volume.deathType = info.deathType == null ? null : EnumUtils.Parse(info.deathType.ToString(), DeathType.Default);
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
138
NewHorizons/Components/NHGameOverManager.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using NewHorizons.External.Modules;
|
||||
using NewHorizons.External.SerializableEnums;
|
||||
using NewHorizons.Handlers;
|
||||
using NewHorizons.Utility.OWML;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NewHorizons.Components
|
||||
{
|
||||
public class NHGameOverManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Mod unique id to game over module list
|
||||
/// Done as a dictionary so that Reload Configs can overwrite entries per mod
|
||||
/// </summary>
|
||||
public static Dictionary<string, GameOverModule[]> gameOvers = new();
|
||||
|
||||
public static NHGameOverManager Instance { get; private set; }
|
||||
|
||||
private GameOverController _gameOverController;
|
||||
private PlayerCameraEffectController _playerCameraEffectController;
|
||||
|
||||
private GameOverModule[] _gameOvers;
|
||||
|
||||
private bool _gameOverSequenceStarted;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_gameOverController = FindObjectOfType<GameOverController>();
|
||||
_playerCameraEffectController = FindObjectOfType<PlayerCameraEffectController>();
|
||||
|
||||
_gameOvers = gameOvers.SelectMany(x => x.Value).ToArray();
|
||||
}
|
||||
|
||||
public void TryHijackDeathSequence()
|
||||
{
|
||||
var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.condition) && DialogueConditionManager.SharedInstance.GetConditionState(x.condition));
|
||||
if (!_gameOverSequenceStarted && gameOver != null && !Locator.GetDeathManager()._finishedDLC)
|
||||
{
|
||||
StartGameOverSequence(gameOver, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType)
|
||||
{
|
||||
_gameOverSequenceStarted = true;
|
||||
Delay.StartCoroutine(GameOver(gameOver, deathType));
|
||||
}
|
||||
|
||||
private IEnumerator GameOver(GameOverModule gameOver, DeathType? deathType)
|
||||
{
|
||||
OWInput.ChangeInputMode(InputMode.None);
|
||||
ReticleController.Hide();
|
||||
Locator.GetPromptManager().SetPromptsVisible(false);
|
||||
Locator.GetPauseCommandListener().AddPauseCommandLock();
|
||||
|
||||
// The PlayerCameraEffectController is what actually kills us, so convince it we're already dead
|
||||
Locator.GetDeathManager()._isDead = true;
|
||||
|
||||
var fadeLength = 2f;
|
||||
|
||||
if (Locator.GetDeathManager()._isDying)
|
||||
{
|
||||
// Player already died at this point, so don't fade
|
||||
fadeLength = 0f;
|
||||
}
|
||||
else if (deathType is DeathType nonNullDeathType)
|
||||
{
|
||||
_playerCameraEffectController.OnPlayerDeath(nonNullDeathType);
|
||||
fadeLength = _playerCameraEffectController._deathFadeLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wake up relaxed next loop
|
||||
PlayerData.SetLastDeathType(DeathType.Meditation);
|
||||
FadeHandler.FadeOut(fadeLength);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(fadeLength);
|
||||
|
||||
if (!string.IsNullOrEmpty(gameOver.text) && _gameOverController != null)
|
||||
{
|
||||
_gameOverController._deathText.text = TranslationHandler.GetTranslation(gameOver.text, TranslationHandler.TextType.UI);
|
||||
_gameOverController.SetupGameOverScreen(5f);
|
||||
|
||||
if (gameOver.colour != null)
|
||||
{
|
||||
_gameOverController._deathText.color = gameOver.colour.ToColor();
|
||||
}
|
||||
|
||||
// Make sure the fade handler is off now
|
||||
FadeHandler.FadeIn(0f);
|
||||
|
||||
// We set this to true to stop it from loading the credits scene, so we can do it ourselves
|
||||
_gameOverController._loading = true;
|
||||
|
||||
yield return new WaitUntil(ReadytoLoadCreditsScene);
|
||||
}
|
||||
|
||||
LoadCreditsScene(gameOver);
|
||||
}
|
||||
|
||||
private bool ReadytoLoadCreditsScene() => _gameOverController._fadedOutText && _gameOverController._textAnimator.IsComplete();
|
||||
|
||||
private void LoadCreditsScene(GameOverModule gameOver)
|
||||
{
|
||||
NHLogger.LogVerbose($"Load credits {gameOver.creditsType}");
|
||||
|
||||
switch (gameOver.creditsType)
|
||||
{
|
||||
case NHCreditsType.Fast:
|
||||
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
|
||||
break;
|
||||
case NHCreditsType.Final:
|
||||
LoadManager.LoadScene(OWScene.Credits_Final, LoadManager.FadeType.ToBlack);
|
||||
break;
|
||||
case NHCreditsType.Kazoo:
|
||||
TimelineObliterationController.s_hasRealityEnded = true;
|
||||
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
|
||||
break;
|
||||
default:
|
||||
// GameOverController disables post processing
|
||||
_gameOverController._flashbackCamera.postProcessing.enabled = true;
|
||||
// For some reason this isn't getting set sometimes
|
||||
AudioListener.volume = 1;
|
||||
GlobalMessenger.FireEvent("TriggerFlashback");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ namespace NewHorizons.Components.Props
|
||||
public bool CloseEyes;
|
||||
public bool SetActiveWithCondition;
|
||||
|
||||
private PlayerCameraEffectController _playerCameraEffectController;
|
||||
private static PlayerCameraEffectController _playerCameraEffectController;
|
||||
private bool _changeConditionOnExitConversation;
|
||||
private bool _inConversation;
|
||||
|
||||
@ -45,7 +45,7 @@ namespace NewHorizons.Components.Props
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
_playerCameraEffectController = GameObject.FindObjectOfType<PlayerCameraEffectController>();
|
||||
if (_playerCameraEffectController == null) _playerCameraEffectController = GameObject.FindObjectOfType<PlayerCameraEffectController>();
|
||||
GlobalMessenger<string, bool>.AddListener("DialogueConditionChanged", OnDialogueConditionChanged);
|
||||
GlobalMessenger.AddListener("ExitConversation", OnExitConversation);
|
||||
GlobalMessenger.AddListener("EnterConversation", OnEnterConversation);
|
||||
|
||||
@ -63,7 +63,7 @@ namespace NewHorizons.Components.ShipLog
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
if(VesselCoordinatePromptHandler.KnowsEyeCoordinates())
|
||||
{
|
||||
AddSystemCard("EyeOfTheUniverse");
|
||||
@ -279,7 +279,7 @@ namespace NewHorizons.Components.ShipLog
|
||||
{
|
||||
var rect = new Rect(0, 0, texture.width, texture.height);
|
||||
var pivot = new Vector2(texture.width / 2, texture.height / 2);
|
||||
return Sprite.Create(texture, rect, pivot);
|
||||
return Sprite.Create(texture, rect, pivot, 100, 0, SpriteMeshType.FullRect, Vector4.zero, false);
|
||||
}
|
||||
|
||||
private void OnTargetReferenceFrame(ReferenceFrame referenceFrame)
|
||||
|
||||
265
NewHorizons/Components/SplashColourizer.cs
Normal file
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// When a Fluid Detector enters this volume, it's splash effects will get colourized to match whats on this planet
|
||||
/// </summary>
|
||||
public class SplashColourizer : MonoBehaviour
|
||||
{
|
||||
public float _radius;
|
||||
|
||||
private SphereShape _sphereShape;
|
||||
|
||||
private Dictionary<SplashEffect, GameObject> _cachedOriginalPrefabs = new();
|
||||
private Dictionary<SplashEffect, GameObject> _cachedModifiedPrefabs = new();
|
||||
|
||||
private FluidDetector _playerDetector, _shipDetector, _probeDetector;
|
||||
|
||||
private MColor _waterColour, _cloudColour, _plasmaColour, _sandColour;
|
||||
|
||||
private GameObject _prefabHolder;
|
||||
|
||||
private bool _probeInsideVolume;
|
||||
|
||||
private List<Texture> _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>();
|
||||
_sphereShape.radius = _radius;
|
||||
|
||||
volume.AddComponent<OWTriggerVolume>();
|
||||
|
||||
_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<SplashColourizer>();
|
||||
|
||||
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<DynamicFluidDetector>());
|
||||
CachePrefabs(_shipDetector = Locator.GetShipDetector().GetComponent<ShipFluidDetector>());
|
||||
CachePrefabs(_probeDetector = Locator.GetProbe().GetDetectorObject().GetComponent<ProbeFluidDetector>());
|
||||
|
||||
GlobalMessenger<SurveyorProbe>.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<SurveyorProbe>.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);
|
||||
|
||||
/// <summary>
|
||||
/// The probe keeps being null idgi
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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<MeshRenderer>(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);
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ namespace NewHorizons.Components.Volumes
|
||||
public override void VanishPlayer(OWRigidbody playerBody, RelativeLocationData entryLocation)
|
||||
{
|
||||
Locator.GetPlayerAudioController().PlayOneShotInternal(AudioType.BH_BlackHoleEmission);
|
||||
FadeHandler.FadeOut(0.2f, false);
|
||||
Main.Instance.ChangeCurrentStarSystem(TargetSolarSystem, PlayerState.AtFlightConsole());
|
||||
PlayerSpawnHandler.TargetSpawnID = TargetSpawnID;
|
||||
}
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using NewHorizons.External.SerializableEnums;
|
||||
using NewHorizons.Handlers;
|
||||
using NewHorizons.Utility;
|
||||
using NewHorizons.Utility.OWML;
|
||||
using System.Collections;
|
||||
using NewHorizons.External.Modules;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
@ -10,78 +6,17 @@ namespace NewHorizons.Components.Volumes
|
||||
{
|
||||
internal class LoadCreditsVolume : BaseVolume
|
||||
{
|
||||
public NHCreditsType creditsType = NHCreditsType.Fast;
|
||||
|
||||
public string gameOverText;
|
||||
public DeathType deathType = DeathType.Default;
|
||||
|
||||
private GameOverController _gameOverController;
|
||||
private PlayerCameraEffectController _playerCameraEffectController;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_gameOverController = FindObjectOfType<GameOverController>();
|
||||
_playerCameraEffectController = FindObjectOfType<PlayerCameraEffectController>();
|
||||
}
|
||||
public GameOverModule gameOver;
|
||||
public DeathType? deathType;
|
||||
|
||||
public override void OnTriggerVolumeEntry(GameObject hitObj)
|
||||
{
|
||||
if (hitObj.CompareTag("PlayerDetector") && enabled)
|
||||
if (hitObj.CompareTag("PlayerDetector") && enabled && (string.IsNullOrEmpty(gameOver.condition) || DialogueConditionManager.SharedInstance.GetConditionState(gameOver.condition)))
|
||||
{
|
||||
// Have to run it off the mod behaviour since the game over controller disables everything
|
||||
Delay.StartCoroutine(GameOver());
|
||||
NHGameOverManager.Instance.StartGameOverSequence(gameOver, deathType);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator GameOver()
|
||||
{
|
||||
OWInput.ChangeInputMode(InputMode.None);
|
||||
ReticleController.Hide();
|
||||
Locator.GetPromptManager().SetPromptsVisible(false);
|
||||
Locator.GetPauseCommandListener().AddPauseCommandLock();
|
||||
|
||||
// The PlayerCameraEffectController is what actually kills us, so convince it we're already dead
|
||||
Locator.GetDeathManager()._isDead = true;
|
||||
|
||||
_playerCameraEffectController.OnPlayerDeath(deathType);
|
||||
|
||||
yield return new WaitForSeconds(_playerCameraEffectController._deathFadeLength);
|
||||
|
||||
if (!string.IsNullOrEmpty(gameOverText) && _gameOverController != null)
|
||||
{
|
||||
_gameOverController._deathText.text = TranslationHandler.GetTranslation(gameOverText, TranslationHandler.TextType.UI);
|
||||
_gameOverController.SetupGameOverScreen(5f);
|
||||
|
||||
// We set this to true to stop it from loading the credits scene, so we can do it ourselves
|
||||
_gameOverController._loading = true;
|
||||
|
||||
yield return new WaitUntil(ReadytoLoadCreditsScene);
|
||||
}
|
||||
|
||||
LoadCreditsScene();
|
||||
}
|
||||
|
||||
private bool ReadytoLoadCreditsScene() => _gameOverController._fadedOutText && _gameOverController._textAnimator.IsComplete();
|
||||
|
||||
public override void OnTriggerVolumeExit(GameObject hitObj) { }
|
||||
|
||||
private void LoadCreditsScene()
|
||||
{
|
||||
NHLogger.LogVerbose($"Load credits {creditsType}");
|
||||
|
||||
switch (creditsType)
|
||||
{
|
||||
case NHCreditsType.Fast:
|
||||
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
|
||||
break;
|
||||
case NHCreditsType.Final:
|
||||
LoadManager.LoadScene(OWScene.Credits_Final, LoadManager.FadeType.ToBlack);
|
||||
break;
|
||||
case NHCreditsType.Kazoo:
|
||||
TimelineObliterationController.s_hasRealityEnded = true;
|
||||
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
NewHorizons/External/Configs/AddonConfig.cs
vendored
@ -1,3 +1,4 @@
|
||||
using NewHorizons.External.Modules;
|
||||
using NewHorizons.OtherMods.AchievementsPlus;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -44,5 +45,11 @@ namespace NewHorizons.External.Configs
|
||||
/// The dimensions of the Echoes of the Eye subtitle is 669 x 67, so aim for that size
|
||||
/// </summary>
|
||||
public string subtitlePath = "subtitle.png";
|
||||
|
||||
/// <summary>
|
||||
/// Custom game over messages for this mod. This can either display a title card before looping like in EOTE, or show a message and roll credits like the various time loop escape endings.
|
||||
/// You must set a dialogue condition for the game over sequence to run.
|
||||
/// </summary>
|
||||
public GameOverModule[] gameOver;
|
||||
}
|
||||
}
|
||||
|
||||
28
NewHorizons/External/Configs/PlanetConfig.cs
vendored
@ -64,6 +64,11 @@ namespace NewHorizons.External.Configs
|
||||
/// </summary>
|
||||
public string[] removeChildren;
|
||||
|
||||
/// <summary>
|
||||
/// optimization. turn this off if you know you're generating a new body and aren't worried about other addons editing it.
|
||||
/// </summary>
|
||||
[DefaultValue(true)] public bool checkForExisting = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modules
|
||||
@ -530,7 +535,7 @@ namespace NewHorizons.External.Configs
|
||||
Spawn.shipSpawnPoints = new SpawnModule.ShipSpawnPoint[] { Spawn.shipSpawn };
|
||||
}
|
||||
|
||||
// Because these guys put TWO spawn points
|
||||
// Because these guys put TWO spawn points
|
||||
if (starSystem == "2walker2.OogaBooga" && name == "The Campground")
|
||||
{
|
||||
Spawn.playerSpawnPoints[0].isDefault = true;
|
||||
@ -658,6 +663,25 @@ namespace NewHorizons.External.Configs
|
||||
}
|
||||
}
|
||||
|
||||
if (Volumes?.creditsVolume != null)
|
||||
{
|
||||
foreach (var volume in Volumes.creditsVolume)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(volume.gameOverText))
|
||||
{
|
||||
if (volume.gameOver == null)
|
||||
{
|
||||
volume.gameOver = new();
|
||||
}
|
||||
volume.gameOver.text = volume.gameOverText;
|
||||
}
|
||||
if (volume.creditsType != null)
|
||||
{
|
||||
volume.gameOver.creditsType = (SerializableEnums.NHCreditsType)volume.creditsType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Base.invulnerableToSun)
|
||||
{
|
||||
Base.hasFluidDetector = false;
|
||||
@ -742,4 +766,4 @@ namespace NewHorizons.External.Configs
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
NewHorizons/External/Modules/GameOverModule.cs
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
using NewHorizons.External.SerializableData;
|
||||
using NewHorizons.External.SerializableEnums;
|
||||
using Newtonsoft.Json;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace NewHorizons.External.Modules
|
||||
{
|
||||
[JsonObject]
|
||||
public class GameOverModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Text displayed in orange on game over. For localization, put translations under UI.
|
||||
/// </summary>
|
||||
public string text;
|
||||
|
||||
/// <summary>
|
||||
/// Change the colour of the game over text. Leave empty to use the default orange.
|
||||
/// </summary>
|
||||
public MColor colour;
|
||||
|
||||
/// <summary>
|
||||
/// Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over.
|
||||
/// Note this is a regular dialogue condition, not a persistent condition.
|
||||
/// </summary>
|
||||
public string condition;
|
||||
|
||||
/// <summary>
|
||||
/// The type of credits that will run after the game over message is shown
|
||||
/// </summary>
|
||||
[DefaultValue("fast")] public NHCreditsType creditsType = NHCreditsType.Fast;
|
||||
}
|
||||
}
|
||||
68
NewHorizons/External/Modules/ProcGenModule.cs
vendored
@ -1,14 +1,78 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
using NewHorizons.External.SerializableData;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace NewHorizons.External.Modules
|
||||
{
|
||||
[JsonObject]
|
||||
public class ProcGenModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Scale height of the proc gen.
|
||||
/// </summary>
|
||||
[Range(0, double.MaxValue)] public float scale;
|
||||
|
||||
/// <summary>
|
||||
/// Ground color, only applied if no texture or material is chosen.
|
||||
/// </summary>
|
||||
public MColor color;
|
||||
|
||||
[Range(0, double.MaxValue)] public float scale;
|
||||
/// <summary>
|
||||
/// Can pick a preset material with a texture from the base game. Does not work with color or any textures.
|
||||
/// </summary>
|
||||
public Material material;
|
||||
|
||||
/// <summary>
|
||||
/// Can use a custom texture. Does not work with material or color.
|
||||
/// </summary>
|
||||
public string texture;
|
||||
|
||||
/// <summary>
|
||||
/// Relative filepath to the texture used for the terrain's smoothness and metallic, which are controlled by the texture's alpha and red channels respectively. Optional.
|
||||
/// Typically black with variable transparency, when metallic isn't wanted.
|
||||
/// </summary>
|
||||
public string smoothnessMap;
|
||||
|
||||
/// <summary>
|
||||
/// How "glossy" the surface is, where 0 is diffuse, and 1 is like a mirror.
|
||||
/// Multiplies with the alpha of the smoothness map if using one.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
[DefaultValue(0f)]
|
||||
public float smoothness = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// How metallic the surface is, from 0 to 1.
|
||||
/// Multiplies with the red of the smoothness map if using one.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
[DefaultValue(0f)]
|
||||
public float metallic = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Relative filepath to the texture used for the normal (aka bump) map. Optional.
|
||||
/// </summary>
|
||||
public string normalMap;
|
||||
|
||||
/// <summary>
|
||||
/// Strength of the normal map. Usually 0-1, but can go above, or negative to invert the map.
|
||||
/// </summary>
|
||||
[DefaultValue(1f)]
|
||||
public float normalStrength = 1f;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum Material
|
||||
{
|
||||
[EnumMember(Value = @"default")] Default = 0,
|
||||
|
||||
[EnumMember(Value = @"ice")] Ice = 1,
|
||||
|
||||
[EnumMember(Value = @"quantum")] Quantum = 2,
|
||||
|
||||
[EnumMember(Value = @"rock")] Rock = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using NewHorizons.External.Modules.Props.Audio;
|
||||
using NewHorizons.External.Modules.Props.Dialogue;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace NewHorizons.External.Modules.Props.EyeOfTheUniverse
|
||||
{
|
||||
@ -46,5 +47,23 @@ namespace NewHorizons.External.Modules.Props.EyeOfTheUniverse
|
||||
/// The dialogue to use for this traveler. If omitted, the first CharacterDialogueTree in the object will be used.
|
||||
/// </summary>
|
||||
public DialogueInfo dialogue;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the base game traveler to position this traveler after at the campfire, starting clockwise from Riebeck. Defaults to the end of the list (right before Riebeck).
|
||||
/// </summary>
|
||||
public TravelerName? afterTraveler;
|
||||
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum TravelerName
|
||||
{
|
||||
Riebeck,
|
||||
Chert,
|
||||
Esker,
|
||||
Felspar,
|
||||
Gabbro,
|
||||
Solanum,
|
||||
Prisoner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ namespace NewHorizons.External.Modules
|
||||
public float offset;
|
||||
|
||||
/// <summary>
|
||||
/// The path to the sprite (.png/.jpg/.exr) to show when the planet is unexplored in map mode.
|
||||
/// The path to the sprite (.png/.jpg/.exr) to show when the planet is unexplored in map mode. If empty, a texture will be created and cached based on the revealed sprite.
|
||||
/// </summary>
|
||||
public string outlineSprite;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using NewHorizons.External.SerializableEnums;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace NewHorizons.External.Modules.Volumes.VolumeInfos
|
||||
@ -7,16 +8,20 @@ namespace NewHorizons.External.Modules.Volumes.VolumeInfos
|
||||
[JsonObject]
|
||||
public class LoadCreditsVolumeInfo : VolumeInfo
|
||||
{
|
||||
[DefaultValue("fast")] public NHCreditsType creditsType = NHCreditsType.Fast;
|
||||
[Obsolete("Use gameOver.creditsType")]
|
||||
public NHCreditsType? creditsType;
|
||||
|
||||
/// <summary>
|
||||
/// Text displayed in orange on game over. For localization, put translations under UI.
|
||||
/// </summary>
|
||||
[Obsolete("Use gameOver.text")]
|
||||
public string gameOverText;
|
||||
|
||||
/// <summary>
|
||||
/// The type of death the player will have if they enter this volume.
|
||||
/// The type of death the player will have if they enter this volume. Don't set to have the camera just fade out.
|
||||
/// </summary>
|
||||
[DefaultValue("default")] public NHDeathType deathType = NHDeathType.Default;
|
||||
[DefaultValue("default")] public NHDeathType? deathType = null;
|
||||
|
||||
/// <summary>
|
||||
/// The game over message to display. Leave empty to go straight to credits.
|
||||
/// </summary>
|
||||
public GameOverModule gameOver;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ namespace NewHorizons.External.SerializableEnums
|
||||
|
||||
[EnumMember(Value = @"final")] Final = 1,
|
||||
|
||||
[EnumMember(Value = @"kazoo")] Kazoo = 2
|
||||
[EnumMember(Value = @"kazoo")] Kazoo = 2,
|
||||
|
||||
[EnumMember(Value = @"none")] None = 3
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,28 +266,48 @@ namespace NewHorizons.Handlers
|
||||
|
||||
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();
|
||||
|
||||
var travelers = new List<Transform>()
|
||||
{
|
||||
quantumCampsiteController._travelerControllers[0].transform, // Riebeck
|
||||
quantumCampsiteController._travelerControllers[2].transform, // Chert
|
||||
quantumCampsiteController._travelerControllers[6].transform, // Esker
|
||||
quantumCampsiteController._travelerControllers[1].transform, // Felspar
|
||||
quantumCampsiteController._travelerControllers[3].transform, // Gabbro
|
||||
};
|
||||
var travelers = new List<Transform>();
|
||||
|
||||
if (quantumCampsiteController._hasMetSolanum)
|
||||
{
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[4].transform); // Solanum
|
||||
}
|
||||
if (quantumCampsiteController._hasMetPrisoner)
|
||||
var hasMetSolanum = quantumCampsiteController._hasMetSolanum;
|
||||
var hasMetPrisoner = quantumCampsiteController._hasMetPrisoner;
|
||||
|
||||
// The order of the travelers in the base game differs depending on if the player has met both Solanum and the Prisoner or not.
|
||||
if (hasMetPrisoner && hasMetSolanum)
|
||||
{
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[0].transform); // Riebeck
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[5].transform); // Prisoner
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[6].transform); // Esker
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[1].transform); // Felspar
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[3].transform); // Gabbro
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[4].transform); // Solanum
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[2].transform); // Chert
|
||||
}
|
||||
else
|
||||
{
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[0].transform); // Riebeck
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[2].transform); // Chert
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[6].transform); // Esker
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[1].transform); // Felspar
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[3].transform); // Gabbro
|
||||
if (hasMetSolanum)
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[4].transform); // Solanum
|
||||
if (hasMetPrisoner)
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[5].transform); // Prisoner
|
||||
}
|
||||
|
||||
// Custom travelers (starting at index 7)
|
||||
// Custom travelers (starting at index 7, after Esker). We loop through the array instead of the list of custom travelers in case a non-NH mod added their own.
|
||||
for (int i = 7; i < quantumCampsiteController._travelerControllers.Length; i++)
|
||||
{
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[i].transform);
|
||||
var travelerInfo = GetActiveCustomEyeTravelers().FirstOrDefault(t => t.controller == quantumCampsiteController._travelerControllers[i]);
|
||||
var travelerName = travelerInfo?.info?.afterTraveler;
|
||||
if (travelerName.HasValue)
|
||||
{
|
||||
InsertTravelerAfter(quantumCampsiteController, travelers, travelerInfo.info.afterTraveler.ToString(), quantumCampsiteController._travelerControllers[i].transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
travelers.Add(quantumCampsiteController._travelerControllers[i].transform);
|
||||
}
|
||||
}
|
||||
|
||||
var radius = 2f + 0.2f * travelers.Count;
|
||||
@ -312,6 +332,22 @@ namespace NewHorizons.Handlers
|
||||
}
|
||||
}
|
||||
|
||||
private static void InsertTravelerAfter(QuantumCampsiteController campsite, List<Transform> travelers, string travelerName, Transform newTraveler)
|
||||
{
|
||||
if (travelerName == "Prisoner")
|
||||
travelerName = "Prisoner_Campfire";
|
||||
var existingTraveler = campsite._travelerControllers.FirstOrDefault(c => c.name == travelerName);
|
||||
if (existingTraveler != null)
|
||||
{
|
||||
var index = travelers.IndexOf(existingTraveler.transform);
|
||||
travelers.Insert(index + 1, newTraveler);
|
||||
}
|
||||
else
|
||||
{
|
||||
travelers.Add(newTraveler);
|
||||
}
|
||||
}
|
||||
|
||||
public class EyeTravelerData
|
||||
{
|
||||
public string id;
|
||||
|
||||
@ -11,24 +11,62 @@ namespace NewHorizons.Handlers
|
||||
/// </summary>
|
||||
public static class FadeHandler
|
||||
{
|
||||
public static void FadeOut(float length) => Delay.StartCoroutine(FadeOutCoroutine(length));
|
||||
public static void FadeOut(float length) => Delay.StartCoroutine(FadeOutCoroutine(length, true));
|
||||
|
||||
private static IEnumerator FadeOutCoroutine(float length)
|
||||
public static void FadeOut(float length, bool fadeSound) => Delay.StartCoroutine(FadeOutCoroutine(length, fadeSound));
|
||||
|
||||
public static void FadeIn(float length) => Delay.StartCoroutine(FadeInCoroutine(length));
|
||||
|
||||
private static IEnumerator FadeOutCoroutine(float length, bool fadeSound)
|
||||
{
|
||||
// Make sure its not already faded
|
||||
if (!LoadManager.s_instance._fadeCanvas.enabled)
|
||||
{
|
||||
LoadManager.s_instance._fadeCanvas.enabled = true;
|
||||
float startTime = Time.unscaledTime;
|
||||
float endTime = Time.unscaledTime + length;
|
||||
|
||||
while (Time.unscaledTime < endTime)
|
||||
{
|
||||
var t = Mathf.Clamp01((Time.unscaledTime - startTime) / length);
|
||||
LoadManager.s_instance._fadeImage.color = Color.Lerp(Color.clear, Color.black, t);
|
||||
if (fadeSound)
|
||||
{
|
||||
AudioListener.volume = 1f - t;
|
||||
}
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
LoadManager.s_instance._fadeImage.color = Color.black;
|
||||
if (fadeSound)
|
||||
{
|
||||
AudioListener.volume = 0;
|
||||
}
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new WaitForSeconds(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerator FadeInCoroutine(float length)
|
||||
{
|
||||
LoadManager.s_instance._fadeCanvas.enabled = true;
|
||||
float startTime = Time.unscaledTime;
|
||||
float endTime = Time.unscaledTime + length;
|
||||
|
||||
while (Time.unscaledTime < endTime)
|
||||
{
|
||||
var t = Mathf.Clamp01((Time.unscaledTime - startTime) / length);
|
||||
LoadManager.s_instance._fadeImage.color = Color.Lerp(Color.clear, Color.black, t);
|
||||
AudioListener.volume = 1f - t;
|
||||
LoadManager.s_instance._fadeImage.color = Color.Lerp(Color.black, Color.clear, t);
|
||||
AudioListener.volume = t;
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
LoadManager.s_instance._fadeImage.color = Color.black;
|
||||
AudioListener.volume = 0;
|
||||
AudioListener.volume = 1;
|
||||
LoadManager.s_instance._fadeCanvas.enabled = false;
|
||||
LoadManager.s_instance._fadeImage.color = Color.clear;
|
||||
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
@ -36,7 +74,7 @@ namespace NewHorizons.Handlers
|
||||
|
||||
private static IEnumerator FadeThenCoroutine(float length, Action action)
|
||||
{
|
||||
yield return FadeOutCoroutine(length);
|
||||
yield return FadeOutCoroutine(length, true);
|
||||
|
||||
action?.Invoke();
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ using UnityEngine.SceneManagement;
|
||||
|
||||
namespace NewHorizons.Handlers
|
||||
{
|
||||
internal class InvulnerabilityHandler
|
||||
public static class InvulnerabilityHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in patches
|
||||
@ -32,8 +32,25 @@ namespace NewHorizons.Handlers
|
||||
}
|
||||
}
|
||||
|
||||
private static DeathManager GetDeathManager() => GameObject.FindObjectOfType<DeathManager>();
|
||||
private static PlayerResources GetPlayerResouces() => GameObject.FindObjectOfType<PlayerResources>();
|
||||
private static DeathManager _deathManager;
|
||||
private static DeathManager GetDeathManager()
|
||||
{
|
||||
if (_deathManager == null)
|
||||
{
|
||||
_deathManager = GameObject.FindObjectOfType<DeathManager>();
|
||||
}
|
||||
return _deathManager;
|
||||
}
|
||||
|
||||
private static PlayerResources _playerResources;
|
||||
private static PlayerResources GetPlayerResouces()
|
||||
{
|
||||
if (_playerResources == null)
|
||||
{
|
||||
_playerResources = GameObject.FindObjectOfType<PlayerResources>();
|
||||
}
|
||||
return _playerResources;
|
||||
}
|
||||
|
||||
static InvulnerabilityHandler()
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
@ -168,26 +172,29 @@ namespace NewHorizons.Handlers
|
||||
|
||||
// I don't remember doing this why is it exceptions what am I doing
|
||||
GameObject existingPlanet = null;
|
||||
try
|
||||
if (body.Config.checkForExisting) // TODO: remove this when we cache name->fullpath in Find
|
||||
{
|
||||
existingPlanet = AstroObjectLocator.GetAstroObject(body.Config.name).gameObject;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (body?.Config?.name == null)
|
||||
try
|
||||
{
|
||||
NHLogger.LogError($"How is there no name for {body}");
|
||||
existingPlanet = AstroObjectLocator.GetAstroObject(body.Config.name).gameObject;
|
||||
}
|
||||
else
|
||||
catch (Exception)
|
||||
{
|
||||
existingPlanet = SearchUtilities.Find(body.Config.name.Replace(" ", "") + "_Body", false);
|
||||
if (body?.Config?.name == null)
|
||||
{
|
||||
NHLogger.LogError($"How is there no name for {body}");
|
||||
}
|
||||
else
|
||||
{
|
||||
existingPlanet = SearchUtilities.Find(body.Config.name.Replace(" ", "") + "_Body", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPlanet == null && body.Config.destroy)
|
||||
{
|
||||
NHLogger.LogError($"{body.Config.name} was meant to be destroyed, but was not found");
|
||||
return false;
|
||||
if (existingPlanet == null && body.Config.destroy)
|
||||
{
|
||||
NHLogger.LogError($"{body.Config.name} was meant to be destroyed, but was not found");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPlanet != null)
|
||||
@ -287,9 +294,9 @@ namespace NewHorizons.Handlers
|
||||
try
|
||||
{
|
||||
NHLogger.Log($"Creating [{body.Config.name}]");
|
||||
var planetObject = GenerateBody(body, defaultPrimaryToSun)
|
||||
var planetObject = GenerateBody(body, defaultPrimaryToSun)
|
||||
?? throw new NullReferenceException("Something went wrong when generating the body but no errors were logged.");
|
||||
|
||||
|
||||
planetObject.SetActive(true);
|
||||
|
||||
var ao = planetObject.GetComponent<NHAstroObject>();
|
||||
@ -316,7 +323,7 @@ namespace NewHorizons.Handlers
|
||||
{
|
||||
NHLogger.LogError($"Error in event handler for OnPlanetLoaded on body {body.Config.name}: {e}");
|
||||
}
|
||||
|
||||
|
||||
body.UnloadCache(true);
|
||||
_loadedBodies.Add(body);
|
||||
return true;
|
||||
@ -390,7 +397,7 @@ namespace NewHorizons.Handlers
|
||||
body.Config.MapMarker.enabled = false;
|
||||
|
||||
const float sphereOfInfluence = 2000f;
|
||||
|
||||
|
||||
var owRigidBody = RigidBodyBuilder.Make(go, sphereOfInfluence, body.Config);
|
||||
var ao = AstroObjectBuilder.Make(go, null, body, false);
|
||||
|
||||
@ -402,7 +409,7 @@ namespace NewHorizons.Handlers
|
||||
BrambleDimensionBuilder.Make(body, go, ao, sector, body.Mod, owRigidBody);
|
||||
|
||||
go = SharedGenerateBody(body, go, sector, owRigidBody);
|
||||
|
||||
|
||||
// Not included in SharedGenerate to not mess up gravity on base game planets
|
||||
if (body.Config.Base.surfaceGravity != 0)
|
||||
{
|
||||
@ -467,7 +474,7 @@ namespace NewHorizons.Handlers
|
||||
}
|
||||
|
||||
var sphereOfInfluence = GetSphereOfInfluence(body);
|
||||
|
||||
|
||||
var owRigidBody = RigidBodyBuilder.Make(go, sphereOfInfluence, body.Config);
|
||||
var ao = AstroObjectBuilder.Make(go, primaryBody, body, false);
|
||||
|
||||
@ -581,7 +588,7 @@ namespace NewHorizons.Handlers
|
||||
GameObject procGen = null;
|
||||
if (body.Config.ProcGen != null)
|
||||
{
|
||||
procGen = ProcGenBuilder.Make(go, sector, body.Config.ProcGen);
|
||||
procGen = ProcGenBuilder.Make(body.Mod, go, sector, body.Config.ProcGen);
|
||||
}
|
||||
|
||||
if (body.Config.Star != null)
|
||||
@ -686,7 +693,7 @@ namespace NewHorizons.Handlers
|
||||
SunOverrideBuilder.Make(go, sector, body.Config.Atmosphere, body.Config.Water, surfaceSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (body.Config.Atmosphere.fogSize != 0)
|
||||
{
|
||||
fog = FogBuilder.Make(go, sector, body.Config.Atmosphere, body.Mod);
|
||||
@ -764,6 +771,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);
|
||||
|
||||
@ -997,15 +1006,10 @@ namespace NewHorizons.Handlers
|
||||
|
||||
private static void RemoveChildren(GameObject go, NewHorizonsBody body)
|
||||
{
|
||||
var goPath = go.transform.GetPath();
|
||||
var transforms = go.GetComponentsInChildren<Transform>(true);
|
||||
foreach (var childPath in body.Config.removeChildren)
|
||||
{
|
||||
// Multiple children can have the same path so we delete all that match
|
||||
var path = $"{goPath}/{childPath}";
|
||||
|
||||
var flag = true;
|
||||
foreach (var childObj in transforms.Where(x => x.GetPath() == path))
|
||||
foreach (var childObj in go.transform.FindAll(childPath))
|
||||
{
|
||||
flag = false;
|
||||
// idk why we wait here but we do
|
||||
|
||||
@ -2,6 +2,7 @@ using NewHorizons.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace NewHorizons.Handlers
|
||||
{
|
||||
@ -51,6 +52,9 @@ namespace NewHorizons.Handlers
|
||||
/// </summary>
|
||||
public static void SetUpStreaming(GameObject obj, Sector sector)
|
||||
{
|
||||
// TODO: used OFTEN by detail builder. 20-40ms adds up to seconds. speed up!
|
||||
|
||||
Profiler.BeginSample("get bundles");
|
||||
// find the asset bundles to load
|
||||
// tries the cache first, then builds
|
||||
if (!_objectCache.TryGetValue(obj, out var assetBundles))
|
||||
@ -94,7 +98,9 @@ namespace NewHorizons.Handlers
|
||||
assetBundles = assetBundlesList.ToArray();
|
||||
_objectCache[obj] = assetBundles;
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.BeginSample("get sectors");
|
||||
foreach (var assetBundle in assetBundles)
|
||||
{
|
||||
// Track the sector even if its null. null means stay loaded forever
|
||||
@ -105,7 +111,9 @@ namespace NewHorizons.Handlers
|
||||
}
|
||||
sectors.SafeAdd(sector);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.BeginSample("load assets");
|
||||
if (sector)
|
||||
{
|
||||
sector.OnOccupantEnterSector += _ =>
|
||||
@ -128,6 +136,7 @@ namespace NewHorizons.Handlers
|
||||
foreach (var assetBundle in assetBundles)
|
||||
StreamingManager.LoadStreamingAssets(assetBundle);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public static bool IsBundleInUse(string assetBundle)
|
||||
@ -152,4 +161,4 @@ namespace NewHorizons.Handlers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +109,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, tex.height), new Vector2(0.5f, 0.5f), 100.0f);
|
||||
var sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100, 0, SpriteMeshType.FullRect, Vector4.zero, false);
|
||||
AddSubtitle(sprite);
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using static NewHorizons.External.Configs.StarSystemConfig;
|
||||
|
||||
namespace NewHorizons.Handlers
|
||||
@ -47,7 +48,7 @@ namespace NewHorizons.Handlers
|
||||
if (_textureCache == null) _textureCache = new List<Texture2D>();
|
||||
_textureCache.Add(texture);
|
||||
|
||||
var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(texture.width / 2f, texture.height / 2f));
|
||||
var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(texture.width / 2f, texture.height / 2f), 100, 0, SpriteMeshType.FullRect, Vector4.zero, false);
|
||||
|
||||
var name = ShipLogStarChartMode.UniqueIDToName(systemID);
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ using NewHorizons.Builder.Props;
|
||||
using NewHorizons.Builder.Props.Audio;
|
||||
using NewHorizons.Builder.Props.EchoesOfTheEye;
|
||||
using NewHorizons.Builder.Props.TranslatorText;
|
||||
using NewHorizons.Components;
|
||||
using NewHorizons.Components.EOTE;
|
||||
using NewHorizons.Components.Fixers;
|
||||
using NewHorizons.Components.Ship;
|
||||
@ -311,6 +312,7 @@ namespace NewHorizons
|
||||
ImageUtilities.ClearCache();
|
||||
AudioUtilities.ClearCache();
|
||||
AssetBundleUtilities.ClearCache();
|
||||
ProcGenBuilder.ClearCache();
|
||||
}
|
||||
|
||||
IsSystemReady = false;
|
||||
@ -478,6 +480,8 @@ namespace NewHorizons
|
||||
// Fix spawn point
|
||||
PlayerSpawnHandler.SetUpPlayerSpawn();
|
||||
|
||||
new GameObject(nameof(NHGameOverManager)).AddComponent<NHGameOverManager>();
|
||||
|
||||
if (isSolarSystem)
|
||||
{
|
||||
// Warp drive
|
||||
@ -834,6 +838,10 @@ namespace NewHorizons
|
||||
AssetBundleUtilities.PreloadBundle(bundle, mod);
|
||||
}
|
||||
}
|
||||
if (addonConfig.gameOver != null)
|
||||
{
|
||||
NHGameOverManager.gameOvers[mod.ModHelper.Manifest.UniqueName] = addonConfig.gameOver;
|
||||
}
|
||||
|
||||
AddonConfigs[mod] = addonConfig;
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
<NoWarn>1701;1702;1591</NoWarn>
|
||||
|
||||
<!-- <DefineConstants>ENABLE_PROFILER</DefineConstants>-->
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugType>none</DebugType>
|
||||
@ -39,4 +41,4 @@
|
||||
<ItemGroup>
|
||||
<Content Include="NewHorizons.csproj.user" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
15
NewHorizons/Patches/DeathManagerPatches.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using HarmonyLib;
|
||||
using NewHorizons.Components;
|
||||
|
||||
namespace NewHorizons.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class DeathManagerPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.FinishDeathSequence))]
|
||||
public static void DeathManager_FinishDeathSequence()
|
||||
{
|
||||
NHGameOverManager.Instance.TryHijackDeathSequence();
|
||||
}
|
||||
}
|
||||
73
NewHorizons/Patches/ProfilerPatch.cs
Normal file
@ -0,0 +1,73 @@
|
||||
#if ENABLE_PROFILER
|
||||
|
||||
using HarmonyLib;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace NewHorizons.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// attach profiler markers to important methods
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class ProfilerPatch
|
||||
{
|
||||
private static string FriendlyName(this MethodBase @this) => $"{@this.DeclaringType.Name}.{@this.Name}";
|
||||
|
||||
[HarmonyTargetMethods]
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
|
||||
{
|
||||
if (!(
|
||||
type.Name == "Main" ||
|
||||
type.Name.EndsWith("Builder") ||
|
||||
type.Name.EndsWith("Handler") ||
|
||||
type.Name.EndsWith("Utilities")
|
||||
)) continue;
|
||||
|
||||
foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
if (method.ContainsGenericParameters) continue;
|
||||
|
||||
// Main.Instance.ModHelper.Console.WriteLine($"[profiler] profiling {method.FriendlyName()}");
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix(MethodBase __originalMethod /*, out Stopwatch __state*/)
|
||||
{
|
||||
Profiler.BeginSample(__originalMethod.FriendlyName());
|
||||
|
||||
// __state = new Stopwatch();
|
||||
// __state.Start();
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
public static void Postfix( /*MethodBase __originalMethod, Stopwatch __state*/)
|
||||
{
|
||||
Profiler.EndSample();
|
||||
|
||||
// __state.Stop();
|
||||
// Main.Instance.ModHelper.Console.WriteLine($"[profiler] {__originalMethod.MethodName()} took {__state.Elapsed.TotalMilliseconds:f1} ms");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bundle loading causes log spam that slows loading, but only in unity dev profiler mode.
|
||||
/// patch it out so it doesnt do false-positive slowness.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class DisableShaderLogSpamPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StackTraceUtility), "ExtractStackTrace")]
|
||||
[HarmonyPatch(typeof(Application), "CallLogCallback")]
|
||||
private static bool DisableShaderLogSpam() => false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -38,6 +38,13 @@
|
||||
"type": "string",
|
||||
"description": "The path to the addons subtitle for the main menu.\nDefaults to \"subtitle.png\".\nThe dimensions of the Echoes of the Eye subtitle is 669 x 67, so aim for that size"
|
||||
},
|
||||
"gameOver": {
|
||||
"type": "array",
|
||||
"description": "Custom game over messages for this mod. This can either display a title card before looping like in EOTE, or show a message and roll credits like the various time loop escape endings.\nYou must set a dialogue condition for the game over sequence to run.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/GameOverModule"
|
||||
}
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "The schema to validate with"
|
||||
@ -79,6 +86,80 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"GameOverModule": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Text displayed in orange on game over. For localization, put translations under UI."
|
||||
},
|
||||
"colour": {
|
||||
"description": "Change the colour of the game over text. Leave empty to use the default orange.",
|
||||
"$ref": "#/definitions/MColor"
|
||||
},
|
||||
"condition": {
|
||||
"type": "string",
|
||||
"description": "Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over.\nNote this is a regular dialogue condition, not a persistent condition."
|
||||
},
|
||||
"creditsType": {
|
||||
"description": "The type of credits that will run after the game over message is shown",
|
||||
"default": "fast",
|
||||
"$ref": "#/definitions/NHCreditsType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MColor": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"r": {
|
||||
"type": "integer",
|
||||
"description": "The red component of this colour from 0-255, higher values will make the colour glow if applicable.",
|
||||
"format": "int32",
|
||||
"maximum": 2147483647.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
"g": {
|
||||
"type": "integer",
|
||||
"description": "The green component of this colour from 0-255, higher values will make the colour glow if applicable.",
|
||||
"format": "int32",
|
||||
"maximum": 2147483647.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
"b": {
|
||||
"type": "integer",
|
||||
"description": "The blue component of this colour from 0-255, higher values will make the colour glow if applicable.",
|
||||
"format": "int32",
|
||||
"maximum": 2147483647.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
"a": {
|
||||
"type": "integer",
|
||||
"description": "The alpha (opacity) component of this colour",
|
||||
"format": "int32",
|
||||
"default": 255,
|
||||
"maximum": 255.0,
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"NHCreditsType": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-enumNames": [
|
||||
"Fast",
|
||||
"Final",
|
||||
"Kazoo",
|
||||
"None"
|
||||
],
|
||||
"enum": [
|
||||
"fast",
|
||||
"final",
|
||||
"kazoo",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"$docs": {
|
||||
|
||||
@ -43,6 +43,11 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkForExisting": {
|
||||
"type": "boolean",
|
||||
"description": "optimization. turn this off if you know you're generating a new body and aren't worried about other addons editing it.",
|
||||
"default": true
|
||||
},
|
||||
"AmbientLights": {
|
||||
"type": "array",
|
||||
"description": "Add ambient lights to this body",
|
||||
@ -353,16 +358,72 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"color": {
|
||||
"$ref": "#/definitions/MColor"
|
||||
},
|
||||
"scale": {
|
||||
"type": "number",
|
||||
"description": "Scale height of the proc gen.",
|
||||
"format": "float",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"color": {
|
||||
"description": "Ground color, only applied if no texture or material is chosen.",
|
||||
"$ref": "#/definitions/MColor"
|
||||
},
|
||||
"material": {
|
||||
"description": "Can pick a preset material with a texture from the base game. Does not work with color or any textures.",
|
||||
"$ref": "#/definitions/Material"
|
||||
},
|
||||
"texture": {
|
||||
"type": "string",
|
||||
"description": "Can use a custom texture. Does not work with material or color."
|
||||
},
|
||||
"smoothnessMap": {
|
||||
"type": "string",
|
||||
"description": "Relative filepath to the texture used for the terrain's smoothness and metallic, which are controlled by the texture's alpha and red channels respectively. Optional.\nTypically black with variable transparency, when metallic isn't wanted."
|
||||
},
|
||||
"smoothness": {
|
||||
"type": "number",
|
||||
"description": "How \"glossy\" the surface is, where 0 is diffuse, and 1 is like a mirror.\nMultiplies with the alpha of the smoothness map if using one.",
|
||||
"format": "float",
|
||||
"default": 0.0,
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
"metallic": {
|
||||
"type": "number",
|
||||
"description": "How metallic the surface is, from 0 to 1.\nMultiplies with the red of the smoothness map if using one.",
|
||||
"format": "float",
|
||||
"default": 0.0,
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
"normalMap": {
|
||||
"type": "string",
|
||||
"description": "Relative filepath to the texture used for the normal (aka bump) map. Optional."
|
||||
},
|
||||
"normalStrength": {
|
||||
"type": "number",
|
||||
"description": "Strength of the normal map. Usually 0-1, but can go above, or negative to invert the map.",
|
||||
"format": "float",
|
||||
"default": 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"Material": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-enumNames": [
|
||||
"Default",
|
||||
"Ice",
|
||||
"Quantum",
|
||||
"Rock"
|
||||
],
|
||||
"enum": [
|
||||
"default",
|
||||
"ice",
|
||||
"quantum",
|
||||
"rock"
|
||||
]
|
||||
},
|
||||
"AtmosphereModule": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@ -1022,6 +1083,17 @@
|
||||
"dialogue": {
|
||||
"description": "The dialogue to use for this traveler. If omitted, the first CharacterDialogueTree in the object will be used.",
|
||||
"$ref": "#/definitions/DialogueInfo"
|
||||
},
|
||||
"afterTraveler": {
|
||||
"description": "The name of the base game traveler to position this traveler after at the campfire, starting clockwise from Riebeck. Defaults to the end of the list (right before Riebeck).",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/TravelerName"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1466,6 +1538,28 @@
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"TravelerName": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"x-enumNames": [
|
||||
"Riebeck",
|
||||
"Chert",
|
||||
"Esker",
|
||||
"Felspar",
|
||||
"Gabbro",
|
||||
"Solanum",
|
||||
"Prisoner"
|
||||
],
|
||||
"enum": [
|
||||
"Riebeck",
|
||||
"Chert",
|
||||
"Esker",
|
||||
"Felspar",
|
||||
"Gabbro",
|
||||
"Solanum",
|
||||
"Prisoner"
|
||||
]
|
||||
},
|
||||
"InstrumentZoneInfo": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@ -4690,7 +4784,7 @@
|
||||
},
|
||||
"outlineSprite": {
|
||||
"type": "string",
|
||||
"description": "The path to the sprite (.png/.jpg/.exr) to show when the planet is unexplored in map mode."
|
||||
"description": "The path to the sprite (.png/.jpg/.exr) to show when the planet is unexplored in map mode. If empty, a texture will be created and cached based on the revealed sprite."
|
||||
},
|
||||
"remove": {
|
||||
"type": "boolean",
|
||||
@ -6391,18 +6485,44 @@
|
||||
"type": "string",
|
||||
"description": "An optional rename of this object"
|
||||
},
|
||||
"creditsType": {
|
||||
"default": "fast",
|
||||
"$ref": "#/definitions/NHCreditsType"
|
||||
"deathType": {
|
||||
"description": "The type of death the player will have if they enter this volume. Don't set to have the camera just fade out.",
|
||||
"default": "default",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/NHDeathType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gameOverText": {
|
||||
"gameOver": {
|
||||
"description": "The game over message to display. Leave empty to go straight to credits.",
|
||||
"$ref": "#/definitions/GameOverModule"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GameOverModule": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Text displayed in orange on game over. For localization, put translations under UI."
|
||||
},
|
||||
"deathType": {
|
||||
"description": "The type of death the player will have if they enter this volume.",
|
||||
"default": "default",
|
||||
"$ref": "#/definitions/NHDeathType"
|
||||
"colour": {
|
||||
"description": "Change the colour of the game over text. Leave empty to use the default orange.",
|
||||
"$ref": "#/definitions/MColor"
|
||||
},
|
||||
"condition": {
|
||||
"type": "string",
|
||||
"description": "Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over.\nNote this is a regular dialogue condition, not a persistent condition."
|
||||
},
|
||||
"creditsType": {
|
||||
"description": "The type of credits that will run after the game over message is shown",
|
||||
"default": "fast",
|
||||
"$ref": "#/definitions/NHCreditsType"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6412,12 +6532,14 @@
|
||||
"x-enumNames": [
|
||||
"Fast",
|
||||
"Final",
|
||||
"Kazoo"
|
||||
"Kazoo",
|
||||
"None"
|
||||
],
|
||||
"enum": [
|
||||
"fast",
|
||||
"final",
|
||||
"kazoo"
|
||||
"kazoo",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"CometTailModule": {
|
||||
|
||||
@ -12,6 +12,7 @@ namespace NewHorizons.Utility.Files
|
||||
public static class AssetBundleUtilities
|
||||
{
|
||||
public static Dictionary<string, (AssetBundle bundle, bool keepLoaded)> AssetBundles = new();
|
||||
private static Dictionary<string, GameObject> _prefabCache = new();
|
||||
|
||||
private static readonly List<AssetBundleCreateRequest> _loadingBundles = new();
|
||||
|
||||
@ -52,6 +53,7 @@ namespace NewHorizons.Utility.Files
|
||||
|
||||
}
|
||||
AssetBundles = AssetBundles.Where(x => x.Value.keepLoaded).ToDictionary(x => x.Key, x => x.Value);
|
||||
_prefabCache.Clear();
|
||||
}
|
||||
|
||||
public static void PreloadBundle(string assetBundleRelativeDir, IModBehaviour mod)
|
||||
@ -113,11 +115,17 @@ namespace NewHorizons.Utility.Files
|
||||
|
||||
public static GameObject LoadPrefab(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod)
|
||||
{
|
||||
var prefab = Load<GameObject>(assetBundleRelativeDir, pathInBundle, mod);
|
||||
if (_prefabCache.TryGetValue(assetBundleRelativeDir + pathInBundle, out var prefab))
|
||||
return prefab;
|
||||
|
||||
prefab = Load<GameObject>(assetBundleRelativeDir, pathInBundle, mod);
|
||||
|
||||
prefab.SetActive(false);
|
||||
|
||||
ReplaceShaders(prefab);
|
||||
|
||||
// replacing shaders is expensive, so cache it
|
||||
_prefabCache.Add(assetBundleRelativeDir + pathInBundle, prefab);
|
||||
|
||||
return prefab;
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
using NewHorizons.Builder.Props;
|
||||
using NewHorizons.External;
|
||||
using NewHorizons.External.Modules.VariableSize;
|
||||
using NewHorizons.Utility.OWML;
|
||||
using OWML.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NewHorizons.Utility.Files
|
||||
@ -43,7 +46,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))
|
||||
{
|
||||
@ -91,8 +104,8 @@ namespace NewHorizons.Utility.Files
|
||||
DeleteTexture(key, texture);
|
||||
}
|
||||
|
||||
public static void DeleteTexture(string key, Texture2D texture)
|
||||
{
|
||||
public static void DeleteTexture(string key, Texture2D texture)
|
||||
{
|
||||
if (_textureCache.ContainsKey(key))
|
||||
{
|
||||
if (_textureCache[key] == texture)
|
||||
@ -247,7 +260,7 @@ namespace NewHorizons.Utility.Files
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static Texture2D MakeOutline(Texture2D texture, Color color, int thickness)
|
||||
private static Texture2D MakeOutline(Texture2D texture, Color color, int thickness)
|
||||
{
|
||||
var key = $"{texture.name} > outline {color} {thickness}";
|
||||
if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
|
||||
@ -368,7 +381,7 @@ namespace NewHorizons.Utility.Files
|
||||
var pixels = image.GetPixels();
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
var amount = (i % image.width) / (float) image.width;
|
||||
var amount = (i % image.width) / (float)image.width;
|
||||
var lightTint = LerpColor(lightTintStart, lightTintEnd, amount);
|
||||
var darkTint = LerpColor(darkTintStart, darkTintEnd, amount);
|
||||
|
||||
@ -489,5 +502,119 @@ namespace NewHorizons.Utility.Files
|
||||
sprite.name = texture.name;
|
||||
return sprite;
|
||||
}
|
||||
|
||||
public static Texture2D GetCachedOutlineOrCreate(NewHorizonsBody body, Texture2D original, string originalPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalPath))
|
||||
{
|
||||
Texture2D defaultTexture = null;
|
||||
if (body.Config.Star != null)
|
||||
{
|
||||
defaultTexture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModeStarOutline.png");
|
||||
}
|
||||
else if (body.Config.Atmosphere != null)
|
||||
{
|
||||
defaultTexture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModNoAtmoOutline.png");
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultTexture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModePlanetOutline.png");
|
||||
}
|
||||
|
||||
return defaultTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
var cachedPath = Path.Combine(body.Mod.ModHelper.Manifest.ModFolderPath, $"TextureCache_{Main.Instance.CurrentStarSystem}", originalPath);
|
||||
var outlineTexture = ImageUtilities.GetTexture(body.Mod, cachedPath);
|
||||
|
||||
if (outlineTexture == null)
|
||||
{
|
||||
NHLogger.LogVerbose($"Caching outline to {cachedPath}");
|
||||
|
||||
var newTexture = ImageUtilities.MakeOutline(original, Color.white, 10);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cachedPath));
|
||||
File.WriteAllBytes(cachedPath, newTexture.EncodeToPNG());
|
||||
|
||||
return newTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
return outlineTexture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture2D AutoGenerateMapModePicture(NewHorizonsBody body)
|
||||
{
|
||||
Texture2D texture;
|
||||
|
||||
if (body.Config.Star != null)
|
||||
{
|
||||
texture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModeStar.png");
|
||||
}
|
||||
else if (body.Config.Atmosphere != null)
|
||||
{
|
||||
texture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModNoAtmo.png");
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = ImageUtilities.GetTexture(Main.Instance, "Assets/DefaultMapModePlanet.png");
|
||||
}
|
||||
|
||||
var color = GetDominantPlanetColor(body);
|
||||
var darkColor = new Color(color.r / 3f, color.g / 3f, color.b / 3f);
|
||||
|
||||
texture = ImageUtilities.LerpGreyscaleImage(texture, color, darkColor);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static Color GetDominantPlanetColor(NewHorizonsBody body)
|
||||
{
|
||||
try
|
||||
{
|
||||
var starColor = body.Config?.Star?.tint;
|
||||
if (starColor != null) return starColor.ToColor();
|
||||
|
||||
var atmoColor = body.Config.Atmosphere?.atmosphereTint;
|
||||
if (body.Config.Atmosphere?.clouds != null && atmoColor != null) return atmoColor.ToColor();
|
||||
|
||||
if (body.Config?.HeightMap?.textureMap != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var texture = ImageUtilities.GetTexture(body.Mod, body.Config.HeightMap.textureMap);
|
||||
var landColor = ImageUtilities.GetAverageColor(texture);
|
||||
if (landColor != null) return landColor;
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
var waterColor = body.Config.Water?.tint;
|
||||
if (waterColor != null) return waterColor.ToColor();
|
||||
|
||||
var lavaColor = body.Config.Lava?.tint;
|
||||
if (lavaColor != null) return lavaColor.ToColor();
|
||||
|
||||
var sandColor = body.Config.Sand?.tint;
|
||||
if (sandColor != null) return sandColor.ToColor();
|
||||
|
||||
switch (body.Config?.Props?.singularities?.FirstOrDefault()?.type)
|
||||
{
|
||||
case SingularityModule.SingularityType.BlackHole:
|
||||
return Color.black;
|
||||
case SingularityModule.SingularityType.WhiteHole:
|
||||
return Color.white;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
NHLogger.LogWarning($"Something went wrong trying to pick the colour for {body.Config.name} but I'm too lazy to fix it.");
|
||||
}
|
||||
|
||||
return Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ using NewHorizons.Utility.OWML;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@ -116,19 +117,23 @@ namespace NewHorizons.Utility
|
||||
if (CachedGameObjects.TryGetValue(path, out var go)) return go;
|
||||
|
||||
// 1: normal find
|
||||
Profiler.BeginSample("1");
|
||||
go = GameObject.Find(path);
|
||||
if (go)
|
||||
{
|
||||
CachedGameObjects.Add(path, go);
|
||||
Profiler.EndSample();
|
||||
return go;
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.BeginSample("2");
|
||||
// 2: find inactive using root + transform.find
|
||||
var names = path.Split('/');
|
||||
|
||||
// Cache the root objects so we don't loop through all of them each time
|
||||
var rootName = names[0];
|
||||
if (!CachedRootGameObjects.TryGetValue(rootName, out var root))
|
||||
if (!CachedRootGameObjects.TryGetValue(rootName, out var root))
|
||||
{
|
||||
root = SceneManager.GetActiveScene().GetRootGameObjects().FirstOrDefault(x => x.name == rootName);
|
||||
if (root != null)
|
||||
@ -142,9 +147,12 @@ namespace NewHorizons.Utility
|
||||
if (go)
|
||||
{
|
||||
CachedGameObjects.Add(path, go);
|
||||
Profiler.EndSample();
|
||||
return go;
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.BeginSample("3");
|
||||
var name = names.Last();
|
||||
if (warn) NHLogger.LogWarning($"Couldn't find object in path {path}, will look for potential matches for name {name}");
|
||||
// 3: find resource to include inactive objects (but skip prefabs)
|
||||
@ -153,10 +161,12 @@ namespace NewHorizons.Utility
|
||||
if (go)
|
||||
{
|
||||
CachedGameObjects.Add(path, go);
|
||||
Profiler.EndSample();
|
||||
return go;
|
||||
}
|
||||
|
||||
if (warn) NHLogger.LogWarning($"Couldn't find object with name {name}");
|
||||
Profiler.EndSample();
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -169,5 +179,31 @@ namespace NewHorizons.Utility
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// transform.find but works for gameobjects with same name
|
||||
/// </summary>
|
||||
public static List<Transform> FindAll(this Transform @this, string path)
|
||||
{
|
||||
var names = path.Split('/');
|
||||
var currentTransforms = new List<Transform> { @this };
|
||||
foreach (var name in names)
|
||||
{
|
||||
var newTransforms = new List<Transform>();
|
||||
foreach (var currentTransform in currentTransforms)
|
||||
{
|
||||
foreach (Transform child in currentTransform)
|
||||
{
|
||||
if (child.name == name)
|
||||
{
|
||||
newTransforms.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentTransforms = newTransforms;
|
||||
}
|
||||
|
||||
return currentTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, Trifid, and friends",
|
||||
"name": "New Horizons",
|
||||
"uniqueName": "xen.NewHorizons",
|
||||
"version": "1.26.1",
|
||||
"version": "1.27.0",
|
||||
"owmlVersion": "2.12.1",
|
||||
"dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
|
||||
"conflicts": [ "PacificEngine.OW_CommonResources" ],
|
||||
|
||||
@ -15,38 +15,14 @@ A dialogue tree is an entire conversation, it's made up of dialogue nodes.
|
||||
|
||||
A node is a set of pages shown to the player followed by options the player can choose from to change the flow of the conversation.
|
||||
|
||||
### Condition
|
||||
### Conditions
|
||||
|
||||
A condition is a yes/no value stored **for this loop and this loop only**. It can be used to show new dialogue options, stop someone from talking to you (looking at you Slate), and more.
|
||||
|
||||
### Persistent Condition
|
||||
|
||||
A persistent condition is similar to a condition, except it _persists_ through loops, and is saved on the players save file.
|
||||
In dialogue, the available conversation topics can be limited by what the player knows, defined using dialogue conditions, persistent conditions, and ship log facts. Dialogue can also set conditions to true or false, and reveal ship log facts to the player. This is covered in detail later on this page.
|
||||
|
||||
### Remote Trigger
|
||||
|
||||
A remote trigger is used to have an NPC talk to you from a distance; ex: Slate stopping you for the umpteenth time to tell you information you already knew.
|
||||
|
||||
### ReuseDialogueOptionsListFrom
|
||||
|
||||
This is a custom XML node introduced by New Horizons. Use it when adding new dialogue to existing characters, to repeat the dialogue options list from another node.
|
||||
|
||||
For example, Slate's first dialogue with options is named `Scientist5`. To make a custom DialogueNode using these dialogue options (meaning new dialogue said by Slate, but reusing the possible player responses) you can write:
|
||||
|
||||
```xml
|
||||
<DialogueNode>
|
||||
<Name>...</Name>
|
||||
<Dialogue>
|
||||
<Page>NEW DIALOGUE FOR SLATE HERE.</Page>
|
||||
</Dialogue>
|
||||
<DialogueOptionsList>
|
||||
<ReuseDialogueOptionsListFrom>Scientist5</ReuseDialogueOptionsListFrom>
|
||||
</DialogueOptionsList>
|
||||
</DialogueNode>
|
||||
```
|
||||
|
||||
Note: If you're loading dialogue in code, 2 frames must pass before entering the conversation in order for ReuseDialogueOptionsListFrom to take effect.
|
||||
|
||||
## Example XML
|
||||
|
||||
Here's an example dialogue XML:
|
||||
@ -176,11 +152,39 @@ In addition to `<DialogueOptions>`, there are other ways to control the flow of
|
||||
|
||||
Defining `<DialogueTarget>` in the `<DialogueNode>` tag instead of a `<DialogueOption>` will make the conversation go directly to that target after the character is done talking.
|
||||
|
||||
### DialogueTargetShipLogCondition
|
||||
### EntryCondition
|
||||
|
||||
Used in tandem with `DialogueTarget`, makes it so you must have a [ship log fact](/guides/ship-log#explore-facts) to go to the next node.
|
||||
The first dialogue node that opens when a player starts talking to a character is chosen using this property. To mark a DialogueNode as beginning the dialogue by default, use the condition DEFAULT (a DialogueTree should always have a node with the DEFAULT entry condition to ensure there is a way to start dialogue).
|
||||
|
||||
### Adding to existing dialogue
|
||||
The entry condition can be either a condition or a persistent condition.
|
||||
|
||||
### Condition
|
||||
|
||||
A condition is a yes/no value stored **for this loop and this loop only**. It can be used to show new dialogue options, stop someone from talking to you (looking at you Slate), and more.
|
||||
|
||||
Conditions can be set in dialogue using `<SetCondition>CONDITION_NAME</SetCondition>`. This can go in a DialogueNode in which case it will set the condition to true when that node is read. There is a similar version of this for DialogueOptions called `<ConditionToSet>CONDITION_NAME</ConditionToSet>` which will set it to true when that option is selected. Conditions can be disabled using `<ConditionToCancel>CONDITION_NAME</<ConditionToCancel>` in a DialogueOption, but cannot be disabled just by entering a DialogueNode.
|
||||
|
||||
You can lock a DialogueOption behind a condition using `<RequiredCondition>CONDITION_NAME</RequiredCondition>`, or remove a DialogueOption after the condition is set to true using `<CancelledCondition>CONDITION_NAME</CancelledCondition>`.
|
||||
|
||||
Dialogue conditions can also be set in code with `DialogueConditionManager.SharedInstance.SetConditionState("CONDITION_NAME", true/false)` or read with `DialogueConditionManager.SharedInstance.GetConditionState("CONDITION_NAME")`.
|
||||
|
||||
Note that `CONDITION_NAME` is a placeholder that you would replace with whatever you want to call your condition. Consider appending conditions with the name of your mod to make for better compatibility between mods, for example a condition name like `SPOKEN_TO` is very generic and might conflict with other mods whereas `NH_EXAMPLES_SPOKEN_TO_ERNESTO` is much less likely to conflict with another mod.
|
||||
|
||||
### Persistent Condition
|
||||
|
||||
A persistent condition is similar to a condition, except it _persists_ through loops, and is saved on the players save file.
|
||||
|
||||
Persistent conditions shared many similar traits with regular dialogue conditions. You can use `<SetPersistentCondition>`, `<DisablePersistentCondition>`. On dialogue options you can use `<RequiredPersistentCondition>`, `<CancelledPersistentCondition>`
|
||||
|
||||
Persistent conditions can also be set in code with `PlayerData.SetPersistentCondition("PERSISTENT_CONDITION_NAME", true/false)` and read using `PlayerData.GetPersistentCondition("PERSISTENT_CONDITION_NAME")`.
|
||||
|
||||
### Ship Logs
|
||||
|
||||
Dialogue can interact with ship logs, either granting them to the player (`<RevealFacts>` on a DialogueNode) or locking dialogue behind ship log completion (`<RequiredLogCondition>` on a DialogueOption).
|
||||
|
||||
You can also use `<DialogueTargetShipLogCondition>` in tandem with `DialogueTarget` to make it so you must have a [ship log fact](/guides/ship-log#explore-facts) to go to the next node.
|
||||
|
||||
## Adding to existing dialogue
|
||||
|
||||
Here's an example of how to add new dialogue to Slate, without overwriting their existing dialogue. This will also allow multiple mods to all add new dialogue to the same character.
|
||||
|
||||
@ -221,8 +225,33 @@ To use this additional dialogue you need to reference it in a planet config file
|
||||
]
|
||||
```
|
||||
|
||||
### ReuseDialogueOptionsListFrom
|
||||
|
||||
This is a custom XML node introduced by New Horizons. Use it when adding new dialogue to existing characters, to repeat the dialogue options list from another node.
|
||||
|
||||
For example, Slate's first dialogue with options is named `Scientist5`. To make a custom DialogueNode using these dialogue options (meaning new dialogue said by Slate, but reusing the possible player responses) you can write:
|
||||
|
||||
```xml
|
||||
<DialogueNode>
|
||||
<Name>...</Name>
|
||||
<Dialogue>
|
||||
<Page>NEW DIALOGUE FOR SLATE HERE.</Page>
|
||||
</Dialogue>
|
||||
<DialogueOptionsList>
|
||||
<ReuseDialogueOptionsListFrom>Scientist5</ReuseDialogueOptionsListFrom>
|
||||
</DialogueOptionsList>
|
||||
</DialogueNode>
|
||||
```
|
||||
|
||||
Note: If you're loading dialogue in code, 2 frames must pass before entering the conversation in order for ReuseDialogueOptionsListFrom to take effect.
|
||||
|
||||
|
||||
## Dialogue FAQ
|
||||
|
||||
### How do I easily position my dialogue relative to a speaking character
|
||||
|
||||
Use `pathToAnimController` to specify the path to the speaking character (if they are a Nomai or Hearthian make sure this goes directly to whatever script controls their animations), then set `isRelativeToParent` to true (this is setting available on all NH props for easier positioning). Now when you set their `position`, it will be relative to the speaker. Since this position is normally where the character is standing, set the `y` position to match how tall the character is. Instead of `pathToAnimController` you can also use `parentPath`.
|
||||
|
||||
### How do I have the dialogue prompt say "Read" or "Play recording"
|
||||
|
||||
`<NameField>` sets the name of the character, which will then show in the prompt to start dialogue. You can alternatively use `<NameField>SIGN</NameField>` to have the prompt say "Read", and `<NameField>RECORDING</NameField>` to have it say "Play recording".
|
||||
26
docs/src/content/docs/guides/nomai-text.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Nomai Text
|
||||
description: Guide to making Nomai Text in New Horizons
|
||||
---
|
||||
|
||||
This page goes over how to use Nomai text in New Horizons.
|
||||
|
||||
## Understanding Nomai Text
|
||||
|
||||
Nomai text is the backbone of many story mods. There are two parts to setting up Nomai text: The XML file and the planet config.
|
||||
|
||||
### XML
|
||||
|
||||
In your XML, you define the actual raw text which will be displayed, the ship logs it unlocks, and the way it branches. See [the Nomai text XML schema](/schemas/text-schema/) for more info.
|
||||
|
||||
Nomai text contains a root `<NomaiObject>` node, followed by `<TextBlock>` nodes and optionally a `<ShipLogConditions>` node.
|
||||
|
||||
Nomai text is made up of `TextBlock`s. Each text block has an `ID` which must be unique (you can just number them for simplicity). After the first defined text block, each must have a `ParentID`. For scrolls and regular wall text, the text block only gets revealed after its parent block. Multiple text blocks can have the same parent, allowing for branching paths. In recorders and computers, each text block must procede in order (the second parented to the first, the third to the second, etc). In cairns, there is only one text block.
|
||||
|
||||
To unlock ship logs after reading each text block, add a `<ShipLogConditions>` node. This can contains multiple `<RevealFact>` nodes, each one defining a `<FactID>`, `<Condition>`. The ship log conditions node can either have `<LocationA/>` or `<LocationB/>`, which means the logs will unlock only if you are at that location. The `<Condition>` lists the TextBlock ids which must be read to reveal the fact as a comma delimited list (e.g., `<Condition>1,2,4</Condition>`)..
|
||||
|
||||
### Json
|
||||
|
||||
In your planet config, you must define where the Nomai text is positioned. See [the translator text json schema](/schemas/body-schema/defs/translatortextinfo/) for more info.
|
||||
|
||||
You can input a `seed` for a wall of text which will randomly generate the position of each arc. To test out different combinations, just keep incrementing the number and then hit "Reload Configs" from the pause menu with debug mode on. This seed ensures the same positioning each time the mod is played. Alternatively, you can use `arcInfo` to set the position and rotation of all text arcs, as well as determining their types (adult, teenager, child, or Stranger). The various age stages make the text look messier, while Stranger allows you to make a translatable version of the DLC text.
|
||||
@ -14,6 +14,7 @@ Before you release anything, you'll want to make sure:
|
||||
- Your repo has the description field (click the cog in the right column on the "Code" tab) set. (this will be shown in the manager)
|
||||
- There's no `config.json` in your addon. (Not super important, but good practice)
|
||||
- Your manifest has a valid name, author, and unique name.
|
||||
- You have included any caches New Horizons has made (i.e., slide reel caches). Since these are made in the install location of the mod you will have to manually copy them into the mod repo and ensure they stay up to date. While these files are not required, they ensure that your players will have faster loading times and reduced memory usage on their first loop (after which the caches will generate for them locally).
|
||||
|
||||
## Releasing
|
||||
|
||||
|
||||