using HarmonyLib; using NewHorizons.External.Configs; using NewHorizons.External.Modules.VariableSize; using NewHorizons.External.SerializableData; using NewHorizons.External.SerializableEnums; using NewHorizons.Utility.OWML; using Newtonsoft.Json; using OWML.Utils; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Xml; using UnityEngine; using static NewHorizons.External.Modules.ParticleFieldModule; using NomaiCoordinates = NewHorizons.External.Configs.StarSystemConfig.NomaiCoordinates; namespace NewHorizons.Utility { public static class NewHorizonsExtensions { private static JsonSerializer jsonSerializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, Formatting = Newtonsoft.Json.Formatting.Indented, }; private static StringBuilder stringBuilder = new StringBuilder(); public static string ToSerializedJson(this PlanetConfig planetConfig) { string json = "{}"; using (StringWriter stringWriter = new StringWriter(stringBuilder)) { using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter) { Formatting = Newtonsoft.Json.Formatting.Indented, IndentChar = '\t', Indentation = 1 }) { jsonSerializer.Serialize(jsonTextWriter, planetConfig); json = "{\n\t\"$schema\": \"https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/body_schema.json\"," + stringBuilder.ToString().Substring(1); stringBuilder.Clear(); } } return json; } public static MVector3 ToMVector3(this Vector3 vector3) { return new MVector3(vector3.x, vector3.y, vector3.z); } public static T GetSetting(this Dictionary dict, string settingName) { return (T)dict[settingName]; } public static float GetFalloffExponent(this GravityVolume gv) { if (gv == null) return 0; if (gv._falloffType == GravityVolume.FalloffType.linear) return 1; if (gv._falloffType == GravityVolume.FalloffType.inverseSquared) return 2; return 0; } public static string ToLanguageName(this TextTranslation.Language language) { switch (language) { case TextTranslation.Language.UNKNOWN: case TextTranslation.Language.TOTAL: case TextTranslation.Language.ENGLISH: return "English"; case TextTranslation.Language.SPANISH_LA: return "Spanish"; case TextTranslation.Language.GERMAN: return "German"; case TextTranslation.Language.FRENCH: return "French"; case TextTranslation.Language.ITALIAN: return "Italian"; case TextTranslation.Language.POLISH: return "Polish"; case TextTranslation.Language.PORTUGUESE_BR: return "Portuguese (Brazil)"; case TextTranslation.Language.JAPANESE: return "Japanese"; case TextTranslation.Language.RUSSIAN: return "Russian"; case TextTranslation.Language.CHINESE_SIMPLE: return "Chinese (Simplified)"; case TextTranslation.Language.KOREAN: return "Korean"; case TextTranslation.Language.TURKISH: return "Turkish"; default: return language.ToString().Replace("_", " ").ToTitleCase(); } } public static CultureInfo ToCultureInfo(this TextTranslation.Language language) { return CultureInfo.GetCultures(CultureTypes.AllCultures).FirstOrDefault(culture => { var name = language.ToLanguageName(); return culture.EnglishName == name || culture.NativeName.ToTitleCase() == name; }) ?? CultureInfo.CurrentCulture; } public static string ToUpperFixed(this string str) { return str.ToUpper(TextTranslation.Get().m_language); } public static string ToLowerFixed(this string str) { return str.ToLower(TextTranslation.Get().m_language); } public static string ToUpper(this string str, TextTranslation.Language language) { return str.ToUpper(language.ToCultureInfo()); } public static string ToLower(this string str, TextTranslation.Language language) { return str.ToLower(language.ToCultureInfo()); } public static string ToCamelCase(this string str) { StringBuilder strBuilder = new StringBuilder(str); strBuilder[0] = strBuilder[0].ToString().ToLowerInvariant().ToCharArray()[0]; return strBuilder.ToString(); } public static string ToTitleCase(this string str) { StringBuilder strBuilder = new StringBuilder(str.ToLowerInvariant()); strBuilder[0] = strBuilder[0].ToString().ToUpperInvariant().ToCharArray()[0]; return strBuilder.ToString(); } public static string ToLowercaseNamingConvention(this string str, string separation = " ") { var r = new Regex(@" (?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace); return r.Replace(str, separation).ToLower(); } public static void CopyPropertiesFrom(this object destination, object source) { // If any this null throw an exception if (source == null || destination == null) throw new Exception("Source or/and Destination Objects are null"); // Getting the Types of the objects Type typeDest = destination.GetType(); Type typeSrc = source.GetType(); // Iterate the Properties of the source instance and // populate them from their desination counterparts PropertyInfo[] srcProps = typeSrc.GetProperties(); foreach (PropertyInfo srcProp in srcProps) { if (!srcProp.CanRead) { continue; } PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name); if (targetProperty == null) { continue; } if (!targetProperty.CanWrite) { continue; } if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate) { continue; } if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0) { continue; } if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)) { continue; } try { targetProperty.SetValue(destination, srcProp.GetValue(source, null), null); } catch (Exception) { NHLogger.LogWarning($"Couldn't copy property {targetProperty.Name} from {source} to {destination}"); } } } public static void CopyFieldsFrom(this object destination, object source) { // If any this null throw an exception if (source == null || destination == null) throw new Exception("Source or/and Destination Objects are null"); // Getting the Types of the objects var typeDest = destination.GetType(); var typeSrc = source.GetType(); foreach (var srcField in typeSrc.GetFields()) { var targetField = typeDest.GetField(srcField.Name); if (targetField == null) { continue; } try { targetField.SetValue(destination, srcField.GetValue(source)); } catch (Exception) { NHLogger.LogWarning($"Couldn't copy field {targetField.Name} from {source} to {destination}"); } } } public static void CopyFrom(this object destination, object source) { destination.CopyPropertiesFrom(source); destination.CopyFieldsFrom(source); } public static string SplitCamelCase(this string str) { return Regex.Replace( Regex.Replace( str, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2" ), @"(\p{Ll})(\P{Ll})", "$1 $2" ); } public static GameObject InstantiateInactive(this GameObject original) { if (!original.activeSelf) { return UnityEngine.Object.Instantiate(original); } original.SetActive(false); var copy = UnityEngine.Object.Instantiate(original); original.SetActive(true); return copy; } public static GameObject Instantiate(this GameObject original) { return UnityEngine.Object.Instantiate(original); } public static T DontDestroyOnLoad(this T target) where T : UnityEngine.Object { UnityEngine.Object.DontDestroyOnLoad(target); return target; } public static Material[] MakePrefabMaterials(this Material[] target) { var materials = new List(); foreach (var material in target) { materials.Add(new Material(material).DontDestroyOnLoad()); } return materials.ToArray(); } public static T Rename(this T target, string name) where T : UnityEngine.Object { target.name = name; return target; } /// /// Transform rotation from Local Space to World Space /// /// /// /// public static Quaternion TransformRotation(this Transform transform, Quaternion localRotation) { return transform.rotation * localRotation; } public static bool CheckAllCoordinates(this NomaiCoordinateInterface nomaiCoordinateInterface) => Main.SystemDict.Where(system => system.Value.Config.Vessel?.coords != null).Select(system => new KeyValuePair(system.Key, system.Value.Config.Vessel.coords)).Any(system => nomaiCoordinateInterface.CheckCoordinates(system.Key, system.Value)); public static bool CheckAllCoordinates(this NomaiCoordinateInterface nomaiCoordinateInterface, out string selectedSystem) { foreach (KeyValuePair cbs in Main.SystemDict.Where(system => system.Value.Config.Vessel?.coords != null).Select(system => new KeyValuePair(system.Key, system.Value.Config.Vessel.coords))) { if (CheckCoordinates(nomaiCoordinateInterface, cbs.Key, cbs.Value)) { selectedSystem = cbs.Key; return true; } } selectedSystem = null; return false; } public static bool CheckCoordinates(this NomaiCoordinateInterface nomaiCoordinateInterface, string system, NomaiCoordinates coordinates) { bool xCorrect = nomaiCoordinateInterface._nodeControllers[0].CheckCoordinate(coordinates.x); bool yCorrect = nomaiCoordinateInterface._nodeControllers[1].CheckCoordinate(coordinates.y); bool zCorrect = nomaiCoordinateInterface._nodeControllers[2].CheckCoordinate(coordinates.z); NHLogger.LogVerbose($"Coordinate Check for {system}: {xCorrect}, {yCorrect}, {zCorrect} [{string.Join("-", coordinates.x)}, {string.Join("-", coordinates.y)}, {string.Join("-", coordinates.z)}]"); return xCorrect && yCorrect && zCorrect; } public static FluidVolume.Type ConvertToOW(this NHFluidType fluidType, FluidVolume.Type @default = FluidVolume.Type.NONE) => EnumUtils.Parse(fluidType.ToString(), @default); public static OWAudioMixer.TrackName ConvertToOW(this NHAudioMixerTrackName trackName, OWAudioMixer.TrackName @default = OWAudioMixer.TrackName.Environment) => EnumUtils.Parse(trackName.ToString(), @default); public static OWAudioSource.ClipSelectionOnPlay ConvertToOW(this NHClipSelectionType clipSelection, OWAudioSource.ClipSelectionOnPlay @default = OWAudioSource.ClipSelectionOnPlay.RANDOM) => EnumUtils.Parse(clipSelection.ToString(), @default); public static void SmoothLookDir(this GameObject go, Vector3 direction, float dt, float angularVelocity) { var start = go.transform.rotation; var end = Quaternion.FromToRotation(Vector3.forward, direction); var angle = Quaternion.Angle(start, end); go.transform.rotation = Quaternion.Slerp(start, end, (angularVelocity / angle) * dt); } public static void LookDir(this GameObject go, Vector3 direction) { go.transform.rotation = Quaternion.FromToRotation(Vector3.forward, direction); } public static AnimationCurve ToAnimationCurve(this TimeValuePair[] pairs) { var curve = new AnimationCurve(); if (pairs != null) { foreach (var pair in pairs) { curve.AddKey(new Keyframe(pair.time, pair.value)); } } return curve; } public static AnimationCurve ToAnimationCurve(this HeightDensityPair[] pairs) { var curve = new AnimationCurve(); if (pairs != null) { foreach (var pair in pairs) { curve.AddKey(new Keyframe(pair.height, pair.density)); } } return curve; } public static List GetChildNodes(this XmlNode parentNode, string tagName) { return parentNode.ChildNodes.Cast().Where(node => node.LocalName == tagName).ToList(); } public static XmlNode GetChildNode(this XmlNode parentNode, string tagName) { return parentNode.ChildNodes.Cast().FirstOrDefault(node => node.LocalName == tagName); } public static string TruncateWhitespaceAndToLower(this string text) { // return Regex.Replace(text.Trim(), @"[^\S\r\n]+", "GUH"); return Regex.Replace(text.Trim(), @"\s+", " ").ToLowerInvariant(); } public static void Stabilize(this SingularityController singularity) { singularity._state = SingularityController.State.Stable; singularity._timer = 0f; singularity._baseRadius = singularity._targetRadius; singularity._currentRadius = singularity._targetRadius; singularity._renderer.SetActivation(active: true); singularity._renderer.SetMaterialProperty(singularity._propID_Radius, singularity._targetRadius); if (singularity._owAmbientSource != null) singularity._owAmbientSource.FadeIn(0.5f); singularity.enabled = true; } public static void OpenEyesImmediate(this PlayerCameraEffectController playerCameraEffectController) { playerCameraEffectController._lastOpenness = 1; playerCameraEffectController._wakeCurve = playerCameraEffectController._fastWakeCurve; playerCameraEffectController._isOpeningEyes = false; playerCameraEffectController._isClosingEyes = false; playerCameraEffectController._eyeAnimDuration = 0; playerCameraEffectController._eyeAnimStartTime = Time.time; playerCameraEffectController._owCamera.postProcessingSettings.eyeMask.openness = 1; playerCameraEffectController._owCamera.postProcessingSettings.bloom.threshold = playerCameraEffectController._owCamera.postProcessingSettings.bloomDefault.threshold; playerCameraEffectController._owCamera.postProcessingSettings.eyeMaskEnabled = false; } public static void CloseEyesImmediate(this PlayerCameraEffectController playerCameraEffectController) { playerCameraEffectController._lastOpenness = 0f; playerCameraEffectController._wakeCurve = playerCameraEffectController._fastWakeCurve; playerCameraEffectController._isOpeningEyes = false; playerCameraEffectController._isClosingEyes = false; playerCameraEffectController._eyeAnimDuration = 0; playerCameraEffectController._eyeAnimStartTime = Time.time; playerCameraEffectController._owCamera.postProcessingSettings.eyeMask.openness = 0f; playerCameraEffectController._owCamera.postProcessingSettings.bloom.threshold = 0f; playerCameraEffectController._owCamera.postProcessingSettings.eyeMaskEnabled = true; } public static float GetSecondsBeforeSupernovaPlayTime(this GlobalMusicController globalMusicController) { var clip = globalMusicController._endTimesSource.audioLibraryClip; if (clip == AudioType.EndOfTime || clip == AudioType.EndOfTime_Dream) return GlobalMusicController.secondsBeforeSupernovaPlayTime; return globalMusicController._endTimesSource.clip.length; } public static CodeMatcher LogInstructions(this CodeMatcher matcher, string prefix) { matcher.InstructionEnumeration().LogInstructions(prefix); return matcher; } public static IEnumerable LogInstructions(this IEnumerable instructions, string prefix) { var message = prefix; foreach (var instruction in instructions) message += $"\n{instruction}"; Debug.LogError(message); return instructions; } } }