mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Merge branch 'dev' into profiler
This commit is contained in:
commit
5e1fa2aac2
BIN
NewHorizons/Assets/textures/Ice.png
Normal file
BIN
NewHorizons/Assets/textures/Ice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 MiB |
BIN
NewHorizons/Assets/textures/Quantum.png
Normal file
BIN
NewHorizons/Assets/textures/Quantum.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
NewHorizons/Assets/textures/Rocks.png
Normal file
BIN
NewHorizons/Assets/textures/Rocks.png
Normal file
Binary file not shown.
|
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;
|
||||
}
|
||||
|
||||
|
||||
@ -156,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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
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;
|
||||
}
|
||||
}
|
||||
|
||||
19
NewHorizons/External/Configs/PlanetConfig.cs
vendored
19
NewHorizons/External/Configs/PlanetConfig.cs
vendored
@ -663,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;
|
||||
|
||||
32
NewHorizons/External/Modules/GameOverModule.cs
vendored
Normal file
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
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,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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -584,7 +584,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)
|
||||
|
||||
@ -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;
|
||||
@ -308,6 +309,7 @@ namespace NewHorizons
|
||||
ImageUtilities.ClearCache();
|
||||
AudioUtilities.ClearCache();
|
||||
AssetBundleUtilities.ClearCache();
|
||||
ProcGenBuilder.ClearCache();
|
||||
}
|
||||
|
||||
IsSystemReady = false;
|
||||
@ -483,6 +485,8 @@ namespace NewHorizons
|
||||
// Fix spawn point
|
||||
PlayerSpawnHandler.SetUpPlayerSpawn();
|
||||
|
||||
new GameObject(nameof(NHGameOverManager)).AddComponent<NHGameOverManager>();
|
||||
|
||||
if (isSolarSystem)
|
||||
{
|
||||
// Warp drive
|
||||
@ -835,6 +839,10 @@ namespace NewHorizons
|
||||
AssetBundleUtilities.PreloadBundle(bundle, mod);
|
||||
}
|
||||
}
|
||||
if (addonConfig.gameOver != null)
|
||||
{
|
||||
NHGameOverManager.gameOvers[mod.ModHelper.Manifest.UniqueName] = addonConfig.gameOver;
|
||||
}
|
||||
|
||||
AddonConfigs[mod] = addonConfig;
|
||||
}
|
||||
|
||||
15
NewHorizons/Patches/DeathManagerPatches.cs
Normal file
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();
|
||||
}
|
||||
}
|
||||
@ -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": {
|
||||
|
||||
@ -358,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,
|
||||
@ -6396,18 +6452,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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6417,12 +6499,14 @@
|
||||
"x-enumNames": [
|
||||
"Fast",
|
||||
"Final",
|
||||
"Kazoo"
|
||||
"Kazoo",
|
||||
"None"
|
||||
],
|
||||
"enum": [
|
||||
"fast",
|
||||
"final",
|
||||
"kazoo"
|
||||
"kazoo",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"CometTailModule": {
|
||||
|
||||
@ -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
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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user