diff --git a/Helper/Abstraction/SettingsBase.cs b/Helper/Abstraction/SettingsBase.cs new file mode 100644 index 0000000..2d91598 --- /dev/null +++ b/Helper/Abstraction/SettingsBase.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SimpleJSON; + +namespace NoStopMod.Helper.Abstraction +{ + interface SettingsBase + { + void Load(ref JSONNode json); + + void Save(ref JSONNode json); + + } +} diff --git a/Helper/EventListener.cs b/Helper/EventListener.cs new file mode 100644 index 0000000..d71c311 --- /dev/null +++ b/Helper/EventListener.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +namespace NoStopMod.Helper +{ + class EventListener + { + + public readonly List> listeners = new List>(); + + private readonly string errorMessage; + + public EventListener(string errorMessage) + { + this.errorMessage = errorMessage; + } + + public void Add(Action listener) + { + listeners.Add(listener); + } + + public void Invoke(T value) + { + for (int i = 0; i < listeners.Count; i++) + { + try + { + listeners[i].Invoke(value); + } + catch (Exception e) + { + NoStopMod.mod.Logger.Error("Error on " + errorMessage + " : " + e.Message + ", " + e.StackTrace); + } + } + } + + } +} diff --git a/Helper/JSONHelper.cs b/Helper/JSONHelper.cs new file mode 100644 index 0000000..592a0be --- /dev/null +++ b/Helper/JSONHelper.cs @@ -0,0 +1,48 @@ +using SimpleJSON; +using System; +using System.Collections.Generic; + +namespace NoStopMod.Helper +{ + class JSONHelper + { + + public static JSONNode CreateEmptyNode() + { + return JSON.Parse("{}"); + } + + public static long ConvertLong(JSONNode node) + { + return node.AsLong; + } + + public static int ConvertInt(JSONNode node) + { + return node.AsInt; + } + + public static List ReadArray(ref JSONNode node, string name, Func converter) + { + JSONArray array = node[name].AsArray; + + List results = new List(array.Count); + for (int i = 0; i < array.Count; i++) + { + results.Add(converter.Invoke(array[i])); + } + return results; + } + + public static JSONArray WriteArray(List list, Func converter) + { + JSONArray array = new JSONArray(); + for (int i = 0; i < list.Count; i++) + { + array.Add(converter.Invoke(list[i])); + } + return array; + } + + } +} diff --git a/Helper/ReflectionField.cs b/Helper/ReflectionField.cs new file mode 100644 index 0000000..45c9113 --- /dev/null +++ b/Helper/ReflectionField.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace NoStopMod.Helper +{ + public class ReflectionField + { + private static FieldInfo fieldInfo; + + private readonly string[] fieldNames; + + public ReflectionField(params string[] fieldNames) + { + this.fieldNames = fieldNames; + } + + public FieldInfo GetFieldInfo(Type type) + { + if (fieldInfo == null) + { + for (int i=0;i < fieldNames.Count();i++) + { + fieldInfo = type.GetField(fieldNames[i], + BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty); + if (fieldInfo != null) break; + } + if (fieldInfo == null) + { + NoStopMod.mod.Logger.Error("Cannot find fieldInfo : (type=" + type + ", name" + fieldNames + ")"); + } + } + return fieldInfo; + } + + public void SetValue(object obj, T value) + { + GetFieldInfo(obj.GetType())?.SetValue(obj, value); + } + + public T GetValue(object obj) + { + return (T) GetFieldInfo(obj.GetType())?.GetValue(obj); + } + + } +} diff --git a/Helper/SimpleGUI.cs b/Helper/SimpleGUI.cs new file mode 100644 index 0000000..cc831c6 --- /dev/null +++ b/Helper/SimpleGUI.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NoStopMod.Helper +{ + class SimpleGUI + { + + public static void Toggle(ref bool previous, string message, Action callback) + { + bool current = GUILayout.Toggle(previous, message); + if (current != previous) + { + previous = current; + callback.Invoke(current); + } + } + + + } +} diff --git a/HyperRabbit/HyperRabbitManager.cs b/HyperRabbit/HyperRabbitManager.cs new file mode 100644 index 0000000..589077a --- /dev/null +++ b/HyperRabbit/HyperRabbitManager.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using UnityModManagerNet; + +namespace NoStopMod.HyperRabbit +{ + class HyperRabbitManager + { + + public static HyperRabbitSettings settings; + + public static void Init() + { + NoStopMod.onGUIListener.Add(OnGUI); + + settings = new HyperRabbitSettings(); + Settings.settings.Add(settings); + } + + private static void OnGUI(UnityModManager.ModEntry modEntry) + { + GUILayout.BeginHorizontal("HyperRabbit"); + + GUILayout.Label("Max otto tile per frame (5 + " + settings.maxTilePerFrame + ")"); + + settings.maxTilePerFrame = (int) GUILayout.HorizontalSlider(settings.maxTilePerFrame, 0, 100); + + GUILayout.EndHorizontal(); + } + + } +} diff --git a/HyperRabbit/HyperRabbitPatches.cs b/HyperRabbit/HyperRabbitPatches.cs new file mode 100644 index 0000000..a77125a --- /dev/null +++ b/HyperRabbit/HyperRabbitPatches.cs @@ -0,0 +1,28 @@ +using HarmonyLib; + +namespace NoStopMod.HyperRabbit +{ + class HyperRabbitPatches + { + + [HarmonyPatch(typeof(scrController), "PlayerControl_Update")] + private static class scrController_PlayerControl_Update_Patch + { + public static void Postfix(scrController __instance) + { + if (RDC.auto && scrController.isGameWorld) + { + int num = HyperRabbitManager.settings.maxTilePerFrame; + while (num > 0 && __instance.chosenplanet.AutoShouldHitNow()) + { + __instance.keyBufferCount = 0; + __instance.Hit(); + num--; + } + } + } + } + + } + +} diff --git a/HyperRabbit/HyperRabbitSettings.cs b/HyperRabbit/HyperRabbitSettings.cs new file mode 100644 index 0000000..f9c7d39 --- /dev/null +++ b/HyperRabbit/HyperRabbitSettings.cs @@ -0,0 +1,26 @@ +using NoStopMod.Helper; +using NoStopMod.Helper.Abstraction; +using SimpleJSON; + +namespace NoStopMod.HyperRabbit +{ + class HyperRabbitSettings : SettingsBase + { + public int maxTilePerFrame = 0; + + public void Load(ref JSONNode json) + { + JSONNode node = json["HyperRabbit"]; + + maxTilePerFrame = node["maxTilePerFrame"].AsInt; + } + + public void Save(ref JSONNode json) + { + JSONNode node = JSONHelper.CreateEmptyNode(); + node["maxTilePerFrame"].AsInt = maxTilePerFrame; + + json["HyperRabbit"] = node; + } + } +} diff --git a/Info.json b/Info.json index b6f2819..291b88a 100644 --- a/Info.json +++ b/Info.json @@ -1,8 +1,11 @@ { "Id": "NoStopMod", + "DisplayName": "NoStopMod", "Author": "Luxus", - "Version": "1.0.0", - "ManagerVersion": "1.0.0", + "Version": "1.2.3", + "ManagerVersion": "0.23.4.0", "AssemblyName": "NoStopMod.dll", - "EntryMethod": "NoStopMod.NoStopMod.Load" -} \ No newline at end of file + "EntryMethod": "NoStopMod.NoStopMod.Load", + "HomePage": "https://github.com/Luxusio/ADOFAI-NoStopMod", + "Repository": "https://raw.githubusercontent.com/Luxusio/ADOFAI-NoStopMod/master/Repository.json" +} diff --git a/InputFixer/HitIgnore/HitIgnoreManager.cs b/InputFixer/HitIgnore/HitIgnoreManager.cs new file mode 100644 index 0000000..9289276 --- /dev/null +++ b/InputFixer/HitIgnore/HitIgnoreManager.cs @@ -0,0 +1,88 @@ +using NoStopMod.InputFixer.HitIgnore.KeyLimiter; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace NoStopMod.InputFixer.HitIgnore +{ + class HitIgnoreManager + { + private static Dictionary dictionary; + + public static bool scnCLS_searchMode; + public static scrController.States scrController_state; + + public static void Init() + { + if (GCS.sceneToLoad == null) GCS.sceneToLoad = "scnNewIntro"; + + dictionary = new Dictionary(); + + bool[] ignoreScnNewIntro = Enumerable.Repeat(false, 1024).ToArray(); + dictionary["scnNewIntro"] = ignoreScnNewIntro; + ignoreScnNewIntro[(int)KeyCode.BackQuote] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha0] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha1] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha2] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha3] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha4] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha5] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha6] = true; + ignoreScnNewIntro[(int)KeyCode.Alpha7] = true; + + bool[] ignoreScnCLS = Enumerable.Repeat(false, 1024).ToArray(); + dictionary["scnCLS"] = ignoreScnCLS; + ignoreScnCLS[(int)KeyCode.S] = true; + ignoreScnCLS[(int)KeyCode.Delete] = true; + ignoreScnCLS[(int)KeyCode.F] = true; + ignoreScnCLS[(int)KeyCode.O] = true; + ignoreScnCLS[(int)KeyCode.Alpha7] = true; + ignoreScnCLS[(int)KeyCode.UpArrow] = true; + ignoreScnCLS[(int)KeyCode.DownArrow] = true; + + scnCLS_searchMode = false; + + if (scrController.instance != null) + { + scrController_state = (scrController.States) scrController.instance.GetState(); + } + + KeyLimiterManager.Init(); + } + + public static bool shouldBeIgnored(KeyCode keyCode) + { + if (keyCode == KeyCode.Escape) return true; + if (KeyLimiterManager.isChangingLimitedKeys) + { + KeyLimiterManager.UpdateKeyLimiter(keyCode); + return true; + } + + if (scrController_state != scrController.States.PlayerControl) + { + return true; + } + + bool[] ignoreScnCLS; + if (dictionary.TryGetValue(GCS.sceneToLoad, out ignoreScnCLS)) + { + if (GCS.sceneToLoad == "scnCLS" && scnCLS_searchMode) + { + return true; + } + if(ignoreScnCLS[(int) keyCode]) return true; + } + + if(KeyLimiterManager.settings.enable) + { + return !KeyLimiterManager.IsKeyEnabled(keyCode); + } + + return false; + } + + + } +} diff --git a/InputFixer/HitIgnore/HitIgnorePatches.cs b/InputFixer/HitIgnore/HitIgnorePatches.cs new file mode 100644 index 0000000..41e553b --- /dev/null +++ b/InputFixer/HitIgnore/HitIgnorePatches.cs @@ -0,0 +1,49 @@ +using HarmonyLib; +using MonsterLove.StateMachine; +using System; + +namespace NoStopMod.InputFixer.HitIgnore +{ + class HitIgnorePatches + { + + // scnCLS_serachMode //////////////////////////////////// + [HarmonyPatch(typeof(scnCLS), "Refresh")] + private static class scnCLS_Refresh_Patch + { + public static void Postfix(scnCLS __instance, ref bool ___searchMode) + { + HitIgnoreManager.scnCLS_searchMode = ___searchMode; + } + } + + [HarmonyPatch(typeof(scnCLS), "ToggleSearchMode")] + private static class scnCLS_ToggleSearchMode_Patch + { + public static void Postfix(scnCLS __instance, ref bool ___searchMode) + { + HitIgnoreManager.scnCLS_searchMode = ___searchMode; + } + } + + // scrController_state ////////////////////////// + [HarmonyPatch(typeof(StateEngine), "ChangeState")] + private static class StateEngine_ChangeState_Patch + { + public static void Postfix(StateEngine __instance, Enum newState, StateTransition transition) + { + HitIgnoreManager.scrController_state = (scrController.States) newState; + } + } + + [HarmonyPatch(typeof(scrController), "OnLandOnPortal")] + private static class scrController_OnLandOnPortal_Patch + { + public static void Postfix(scrController __instance) + { + HitIgnoreManager.scrController_state = scrController.States.Won; + } + } + + } +} diff --git a/InputFixer/HitIgnore/KeyLimiter/KeyLimiterManager.cs b/InputFixer/HitIgnore/KeyLimiter/KeyLimiterManager.cs new file mode 100644 index 0000000..75bc237 --- /dev/null +++ b/InputFixer/HitIgnore/KeyLimiter/KeyLimiterManager.cs @@ -0,0 +1,100 @@ +using System; +using System.Linq; +using UnityEngine; +using UnityModManagerNet; + +namespace NoStopMod.InputFixer.HitIgnore.KeyLimiter +{ + class KeyLimiterManager + { + + public static KeyLimiterSettings settings; + + public static bool isChangingLimitedKeys = false; + + private static bool[] enableKey = Enumerable.Repeat(false, 1024).ToArray(); + + public static void Init() + { + NoStopMod.onGUIListener.Add(OnGUI); + NoStopMod.onHideGUIListener.Add(OnHideGUI); + Settings.settingsLoadListener.Add(_ => InitEnableKey()); + + settings = new KeyLimiterSettings(); + Settings.settings.Add(settings); + } + + private static void InitEnableKey() + { + enableKey = Enumerable.Repeat(false, 1024).ToArray(); + for (int i = 0; i < settings.limitKeys.Count; i++) + { + enableKey[(int)settings.limitKeys[i]] = true; + } + } + + private static void OnGUI(UnityModManager.ModEntry entry) + { + settings.enable = GUILayout.Toggle(settings.enable, "Enable key limiter"); + if (settings.enable) + { + //GUILayout.Label("", Array.Empty()); + GUILayout.BeginHorizontal(Array.Empty()); + GUILayout.Space(20f); + + GUILayout.BeginVertical(Array.Empty()); + GUILayout.Space(8f); + GUILayout.EndVertical(); + + for (int i = 0; i < settings.limitKeys.Count; i++) + { + GUILayout.Label(settings.limitKeys[i].ToString(), Array.Empty()); + GUILayout.Space(8f); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(Array.Empty()); + if (GUILayout.Button(isChangingLimitedKeys ? "Complete" : "Change Limited Keys", Array.Empty())) + { + isChangingLimitedKeys = !isChangingLimitedKeys; + } + if (isChangingLimitedKeys) + { + GUILayout.Label("Press keys to add / remove limited key", Array.Empty()); + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + } + } + + private static void OnHideGUI(UnityModManager.ModEntry entry) + { + isChangingLimitedKeys = false; + } + + public static bool IsKeyEnabled(KeyCode keyCode) + { + return enableKey[(int) keyCode]; + } + + public static void UpdateKeyLimiter(KeyCode keyCode) + { + int idx = settings.limitKeys.IndexOf(keyCode); + if (idx == -1) + { + settings.limitKeys.Add(keyCode); + enableKey[(int)keyCode] = true; + } + else + { + settings.limitKeys.RemoveAt(idx); + enableKey[(int)keyCode] = false; + } + + } + + } +} diff --git a/InputFixer/HitIgnore/KeyLimiter/KeyLimiterSettings.cs b/InputFixer/HitIgnore/KeyLimiter/KeyLimiterSettings.cs new file mode 100644 index 0000000..e63ab50 --- /dev/null +++ b/InputFixer/HitIgnore/KeyLimiter/KeyLimiterSettings.cs @@ -0,0 +1,33 @@ +using NoStopMod.Helper; +using NoStopMod.Helper.Abstraction; +using SimpleJSON; +using System.Collections.Generic; +using UnityEngine; + +namespace NoStopMod.InputFixer.HitIgnore.KeyLimiter +{ + class KeyLimiterSettings : SettingsBase + { + + public bool enable = false; + public List limitKeys = new List(); + + public void Load(ref JSONNode json) + { + JSONNode node = json["KeyLimiter"]; + + enable = node["enable"].AsBool; + limitKeys = JSONHelper.ReadArray(ref node, "limitKeys", (arrayNode) => { return (KeyCode) arrayNode.AsInt; }); + + } + + public void Save(ref JSONNode json) + { + JSONNode node = JSONHelper.CreateEmptyNode(); + node["enable"].AsBool = enable; + node["limitKeys"] = JSONHelper.WriteArray(limitKeys, (element) => { return (int) element; }); + + json["KeyLimiter"] = node; + } + } +} diff --git a/InputFixer/InputFixerManager.cs b/InputFixer/InputFixerManager.cs new file mode 100644 index 0000000..962aa6a --- /dev/null +++ b/InputFixer/InputFixerManager.cs @@ -0,0 +1,159 @@ +using NoStopMod.InputFixer.HitIgnore; +using NoStopMod.Helper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using UnityEngine; +using UnityModManagerNet; +using NoStopMod.InputFixer.SyncFixer; + +namespace NoStopMod.InputFixer +{ + class InputFixerManager + { + public static InputFixerSettings settings; + + private static Thread thread; + public static Queue>> keyQueue = new Queue>>(); + + + //public static long offsetTick; + public static long currPressTick; + + public static bool jumpToOtherClass = false; + public static bool editInputLimit = false; + + private static bool[] mask; + + public static void Init() + { + NoStopMod.onToggleListener.Add(UpdateEnableAsync); + NoStopMod.onGUIListener.Add(OnGUI); + NoStopMod.onApplicationQuitListener.Add(_ => Stop()); + Settings.settingsLoadListener.Add(_ => UpdateEnableAsync(settings.enableAsync)); + + settings = new InputFixerSettings(); + Settings.settings.Add(settings); + + mask = Enumerable.Repeat(false, 1024).ToArray(); + + HitIgnoreManager.Init(); + SyncFixerManager.Init(); + } + + private static void OnGUI(UnityModManager.ModEntry modEntry) + { + //GUILayout.BeginVertical("Input"); + SimpleGUI.Toggle(ref settings.enableAsync, "Toggle Input Asynchronously", UpdateEnableAsync); + //GUILayout.EndVertical(); + } + + private static void UpdateEnableAsync(bool value) + { + if (value) + { + Start(); + } + else + { + Stop(); + } + } + + public static void Start() + { + Stop(); + if (settings.enableAsync) { + thread = new Thread(Run); + thread.Start(); + } + } + + public static void Stop() + { + if (thread != null) + { + thread.Abort(); + thread = null; + } + keyQueue.Clear(); + } + + private static bool GetKeyDown(int idx) + { + if (mask[idx]) + { + if (!Input.GetKey((KeyCode)idx)) + { + mask[idx] = false; + } + } + else + { + if (Input.GetKey((KeyCode)idx)) + { + mask[idx] = true; + return true; + } + } + return false; + } + + private static void Run() + { + long prevTick, currTick; + prevTick = DateTime.Now.Ticks; + while (settings.enableAsync) + { + currTick = DateTime.Now.Ticks; + if (currTick > prevTick) + { + prevTick = currTick; + UpdateKeyQueue(currTick); + } + } + } + + public static void UpdateKeyQueue(long currTick) + { + List keyCodes = getPressedKeys(); + if (keyCodes.Any()) + { + keyQueue.Enqueue(new Tuple>(currTick, keyCodes)); + } + } + + private static List getPressedKeys() + { + List keyCodes = new List(); + + for (int i = 0; i < 320; i++) + { + if (GetKeyDown(i)) + { + keyCodes.Add((KeyCode)i); + } + } + + for (int i = 323; i <= 329; i++) + { + if (GetKeyDown(i)) + { + keyCodes.Add((KeyCode)i); + } + } + + return keyCodes; + } + + public static double getAngle(scrPlanet __instance, double ___snappedLastAngle, long nowTick) + { + return ___snappedLastAngle + (SyncFixerManager.newScrConductor.getSongPosition(__instance.conductor, nowTick) - __instance.conductor.lastHit) / __instance.conductor.crotchet + * 3.141592653598793238 * __instance.controller.speed * (double)(__instance.controller.isCW ? 1 : -1); + } + + + + } +} diff --git a/InputFixer/InputFixerPatches.cs b/InputFixer/InputFixerPatches.cs new file mode 100644 index 0000000..c0e33a5 --- /dev/null +++ b/InputFixer/InputFixerPatches.cs @@ -0,0 +1,140 @@ +using DG.Tweening; +using HarmonyLib; +using NoStopMod.InputFixer.HitIgnore; +using NoStopMod.InputFixer.SyncFixer; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace NoStopMod.InputFixer +{ + public static class AsyncInputPatches + { + + [HarmonyPatch(typeof(scrController), "Awake")] + private static class scrController_Awake_Patch + { + public static void Postfix(scrController __instance) + { + InputFixerManager.Start(); + } + } + + [HarmonyPatch(typeof(scrConductor), "Update")] + private static class scrConductor_Update_Patch_Time + { + public static void Postfix(scrConductor __instance) + { + + if (!InputFixerManager.settings.enableAsync) + { + InputFixerManager.UpdateKeyQueue(NoStopMod.CurrFrameTick()); + } + + while (InputFixerManager.keyQueue.Any()) + { + long tick; + List keyCodes; + InputFixerManager.keyQueue.Dequeue().Deconstruct(out tick, out keyCodes); + + if (AudioListener.pause || RDC.auto) continue; + + scrController controller = __instance.controller; + int count = 0; + for (int i = 0; i < keyCodes.Count(); i++) + { + if (HitIgnoreManager.shouldBeIgnored(keyCodes[i])) continue; + if (++count > 4) break; + } + + InputFixerManager.currPressTick = tick - SyncFixerManager.newScrConductor.offsetTick; + controller.keyBufferCount += count; + while (controller.keyBufferCount > 0) + { + controller.keyBufferCount--; + InputFixerManager.jumpToOtherClass = true; + controller.chosenplanet.Update_RefreshAngles(); + controller.Hit(); + } + } + + } + } + + [HarmonyPatch(typeof(scrController), "CountValidKeysPressed")] + private static class scrController_CountValidKeysPressed_Patch + { + public static bool Prefix(scrController __instance, ref int __result) + { + __result = 0; + return false; + } + } + + [HarmonyPatch(typeof(scrPlanet), "Update_RefreshAngles")] + private static class scrPlanet_Update_RefreshAngles_Patch + { + public static bool Prefix(scrPlanet __instance, ref double ___snappedLastAngle) + { + + if (InputFixerManager.jumpToOtherClass) + { + InputFixerManager.jumpToOtherClass = false; + __instance.angle = InputFixerManager.getAngle(__instance, ___snappedLastAngle, InputFixerManager.currPressTick); + return false; + } + + if (!__instance.isChosen || __instance.conductor.crotchet == 0.0) return false; + + if (!GCS.d_stationary) + { + long nowTick = NoStopMod.CurrFrameTick() - SyncFixerManager.newScrConductor.offsetTick; + __instance.angle = InputFixerManager.getAngle(__instance, ___snappedLastAngle, nowTick); + + if (__instance.shouldPrint) + { + __instance.shouldPrint = false; + } + } + else + { + if (Input.GetKey(KeyCode.DownArrow)) + { + __instance.angle += 0.10000000149011612; + } + if (Input.GetKey(KeyCode.UpArrow)) + { + __instance.angle -= 0.10000000149011612; + } + } + float num = (float)__instance.angle; + if (__instance.currfloor != null) + { + if (__instance.controller.rotationEase != Ease.Linear) + { + float num2 = scrMisc.EasedAngle((float)___snappedLastAngle, (float)__instance.targetExitAngle, num, __instance.controller.rotationEase, __instance.controller.rotationEaseParts); + if (!float.IsNaN(num2) && !float.IsInfinity(num2)) + { + num = num2; + } + } + if (__instance.controller.stickToFloor) + { + num -= (__instance.currfloor.transform.rotation.eulerAngles - __instance.currfloor.startRot).z * 0.017453292f; + } + } + Vector3 position = __instance.transform.position; + __instance.other.transform.position = new Vector3(position.x + Mathf.Sin(num) * __instance.cosmeticRadius, position.y + Mathf.Cos(num) * __instance.cosmeticRadius, position.z); + if (__instance.is3D) + { + __instance.other.transform.position = new Vector3(position.x + Mathf.Sin((float)__instance.angle) * __instance.cosmeticRadius, position.y, position.z + Mathf.Cos((float)__instance.angle) * __instance.cosmeticRadius); + } + + return false; + } + } + + + } +} diff --git a/InputFixer/InputFixerSettings.cs b/InputFixer/InputFixerSettings.cs new file mode 100644 index 0000000..90236d7 --- /dev/null +++ b/InputFixer/InputFixerSettings.cs @@ -0,0 +1,31 @@ +using NoStopMod.Helper; +using NoStopMod.Helper.Abstraction; +using SimpleJSON; +using System; + +namespace NoStopMod.InputFixer +{ + class InputFixerSettings : SettingsBase + { + public bool enableAsync = false; + //public bool enableKeyLimit = false; + + public void Load(ref JSONNode json) + { + JSONNode node = json["InputFixer"]; + + enableAsync = node["enableAsync"].AsBool; + //enableKeyLimit = node["enableKeyLimit"].AsBool; + JSONArray array = node["LimitKeys"].AsArray; + } + + public void Save(ref JSONNode json) + { + JSONNode node = JSONHelper.CreateEmptyNode(); + node["enableAsync"].AsBool = enableAsync; + //node["enableKeyLimit"].AsBool = enableKeyLimit; + + json["InputFixer"] = node; + } + } +} diff --git a/InputFixer/SyncFixer/SongAudioSource.cs b/InputFixer/SyncFixer/SongAudioSource.cs new file mode 100644 index 0000000..34a5898 --- /dev/null +++ b/InputFixer/SyncFixer/SongAudioSource.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NoStopMod.InputFixer.SyncFixer +{ + class SongAudioSource : MonoBehaviour + { + + //public AudioSource m_audioSource; + + //private bool m_playbackUnmutePending; + //private double? m_startTime; + + //public void PlayScheduled(double dspStartTime) + //{ + // m_startTime = dspStartTime; + // m_playbackUnmutePending = true; // weird bug, plays an artifact before scheduled time? + // m_audioSource.PlayScheduled(dspStartTime); + //} + + //void OnAudioFilterRead(float[] data, int channels) + //{ + // if (!m_playbackUnmutePending) + // { + // return; + // } + + // if (AudioSettings.dspTime >= m_startTime) + // { + // m_playbackUnmutePending = false; + // return; + // } + + // for (int i = 0; i < data.Length; i++) + // { + // data[i] = 0f; // mute until start time cos PlaySchedule artifact. + // } + //} + + } +} diff --git a/InputFixer/SyncFixer/SyncFixerManager.cs b/InputFixer/SyncFixer/SyncFixerManager.cs new file mode 100644 index 0000000..d5fa08f --- /dev/null +++ b/InputFixer/SyncFixer/SyncFixerManager.cs @@ -0,0 +1,19 @@ +namespace NoStopMod.InputFixer.SyncFixer +{ + class SyncFixerManager + { + + public static newScrConductor newScrConductor; + + public static void Init() + { + newScrConductor = new newScrConductor(); + if (scrConductor.instance != null) + { + newScrConductor.dspTime = newScrConductor.dspTimeField.GetValue(scrConductor.instance); + newScrConductor.FixOffsetTick(); + } + } + + } +} diff --git a/InputFixer/SyncFixer/SyncFixerPatchesScrConductor.cs b/InputFixer/SyncFixer/SyncFixerPatchesScrConductor.cs new file mode 100644 index 0000000..48877ff --- /dev/null +++ b/InputFixer/SyncFixer/SyncFixerPatchesScrConductor.cs @@ -0,0 +1,169 @@ +using ADOFAI; +using HarmonyLib; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace NoStopMod.InputFixer.SyncFixer +{ + class SyncFixerPatchesScrConductor + { + //[HarmonyPatch(typeof(scrConductor), "StartMusicCo")] + //private static class scrConductor_StartMusicCo_Patch + //{ + // public static IEnumerator Transpiler(scrConductor __instance, Action onComplete, Action onSongScheduled = null) + // { + + [HarmonyPatch(typeof(scrConductor), "Awake")] + private static class scrConductor_Awake_Patch + { + public static bool Prefix(scrConductor __instance) + { + SyncFixerManager.newScrConductor.Awake(__instance); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "Start")] + private static class scrConductor_Start_Patch + { + public static bool Prefix(scrConductor __instance) + { + SyncFixerManager.newScrConductor.Start(__instance); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "Rewind")] + private static class scrConductor_Rewind_Patch + { + public static bool Prefix(scrConductor __instance) + { + SyncFixerManager.newScrConductor.Rewind(__instance); + return false; + } + } + + //[HarmonyPatch(typeof(scrConductor), "SetupConductorWithLevelData")] + //private static class scrConductor_SetupConductorWithLevelData_Patch + //{ + // public static bool Prefix(scrConductor __instance, LevelData levelData) + // { + // SyncFixerManager.newScrConductor.SetupConductorWithLevelData(__instance, levelData); + // return false; + // } + //} + + //[HarmonyPatch(typeof(scrConductor), "PlayHitTimes")] + //private static class scrConductor_PlayHitTimes_Patch + //{ + // public static bool Prefix(scrConductor __instance) + // { + // SyncFixerManager.newScrConductor.PlayHitTimes(__instance); + // return false; + // } + //} + + [HarmonyPatch(typeof(scrConductor), "GetCountdownTime")] + private static class scrConductor_GetCountdownTime_Patch + { + public static bool Prefix(scrConductor __instance, ref double __result, int i) + { + __result = SyncFixerManager.newScrConductor.GetCountdownTime(__instance, i); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "StartMusic")] + private static class scrConductor_StartMusic_Patch + { + public static bool Prefix(scrConductor __instance, Action onComplete = null, Action onSongScheduled = null) + { + SyncFixerManager.newScrConductor.StartMusic(__instance, onComplete, onSongScheduled); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "StartMusicCo")] + private static class scrConductor_StartMusicCo_Patch + { + public static bool Prefix(IEnumerator __result, scrConductor __instance, Action onComplete, Action onSongScheduled = null) + { + __result = SyncFixerManager.newScrConductor.StartMusicCo(__instance, onComplete, onSongScheduled); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "ToggleHasSongStarted")] + private static class scrConductor_ToggleHasSongStarted_Patch + { + public static bool Prefix(IEnumerator __result, scrConductor __instance, double songstarttime) + { + __result = SyncFixerManager.newScrConductor.ToggleHasSongStarted(__instance, songstarttime); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "Update")] + private static class scrConductor_Update_Patch + { + public static bool Prefix(scrConductor __instance) + { + SyncFixerManager.newScrConductor.Update(__instance); + return false; + } + } + + //[HarmonyPatch(typeof(scrConductor), "OnBeat")] + //private static class scrConductor_OnBeat_Patch + //{ + // public static bool Prefix(scrConductor __instance) + // { + // SyncFixerManager.newScrConductor.OnBeat(__instance); + // return false; + // } + //} + + [HarmonyPatch(typeof(scrConductor), "PlaySfx")] + private static class scrConductor_PlaySfx_Patch + { + public static bool Prefix(scrConductor __instance, int num, float volume = 1f, bool ignoreListenerPause = false) + { + SyncFixerManager.newScrConductor.PlaySfx(__instance, num, volume, ignoreListenerPause); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "ScrubMusicToTile")] + private static class scrConductor_ScrubMusicToTile_Patch + { + public static bool Prefix(scrConductor __instance, int tileID) + { + SyncFixerManager.newScrConductor.ScrubMusicToTile(__instance, tileID); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "DesyncFix")] + private static class scrConductor_DesyncFix_Patch + { + public static bool Prefix(IEnumerator __result, scrConductor __instance) + { + __result = SyncFixerManager.newScrConductor.DesyncFix(__instance); + return false; + } + } + + [HarmonyPatch(typeof(scrConductor), "SaveVisualOffset")] + private static class scrConductor_SaveVisualOffset_Patch + { + public static bool Prefix(scrConductor __instance, double offset) + { + SyncFixerManager.newScrConductor.SaveVisualOffset(offset); + return false; + } + } + + + } +} diff --git a/InputFixer/SyncFixer/SyncFixerPatchesScrController.cs b/InputFixer/SyncFixer/SyncFixerPatchesScrController.cs new file mode 100644 index 0000000..ee6842a --- /dev/null +++ b/InputFixer/SyncFixer/SyncFixerPatchesScrController.cs @@ -0,0 +1,86 @@ +using DG.Tweening; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NoStopMod.InputFixer.SyncFixer +{ + class SyncFixerPatchesScrController + { + + [HarmonyPatch(typeof(scrController), "OnMusicScheduled")] + private static class scrController_OnMusicScheduled_Patch + { + public static bool Prefix(scrController __instance) + { + if (GCS.checkpointNum != 0) + { + //__instance.conductor.hasSongStarted = true; + __instance.Scrub(GCS.checkpointNum, RDC.auto && __instance.isLevelEditor); + __instance.ChangeState(scrController.States.Checkpoint); + } + else if (!GCS.d_oldConductor) + { + __instance.conductor.song.time = 0f; + scrController.States states = (__instance.gameworld && !__instance.forceNoCountdown) ? scrController.States.Countdown : scrController.States.PlayerControl; + __instance.printe("changing to state: " + states); + __instance.ChangeState(states); + } + __instance.uiController.MinimizeDifficultyContainer(); + if (GCS.checkpointNum != 0) + { + scrDebugHUDMessage.Log("OnMusicStart"); + if (__instance.isLevelEditor) + { + scrUIController.instance.FadeFromBlack(1f); + } + } + if (!__instance.gameworld) + { + scrUIController.instance.FadeFromBlack(0.3f); + } + float duration = Mathf.Min(50f / (__instance.conductor.bpm * __instance.conductor.song.pitch), 0.5f); + DOTween.To(() => __instance.chosenplanet.cosmeticRadius, delegate (float x) + { + __instance.chosenplanet.cosmeticRadius = x; + }, __instance.startRadius, duration); + + return false; + } + } + + + [HarmonyPatch(typeof(scrController), "Scrub")] + private static class scrController_Scrub_Patch + { + public static bool Prefix(scrController __instance, int floorNum, bool forceDontStartMusicFourTilesBefore = false) + { + if (floorNum > scrLevelMaker.instance.listFloors.Count - 1 || floorNum < 0) + { + scrDebugHUDMessage.Log("Past the limit"); + return false; + } + int num = __instance.FindScrubStart(floorNum, forceDontStartMusicFourTilesBefore); + int windbackNum = (num == floorNum) ? -1 : num; + __instance.chosenplanet.ScrubToFloorNumber(floorNum, windbackNum, __instance.isLevelEditor || RDC.debug); + if (RDC.debug) + { + __instance.camy.ViewObjectInstant(__instance.chosenplanet.transform, false); + } + //base.conductor.ScrubMusicToTile(num); + if (__instance.isLevelEditor) + { + GameObject.Find("Vfx") + ?.GetComponent() + ?.ScrubToTime((float) __instance.lm.listFloors[num].entryTime); + } + return false; + } + } + + } +} diff --git a/InputFixer/SyncFixer/newScrConductor.cs b/InputFixer/SyncFixer/newScrConductor.cs new file mode 100644 index 0000000..9d4726f --- /dev/null +++ b/InputFixer/SyncFixer/newScrConductor.cs @@ -0,0 +1,610 @@ +using NoStopMod.Helper; +using RDTools; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + + + +namespace NoStopMod.InputFixer.SyncFixer +{ + + + class newScrConductor + { + // Note : this class replaces scrController in ADOFAI. + // to fix Desync bug, I replaced AudioSettings.dspTime to DateTime.Now.Ticks + // I hope this mechanism work well. + + /* + dspTime update + Start() + Rewind() + Update() + */ + + // new code + public long offsetTick; + + public long dspTick; + + public double dspTime; + + public static ReflectionField dspTimeField = new ReflectionField("dspTime", "_dspTime"); + + public void FixOffsetTick() + { + offsetTick = NoStopMod.CurrFrameTick() - (long)(this.dspTime * 10000000); +#if DEBUG + NoStopMod.mod.Logger.Log("FixOffsetTick"); +#endif + } + + public double getSongPosition(scrConductor __instance, long nowTick) + { + if (!GCS.d_oldConductor && !GCS.d_webglConductor) + { + return ((nowTick / 10000000.0 - this.dspTimeSong - scrConductor.calibration_i) * __instance.song.pitch) - __instance.addoffset; + } + else + { + return (__instance.song.time - scrConductor.calibration_i) - __instance.addoffset / __instance.song.pitch; + } + } + + // from scrController + public int FindSongStartTile(scrConductor conductor, int floorNum, bool forceDontStartMusicFourTilesBefore = false) + { + int result = floorNum; + if (GCS.usingCheckpoints && !forceDontStartMusicFourTilesBefore) + { + List floorList = conductor.lm.listFloors; + double startSpeed = conductor.crotchetAtStart / (double)floorList[floorNum].speed; + for (int i = floorNum - 1; i >= 1; i--) + { + if (floorList[i].entryTime <= floorList[floorNum].entryTime - (double)conductor.countdownTicks * startSpeed) + { + result = i; + break; + } + } + } + return result; + } + + + // Original Code + public List hitSoundsData = new List(); + + public int nextHitSoundToSchedule; + + public int crotchetsPerBar = 8; + + //public double _songposition_minusi; + + public double previousFrameTime; + + public double lastReportedPlayheadPosition; + + public double nextBeatTime; + + public double nextBarTime; + + public float buffer = 1f; + + public float[] _spectrum = new float[1024]; + + //public AudioSource audiosource; + + //public bool playedHitSounds; + + //public scrConductor.DuckState _duckState; + + //public float songPreduckVolume; + + //public float song2PreduckVolume; + + public double dspTimeSong; + + public List _onBeats = new List(); + + public static scrConductor instance + { + get + { + if (newScrConductor._instance == null) + { + newScrConductor._instance = UnityEngine.Object.FindObjectOfType(); + } + return newScrConductor._instance; + } + } + + private static scrConductor _instance; + + public Coroutine startMusicCoroutine; + + public struct HitSoundsData + { + public HitSoundsData(HitSound hitSound, double time, float volume) + { + this.hitSound = hitSound; + this.time = time; + this.volume = volume; + this.played = false; + } + + public HitSound hitSound; + public double time; + public float volume; + public bool played; + } + + public void Awake(scrConductor __instance) + { + ADOBase.Startup(); + if (GCS.d_webglConductor) + { + this.buffer = 0.5f; + } + if (Application.platform == RuntimePlatform.WindowsPlayer) + { + this.buffer = 0.5f; + } + if (Application.platform == RuntimePlatform.WindowsEditor) + { + this.buffer = 0.5f; + } + if (Application.platform == RuntimePlatform.OSXPlayer) + { + this.buffer = 0.5f; + } + if (Application.platform == RuntimePlatform.OSXEditor) + { + this.buffer = 0.5f; + } + if (__instance.uiController != null) + { + __instance.txtOffset = __instance.uiController.txtOffset; + } + if (scnEditor.instance == null) + { + scnEditor.instance = __instance.editorComponent; + } + if (scnEditor.instance == null) + { + scnCLS.instance = __instance.CLSComponent; + } + if (CustomLevel.instance == null) + { + CustomLevel.instance = __instance.customLevelComponent; + } + } + + public void Start(scrConductor __instance) + { + __instance.crotchet = (double)(60f / __instance.bpm); + __instance.crotchetAtStart = __instance.crotchet; + AudioSource[] components = __instance.GetComponents(); + __instance.song = components[0]; + if (components.Length > 1) + { + __instance.song2 = components[1]; + } + + this.nextBeatTime = 0.0; + this.nextBarTime = 0.0; + if (__instance.txtOffset != null) + { + __instance.txtOffset.text = ""; + } + this.lastReportedPlayheadPosition = AudioSettings.dspTime; + this.dspTime = AudioSettings.dspTime; +#if DEBUG + NoStopMod.mod.Logger.Log("CallFrom Start"); +#endif + FixOffsetTick(); + this.previousFrameTime = Time.unscaledTime; + if (__instance.song.pitch == 0f && !__instance.isLevelEditor) + { + Debug.LogError("Song pitch is zero set to zero?!"); + } + if (__instance.controller != null) + { + __instance.controller.startVolume = __instance.song.volume; + } + + } + + public void Rewind(scrConductor __instance) + { + __instance.tick = null; + __instance.tickclip = null; + __instance.isGameWorld = true; + __instance.rescrub = true; + __instance.crotchet = 0.0; + this.nextBeatTime = 0.0; + this.nextBarTime = 0.0; + __instance.beatNumber = 0; + __instance.barNumber = 0; + __instance.hasSongStarted = false; + __instance.getSpectrum = false; + __instance.txtStatus = null; + __instance.txtOffset = null; + this.dspTimeSong = 0.0; + __instance.lastHit = 0.0; + this.lastReportedPlayheadPosition = AudioSettings.dspTime; + this.dspTime = AudioSettings.dspTime; +#if DEBUG + NoStopMod.mod.Logger.Log("callFrom Rewind"); +#endif + FixOffsetTick(); + this.previousFrameTime = (double)Time.unscaledTime; + + } + + //public void SetupConductorWithLevelData(scrConductor __instance, LevelData levelData) + //{ + // __instance.bpm = levelData.bpm; + // __instance.crotchet = (double)(60f / __instance.bpm); + // __instance.crotchetAtStart = __instance.crotchet; + // __instance.addoffset = (double)((float)levelData.offset * 0.001f); + // __instance.song.volume = (float)levelData.volume * 0.01f; + // __instance.hitSoundVolume = (float)levelData.hitsoundVolume * 0.01f; + // __instance.hitSound = levelData.hitsound; + // __instance.separateCountdownTime = levelData.separateCountdownTime; + // float num = (float)levelData.pitch * 0.01f; + // if (GCS.standaloneLevelMode) + // { + // num *= GCS.currentSpeedRun; + // } + // __instance.song.pitch = num; + //} + + // StartMusicCo, DesyncFix + public void PlayHitTimes(scrConductor __instance, double hitsoundPlayFrom) + { + this.hitSoundsData = new List(); + //if (this.playedHitSounds) + //{ + // AudioManager.Instance.StopAllSounds(); + //} + //this.playedHitSounds = true; + if (ADOBase.sceneName.Contains("scnCalibration") || + __instance.lm == null || + !GCS.d_hitsounds || + (__instance.controller != null && !__instance.controller.isLevelEditor && !__instance.forceHitSounds)) + { + return; + } + HitSound hitSound = __instance.hitSound; + float volume = __instance.hitSoundVolume; + List floorList = __instance.lm.listFloors; + int num = (GCS.checkpointNum < floorList.Count && GCS.usingCheckpoints) ? (GCS.checkpointNum + 1) : 1; + + this.nextHitSoundToSchedule = 0; + for (int i = 1; i < floorList.Count; i++) + { + scrFloor scrFloor = floorList[i]; + ffxSetHitsound setHitsound = scrFloor.GetComponent(); + if (setHitsound != null) + { + hitSound = setHitsound.hitSound; + volume = setHitsound.volume; + } + double hitsoundOffset = (hitSound == HitSound.Shaker || hitSound == HitSound.ShakerLoud) ? 0.015 : 0.0; + double time = hitsoundPlayFrom + scrFloor.entryTimePitchAdj - hitsoundOffset; + if (i >= num && time > this.dspTime && !scrFloor.midSpin && hitSound != HitSound.None) + { + HitSoundsData item = new HitSoundsData(hitSound, time, volume); + this.hitSoundsData.Add(item); + } + } + + if (__instance.playCountdownHihats) + { + if (!__instance.fastTakeoff) + { + __instance.countdownTimes = new double[__instance.countdownTicks]; + for (int j = 0; j < __instance.countdownTicks; j++) + { + double countdownTime = this.GetCountdownTime(__instance, j); + if (countdownTime > this.dspTime) + { + __instance.countdownTimes[j] = countdownTime; + AudioManager.Play("sndHat", countdownTime, __instance.hitSoundVolume, 10); + } + } + } + if (__instance.playEndingCymbal) + { + AudioManager.Play("sndCymbalCrash", this.dspTimeSong + __instance.lm.listFloors[__instance.lm.listFloors.Count - 1].entryTimePitchAdj + + __instance.addoffset / __instance.song.pitch, __instance.hitSoundVolume, 128); + } + } + } + + public double GetCountdownTime(scrConductor __instance, int i) + { + if (GCS.checkpointNum != 0 && GCS.usingCheckpoints) + { + int index = (GCS.checkpointNum != __instance.lm.listFloors.Count - 1) ? (GCS.checkpointNum + 1) : GCS.checkpointNum; + return this.dspTimeSong + __instance.lm.listFloors[index].entryTimePitchAdj - (double)(__instance.countdownTicks - i) * (__instance.crotchet / (double)__instance.lm.listFloors[GCS.checkpointNum].speed) / (double)__instance.song.pitch + __instance.addoffset / (double)__instance.song.pitch; + } + return this.dspTimeSong + (double)i * __instance.crotchet / (double)__instance.song.pitch + __instance.addoffset / (double)__instance.song.pitch; + } + + public void StartMusic(scrConductor __instance, Action onComplete = null, Action onSongScheduled = null) + { + if (this.startMusicCoroutine != null) + { + __instance.StopCoroutine(this.startMusicCoroutine); + } + this.startMusicCoroutine = __instance.StartCoroutine(this.StartMusicCo(__instance, onComplete, onSongScheduled)); + } + + public IEnumerator StartMusicCo(scrConductor __instance, Action onComplete, Action onSongScheduled = null) + { + this.dspTime = AudioSettings.dspTime; +#if DEBUG + NoStopMod.mod.Logger.Log("call From StartMusicCo First"); +#endif + FixOffsetTick(); + this.dspTimeSong = this.dspTime + (double)this.buffer + 0.1f; + + for (float timer = 0.1f; timer >= 0f; timer -= Time.deltaTime) + { + yield return null; + } + +#if DEBUG + NoStopMod.mod.Logger.Log("call From StartMusicCo Second"); +#endif + this.dspTime = AudioSettings.dspTime; + FixOffsetTick(); + + AudioListener.pause = true; + + double countdownTime = __instance.crotchetAtStart * __instance.countdownTicks; + double separatedCountdownTime = __instance.separateCountdownTime ? countdownTime : 0.0; + + + + if (GCS.checkpointNum == 0) + { + this.dspTimeSong = this.dspTime + this.buffer; + if (__instance.fastTakeoff) + { + this.dspTimeSong -= countdownTime / __instance.song.pitch; + } + + double time = this.dspTimeSong + separatedCountdownTime / __instance.song.pitch; + + __instance.song.PlayScheduled(time); + __instance.song2?.PlayScheduled(time); + } + else + { + __instance.song.PlayScheduled(this.dspTime); + + double entryTime = __instance.lm.listFloors[FindSongStartTile(__instance, GCS.checkpointNum, RDC.auto && __instance.isLevelEditor)].entryTime; + + __instance.lastHit = entryTime; + __instance.song.time = (float)(entryTime + __instance.addoffset - separatedCountdownTime); + this.dspTimeSong = this.dspTime - (entryTime + __instance.addoffset) / __instance.song.pitch; + } + onSongScheduled?.Invoke(); + + double hitSoundPlayFrom = this.dspTimeSong + __instance.addoffset / __instance.song.pitch; + this.PlayHitTimes(__instance, hitSoundPlayFrom); + + __instance.hasSongStarted = true; + + yield return null; + AudioListener.pause = false; +#if DEBUG + NoStopMod.mod.Logger.Log("call From StartMusicCo Third"); +#endif + FixOffsetTick(); + + yield return new WaitForSeconds(4f); + while (__instance.song.isPlaying) + { + yield return null; + } + onComplete?.Invoke(); + + yield break; + } + + public IEnumerator ToggleHasSongStarted(scrConductor __instance, double songstarttime) + { + yield break; + } + + public void Update(scrConductor __instance) + { + RDInput.Update(); + + if (AudioListener.pause) + { + offsetTick += NoStopMod.CurrFrameTick() - NoStopMod.PrevFrameTick(); + } + else + { + this.dspTime += Time.unscaledTime - this.previousFrameTime; + dspTick = NoStopMod.CurrFrameTick() - offsetTick; +#if DEBUG + NoStopMod.mod.Logger.Log("dspTime : " + __instance.dspTime + ", " + (dspTick / 10000000.0) + "diff(" + (__instance.dspTime - (dspTick / 10000000.0)) + ")"); +#endif + } + + this.previousFrameTime = Time.unscaledTime; + if (AudioSettings.dspTime != this.lastReportedPlayheadPosition) + { + this.dspTime = AudioSettings.dspTime; + this.lastReportedPlayheadPosition = AudioSettings.dspTime; + FixOffsetTick(); + } + + if (__instance.hasSongStarted && __instance.isGameWorld && (scrController.States)__instance.controller.GetState() != scrController.States.Fail && (scrController.States)__instance.controller.GetState() != scrController.States.Fail2) + { + while (this.nextHitSoundToSchedule < this.hitSoundsData.Count) + { + HitSoundsData hitSoundsData = this.hitSoundsData[this.nextHitSoundToSchedule]; + if (this.dspTime + 5.0 <= hitSoundsData.time) + { + break; + } + AudioManager.Play("snd" + hitSoundsData.hitSound, hitSoundsData.time, hitSoundsData.volume, 128); + this.nextHitSoundToSchedule++; + } + } + __instance.crotchet = (double)(60f / __instance.bpm); + double prevSongposition_minusi = __instance.songposition_minusi; + __instance.songposition_minusi = getSongPosition(__instance, dspTick); + + __instance.deltaSongPos = __instance.songposition_minusi - prevSongposition_minusi; + __instance.deltaSongPos = Math.Max(__instance.deltaSongPos, 0.0); + if (__instance.songposition_minusi > this.nextBeatTime) + { + __instance.OnBeat(); + this.nextBeatTime += __instance.crotchet; + __instance.beatNumber++; + } + if (__instance.songposition_minusi < this.nextBeatTime - __instance.crotchet) + { + this.nextBeatTime -= __instance.crotchet; + __instance.beatNumber--; + } + if (__instance.songposition_minusi > this.nextBarTime) + { + this.nextBarTime += __instance.crotchet * (double)this.crotchetsPerBar; + __instance.barNumber++; + } + + if (Input.GetKeyDown(KeyCode.G) && Application.isEditor) + { + float time = __instance.song.time; + if (__instance.separateCountdownTime) + { + double num = __instance.crotchetAtStart; + int num2 = __instance.countdownTicks; + } + double songposition_minusi2 = __instance.songposition_minusi; + float calibration_i = scrConductor.calibration_i; + float pitch = __instance.song.pitch; + double num3 = __instance.addoffset; + } + + if (__instance.getSpectrum && !GCS.lofiVersion) + { + AudioSource audioSource = __instance.song; + if (__instance.CLSComponent != null) + { + PreviewSongPlayer previewSongPlayer = __instance.CLSComponent.previewSongPlayer; + if (previewSongPlayer.playing) + { + audioSource = previewSongPlayer.audioSource; + } + } + audioSource.GetSpectrumData(__instance.spectrum, 0, FFTWindow.BlackmanHarris); + } + } + + //public void OnBeat(scrConductor __instance) + //{ + // List onBeats = __instance.onBeats; + // if (onBeats == null) + // { + // return; + // } + // int count = onBeats.Count; + // for (int i = 0; i < count; i++) + // { + // onBeats[i].OnBeat(); + // } + // if (__instance.controller != null && __instance.controller.gameworld) + // { + // List listFloors = __instance.controller.lm.listFloors; + // int count2 = listFloors.Count; + // for (int j = 0; j < count2; j++) + // { + // listFloors[j].OnBeat(); + // } + // } + //} + + public void PlaySfx(scrConductor __instance, int num, float volume = 1f, bool ignoreListenerPause = false) + { + if (num == 2) + { + volume = 1.5f; + } + scrSfx.instance.Play(num, ignoreListenerPause, volume); + } + + public void ScrubMusicToTile(scrConductor __instance, int tileID) + { + } + + public IEnumerator DesyncFix(scrConductor __instance) + { + yield break; + } + + private int GetOffsetChange(bool fine) + { + return fine ? 1 : 10; + } + + public static void SaveCurrentPreset() + { + for (int i = 0; i < scrConductor.userPresets.Count; i++) + { + CalibrationPreset calibrationPreset = scrConductor.userPresets[i]; + if (scrConductor.currentPreset.outputType == calibrationPreset.outputType && scrConductor.currentPreset.outputName == calibrationPreset.outputName) + { + RDBaseDll.printem("found preset, modifying."); + calibrationPreset.inputOffset = scrConductor.currentPreset.inputOffset; + scrConductor.userPresets[i] = calibrationPreset; + return; + } + } + RDBaseDll.printem("adding preset: " + scrConductor.currentPreset); + scrConductor.userPresets.Add(scrConductor.currentPreset); + } + + + public void SaveVisualOffset(double offset) + { + Persistence.SetVisualOffset((float)offset); + PlayerPrefs.SetFloat("offset_v", (float)offset); + PlayerPrefs.Save(); + } + + //public void LoadOnBeats() + //{ + // if (this._onBeats.Count != 0) + // { + // return; + // } + // this._onBeats = new List(); + // GameObject[] array = GameObject.FindGameObjectsWithTag("Beat"); + // for (int i = 0; i < array.Length; i++) + // { + // ADOBase component = array[i].GetComponent(); + // if (component != null && component) + // { + // this._onBeats.Add(component); + // } + // } + //} + + + + + } +} + diff --git a/NoStopMod.cs b/NoStopMod.cs index bdbebe0..2f37907 100644 --- a/NoStopMod.cs +++ b/NoStopMod.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.IO; -using System.Reflection; +using System.Reflection; using HarmonyLib; -using UnityEngine; using UnityModManagerNet; -using UnityEngine.Scripting; +using NoStopMod.InputFixer; +using NoStopMod.HyperRabbit; +using NoStopMod.Helper; +using System; namespace NoStopMod { @@ -16,85 +13,81 @@ class NoStopMod public static UnityModManager.ModEntry mod; public static Harmony harmony; + public static bool isEnabled = false; - public static bool currentEnabled = false; - public static bool ready = false; - public static bool gcEnabled = true; + public static EventListener onToggleListener = new EventListener("OnToggle"); + public static EventListener onGUIListener = new EventListener("OnGUI"); + public static EventListener onHideGUIListener = new EventListener("OnHideGUI"); + public static EventListener onApplicationQuitListener = new EventListener("OnApplicationQuit"); + + private static long currFrameTick; + private static long prevFrameTick; public static bool Load(UnityModManager.ModEntry modEntry) { - modEntry.OnToggle = new Func(NoStopMod.OnToggle); + modEntry.OnToggle = NoStopMod.OnToggle; + modEntry.OnGUI = onGUIListener.Invoke; + modEntry.OnHideGUI = onHideGUIListener.Invoke; + + NoStopMod.harmony = new Harmony(modEntry.Info.Id); NoStopMod.mod = modEntry; + + NoStopMod.prevFrameTick = DateTime.Now.Ticks; + NoStopMod.currFrameTick = prevFrameTick; + + InputFixerManager.Init(); + HyperRabbitManager.Init(); + + Settings.Init(); return true; } public static bool OnToggle(UnityModManager.ModEntry modEntry, bool enabled) { NoStopMod.mod = modEntry; - - if (NoStopMod.currentEnabled != enabled) + + if (enabled) { - if (enabled) - { - NoStopMod.ready = true; - NoStopMod.harmony = new Harmony(modEntry.Info.Id); - NoStopMod.harmony.PatchAll(Assembly.GetExecutingAssembly()); - - } - else - { - NoStopMod.ready = false; - NoStopMod.harmony.UnpatchAll(NoStopMod.harmony.Id); - - } - NoStopMod.currentEnabled = enabled; + NoStopMod.harmony.PatchAll(Assembly.GetExecutingAssembly()); } - NoStopMod.gcEnabled = true; + else + { + NoStopMod.harmony.UnpatchAll(NoStopMod.harmony.Id); + } + + isEnabled = enabled; + onToggleListener.Invoke(enabled); return true; } - public static bool GetDisableAutoSave() + public static long CurrFrameTick() { - return NoStopMod.ready && !NoStopMod.gcEnabled; + return NoStopMod.currFrameTick; } - public static void DisableGC() + public static long PrevFrameTick() { - if (NoStopMod.ready) + return NoStopMod.prevFrameTick; + } + + [HarmonyPatch(typeof(scrConductor), "Update")] + private static class scrConductor_Update_Patch_Time + { + public static void Prefix(scrConductor __instance) { - try - { - gcEnabled = false; - //NoStopMod.mod.Logger.Log("disablegc"); - GarbageCollector.GCMode = GarbageCollector.Mode.Disabled; - System.Runtime.GCSettings.LatencyMode = System.Runtime.GCLatencyMode.SustainedLowLatency; - } - catch (NotImplementedException e) - { - NoStopMod.mod.Logger.Log("Exception occur"); - NoStopMod.mod.Logger.Error(e.ToString()); - } + NoStopMod.prevFrameTick = NoStopMod.currFrameTick; + NoStopMod.currFrameTick = DateTime.Now.Ticks; } } - public static void EnableGC() + [HarmonyPatch(typeof(scrController), "OnApplicationQuit")] + private static class scrController_OnApplicationQuit_Patch { - if (NoStopMod.ready) + public static void Prefix(scrController __instance) { - try - { - gcEnabled = true; - //NoStopMod.mod.Logger.Log("enablegc"); - GarbageCollector.GCMode = GarbageCollector.Mode.Enabled; - System.Runtime.GCSettings.LatencyMode = System.Runtime.GCLatencyMode.Interactive; - GC.Collect(); - } catch (NotImplementedException e) - { - NoStopMod.mod.Logger.Log("Exception occur"); - NoStopMod.mod.Logger.Error(e.ToString()); - } + onApplicationQuitListener.Invoke(__instance); } } - + } } diff --git a/NoStopMod.csproj b/NoStopMod.csproj index 6fc2a83..a38a20e 100644 --- a/NoStopMod.csproj +++ b/NoStopMod.csproj @@ -35,43 +35,347 @@ false - + + False ..\lib\0Harmony.dll - - ..\lib\Assembly-CSharp.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Assembly-CSharp.dll - - ..\lib\Assembly-CSharp-firstpass.dll + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Assembly-CSharp-firstpass.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\DemiLib.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\DOTween.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\DOTweenPro.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\I18N.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\I18N.CJK.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\I18N.MidEast.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\I18N.Other.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\I18N.Rare.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\I18N.West.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Ionic.Zip.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Mono.Data.Sqlite.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Mono.Posix.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Mono.Security.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Mono.WebBrowser.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Novell.Directory.Ldap.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Ookii.Dialogs.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\RDTools.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\sharedRuntime.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Sirenix.OdinInspector.Attributes.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Sirenix.OdinInspector.CompatibilityLayer.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Sirenix.Serialization.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Sirenix.Serialization.Config.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Sirenix.Utilities.dll + + + + + + + + + + + + + + + + + - - - - ..\lib\UnityEngine.dll + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Unity.MemoryProfiler.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Unity.TextMeshPro.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\Unity.Timeline.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.AccessibilityModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.AIModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.AndroidJNIModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.AnimationModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ARModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.AssetBundleModule.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.AudioModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ClothModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ClusterInputModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ClusterRendererModule.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.CoreModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.CrashReportingModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.DirectorModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.DSPGraphModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.GameCenterModule.dll - - ..\lib\UnityEngine.CoreModule.dll + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.GridModule.dll - - ..\lib\UnityEngine.IMGUIModule.dll + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.HotReloadModule.dll - - ..\lib\UnityEngine.UI.dll + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ImageConversionModule.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.IMGUIModule.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.InputLegacyModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.InputModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.JSONSerializeModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.LocalizationModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ParticleSystemModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.PerformanceReportingModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.Physics2DModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.PhysicsModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ProfilerModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.ScreenCaptureModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.SharedInternalsModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.SpriteMaskModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.SpriteShapeModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.StreamingModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.SubstanceModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.SubsystemsModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.TerrainModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.TerrainPhysicsModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.TextCoreModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.TextRenderingModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.TilemapModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.TLSModule.dll + + + False + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UI.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UIElementsModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UIModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UmbraModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UNETModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityAnalyticsModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityConnectModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityTestProtocolModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityWebRequestAudioModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityWebRequestModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityWebRequestTextureModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.UnityWebRequestWWWModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.VehiclesModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.VFXModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.VideoModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.VRModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.WindModule.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\UnityEngine.XRModule.dll ..\lib\UnityModManager.dll + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\urecordRuntime.dll + + + ..\..\..\..\Desktop\ADOFAI R75 20210619\dll\XTUtilities.dll + + + + + + + + + + + + + + + + + + + + - + + + diff --git a/Patches/Patches.cs b/Patches/Patches.cs deleted file mode 100644 index 3b7a0ba..0000000 --- a/Patches/Patches.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using HarmonyLib; -using UnityEngine; - -namespace NoStopMod.Patches -{ - internal static class Patches - { - [HarmonyPatch(typeof(CustomLevel), "Play")] - public static class CustomLevel_Play_Patch - { - private static void Prefix(CustomLevel __instance) - { - //NoStopMod.mod.Logger.Log("Play"); - NoStopMod.DisableGC(); - } - } - - [HarmonyPatch(typeof(scnEditor), "ResetScene")] - public static class scnEditor_ResetScene_Patch - { - private static void Postfix(scnEditor __instance) - { - //NoStopMod.mod.Logger.Log("ResetScene"); - NoStopMod.EnableGC(); - } - } - - [HarmonyPatch(typeof(scnEditor), "SaveBackup")] - public static class scnEditor_SaveBackup_Patch - { - private static bool Prefix(scnEditor __instance) - { - //NoStopMod.mod.Logger.Log("SaveBackup"); - if (NoStopMod.GetDisableAutoSave()) - { - //NoStopMod.mod.Logger.Log("Cancel AutoSave"); - return false; - } - - return true; - } - } - - [HarmonyPatch(typeof(scrController), "Awake")] - public static class scrController_Awake_Patch - { - public static void Postfix(scrController __instance) - { - //NoStopMod.mod.Logger.Log("Awake"); - NoStopMod.EnableGC(); - } - } - - [HarmonyPatch(typeof(scrController), "FailAction")] - public static class scrController_FailAction_Patch - { - private static void Prefix(scrController __instance) - { - NoStopMod.EnableGC(); - } - } - - [HarmonyPatch(typeof(scrController), "QuitToMainMenu")] - internal static class scrController_QuitToMainMenu_Patch - { - private static void Postfix(scrController __instance) - { - //NoStopMod.mod.Logger.Log("StartLoadingScene"); - NoStopMod.EnableGC(); - } - } - - [HarmonyPatch(typeof(scrController), "ResetCustomLevel")] - internal static class scrController_ResetCustomLevel_Patch - { - private static void Postfix(scrController __instance) - { - //NoStopMod.mod.Logger.Log("ResetCustomLevel"); - NoStopMod.EnableGC(); - } - } - - [HarmonyPatch(typeof(scrController), "Restart")] - public static class scrController_Restart_Patch - { - private static void Prefix(scrController __instance) - { - //NoStopMod.mod.Logger.Log("Restart"); - NoStopMod.EnableGC(); - } - } - - [HarmonyPatch(typeof(scrController), "StartLoadingScene")] - internal static class scrController_StartLoadingScene_Patch - { - private static void Postfix(scrController __instance) - { - //NoStopMod.mod.Logger.Log("StartLoadingScene"); - NoStopMod.EnableGC(); - } - } - - - - - [HarmonyPatch(typeof(scrUIController), "WipeToBlack")] - public static class scrUIController_WipeToBlack_Patch - { - private static void Postfix(scrUIController __instance) - { - //NoStopMod.mod.Logger.Log("WipeToBlack"); - NoStopMod.EnableGC(); - } - } - - } - - -} diff --git a/Repository.json b/Repository.json new file mode 100644 index 0000000..3748cac --- /dev/null +++ b/Repository.json @@ -0,0 +1,9 @@ +{ + "Releases": [ + { + "Id": "NoStopMod", + "Version": "1.2.2", + "DownloadUrl": "https://github.com/Luxusio/ADOFAI-NoStopMod/releases/download/1.2.2/NoStopMod-1.2.2.zip" + } + ] +} \ No newline at end of file diff --git a/Settings.cs b/Settings.cs new file mode 100644 index 0000000..0c52bee --- /dev/null +++ b/Settings.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NoStopMod.Helper; +using NoStopMod.Helper.Abstraction; +using SimpleJSON; + +namespace NoStopMod +{ + class Settings + { + + public const String path = "\\Mods\\NoStopMod\\settings.json"; + + public static List settings = new List(); + public static EventListener settingsLoadListener = new EventListener("SettingsLoad"); + + public static void Init() + { + NoStopMod.onToggleListener.Add(_ => Load()); + NoStopMod.onApplicationQuitListener.Add(_ => Save()); + } + + public static void Load() + { + try + { + string text = File.ReadAllText(Environment.CurrentDirectory + path); + + JSONNode jsonNode = JSON.Parse(text); + for (int i = 0; i < settings.Count(); i++) + { + try + { + settings[i].Load(ref jsonNode); + } + catch (Exception e) + { + NoStopMod.mod.Logger.Error("While Loading : " + e.Message + ", " + e.StackTrace); + } + } + settingsLoadListener.Invoke(true); + } + catch + { + Save(); + settingsLoadListener.Invoke(false); + } + } + + public static void Save() + { + JSONNode node = JSONHelper.CreateEmptyNode(); + for (int i = 0; i < settings.Count();i++) + { + try + { + settings[i].Save(ref node); + } + catch (Exception e) + { + NoStopMod.mod.Logger.Error("While Saving : " + e.Message + ", " + e.StackTrace); + } + } + + File.WriteAllText(Environment.CurrentDirectory + path, node.ToString()); + } + + } +} diff --git a/lib/SimpleJSON.cs b/lib/SimpleJSON.cs new file mode 100644 index 0000000..4777724 --- /dev/null +++ b/lib/SimpleJSON.cs @@ -0,0 +1,1434 @@ +/* * * * * + * A simple JSON Parser / builder + * ------------------------------ + * + * It mainly has been written as a simple JSON parser. It can build a JSON string + * from the node-tree, or generate a node tree from any valid JSON string. + * + * Written by Bunny83 + * 2012-06-09 + * + * Changelog now external. See Changelog.txt + * + * The MIT License (MIT) + * + * Copyright (c) 2012-2019 Markus Göbel (Bunny83) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * * * * */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace SimpleJSON +{ + public enum JSONNodeType + { + Array = 1, + Object = 2, + String = 3, + Number = 4, + NullValue = 5, + Boolean = 6, + None = 7, + Custom = 0xFF, + } + public enum JSONTextMode + { + Compact, + Indent + } + + public abstract partial class JSONNode + { + #region Enumerators + public struct Enumerator + { + private enum Type { None, Array, Object } + private Type type; + private Dictionary.Enumerator m_Object; + private List.Enumerator m_Array; + public bool IsValid { get { return type != Type.None; } } + public Enumerator(List.Enumerator aArrayEnum) + { + type = Type.Array; + m_Object = default(Dictionary.Enumerator); + m_Array = aArrayEnum; + } + public Enumerator(Dictionary.Enumerator aDictEnum) + { + type = Type.Object; + m_Object = aDictEnum; + m_Array = default(List.Enumerator); + } + public KeyValuePair Current + { + get + { + if (type == Type.Array) + return new KeyValuePair(string.Empty, m_Array.Current); + else if (type == Type.Object) + return m_Object.Current; + return new KeyValuePair(string.Empty, null); + } + } + public bool MoveNext() + { + if (type == Type.Array) + return m_Array.MoveNext(); + else if (type == Type.Object) + return m_Object.MoveNext(); + return false; + } + } + public struct ValueEnumerator + { + private Enumerator m_Enumerator; + public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } + public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } + public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } + public JSONNode Current { get { return m_Enumerator.Current.Value; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + public ValueEnumerator GetEnumerator() { return this; } + } + public struct KeyEnumerator + { + private Enumerator m_Enumerator; + public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } + public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } + public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } + public string Current { get { return m_Enumerator.Current.Key; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + public KeyEnumerator GetEnumerator() { return this; } + } + + public class LinqEnumerator : IEnumerator>, IEnumerable> + { + private JSONNode m_Node; + private Enumerator m_Enumerator; + internal LinqEnumerator(JSONNode aNode) + { + m_Node = aNode; + if (m_Node != null) + m_Enumerator = m_Node.GetEnumerator(); + } + public KeyValuePair Current { get { return m_Enumerator.Current; } } + object IEnumerator.Current { get { return m_Enumerator.Current; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + + public void Dispose() + { + m_Node = null; + m_Enumerator = new Enumerator(); + } + + public IEnumerator> GetEnumerator() + { + return new LinqEnumerator(m_Node); + } + + public void Reset() + { + if (m_Node != null) + m_Enumerator = m_Node.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new LinqEnumerator(m_Node); + } + } + + #endregion Enumerators + + #region common interface + + public static bool forceASCII = false; // Use Unicode by default + public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber + public static bool allowLineComments = true; // allow "//"-style comments at the end of a line + + public abstract JSONNodeType Tag { get; } + + public virtual JSONNode this[int aIndex] { get { return null; } set { } } + + public virtual JSONNode this[string aKey] { get { return null; } set { } } + + public virtual string Value { get { return ""; } set { } } + + public virtual int Count { get { return 0; } } + + public virtual bool IsNumber { get { return false; } } + public virtual bool IsString { get { return false; } } + public virtual bool IsBoolean { get { return false; } } + public virtual bool IsNull { get { return false; } } + public virtual bool IsArray { get { return false; } } + public virtual bool IsObject { get { return false; } } + + public virtual bool Inline { get { return false; } set { } } + + public virtual void Add(string aKey, JSONNode aItem) + { + } + public virtual void Add(JSONNode aItem) + { + Add("", aItem); + } + + public virtual JSONNode Remove(string aKey) + { + return null; + } + + public virtual JSONNode Remove(int aIndex) + { + return null; + } + + public virtual JSONNode Remove(JSONNode aNode) + { + return aNode; + } + public virtual void Clear() { } + + public virtual JSONNode Clone() + { + return null; + } + + public virtual IEnumerable Children + { + get + { + yield break; + } + } + + public IEnumerable DeepChildren + { + get + { + foreach (var C in Children) + foreach (var D in C.DeepChildren) + yield return D; + } + } + + public virtual bool HasKey(string aKey) + { + return false; + } + + public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) + { + return aDefault; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact); + return sb.ToString(); + } + + public virtual string ToString(int aIndent) + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent); + return sb.ToString(); + } + internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode); + + public abstract Enumerator GetEnumerator(); + public IEnumerable> Linq { get { return new LinqEnumerator(this); } } + public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } } + public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } } + + #endregion common interface + + #region typecasting properties + + + public virtual double AsDouble + { + get + { + double v = 0.0; + if (double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) + return v; + return 0.0; + } + set + { + Value = value.ToString(CultureInfo.InvariantCulture); + } + } + + public virtual int AsInt + { + get { return (int)AsDouble; } + set { AsDouble = value; } + } + + public virtual float AsFloat + { + get { return (float)AsDouble; } + set { AsDouble = value; } + } + + public virtual bool AsBool + { + get + { + bool v = false; + if (bool.TryParse(Value, out v)) + return v; + return !string.IsNullOrEmpty(Value); + } + set + { + Value = (value) ? "true" : "false"; + } + } + + public virtual long AsLong + { + get + { + long val = 0; + if (long.TryParse(Value, out val)) + return val; + return 0L; + } + set + { + Value = value.ToString(); + } + } + + public virtual ulong AsULong + { + get + { + ulong val = 0; + if (ulong.TryParse(Value, out val)) + return val; + return 0; + } + set + { + Value = value.ToString(); + } + } + + public virtual JSONArray AsArray + { + get + { + return this as JSONArray; + } + } + + public virtual JSONObject AsObject + { + get + { + return this as JSONObject; + } + } + + + #endregion typecasting properties + + #region operators + + public static implicit operator JSONNode(string s) + { + return (s == null) ? (JSONNode) JSONNull.CreateOrGet() : new JSONString(s); + } + public static implicit operator string(JSONNode d) + { + return (d == null) ? null : d.Value; + } + + public static implicit operator JSONNode(double n) + { + return new JSONNumber(n); + } + public static implicit operator double(JSONNode d) + { + return (d == null) ? 0 : d.AsDouble; + } + + public static implicit operator JSONNode(float n) + { + return new JSONNumber(n); + } + public static implicit operator float(JSONNode d) + { + return (d == null) ? 0 : d.AsFloat; + } + + public static implicit operator JSONNode(int n) + { + return new JSONNumber(n); + } + public static implicit operator int(JSONNode d) + { + return (d == null) ? 0 : d.AsInt; + } + + public static implicit operator JSONNode(long n) + { + if (longAsString) + return new JSONString(n.ToString()); + return new JSONNumber(n); + } + public static implicit operator long(JSONNode d) + { + return (d == null) ? 0L : d.AsLong; + } + + public static implicit operator JSONNode(ulong n) + { + if (longAsString) + return new JSONString(n.ToString()); + return new JSONNumber(n); + } + public static implicit operator ulong(JSONNode d) + { + return (d == null) ? 0 : d.AsULong; + } + + public static implicit operator JSONNode(bool b) + { + return new JSONBool(b); + } + public static implicit operator bool(JSONNode d) + { + return (d == null) ? false : d.AsBool; + } + + public static implicit operator JSONNode(KeyValuePair aKeyValue) + { + return aKeyValue.Value; + } + + public static bool operator ==(JSONNode a, object b) + { + if (ReferenceEquals(a, b)) + return true; + bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator; + bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator; + if (aIsNull && bIsNull) + return true; + return !aIsNull && a.Equals(b); + } + + public static bool operator !=(JSONNode a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion operators + + [ThreadStatic] + private static StringBuilder m_EscapeBuilder; + internal static StringBuilder EscapeBuilder + { + get + { + if (m_EscapeBuilder == null) + m_EscapeBuilder = new StringBuilder(); + return m_EscapeBuilder; + } + } + internal static string Escape(string aText) + { + var sb = EscapeBuilder; + sb.Length = 0; + if (sb.Capacity < aText.Length + aText.Length / 10) + sb.Capacity = aText.Length + aText.Length / 10; + foreach (char c in aText) + { + switch (c) + { + case '\\': + sb.Append("\\\\"); + break; + case '\"': + sb.Append("\\\""); + break; + case '\n': + sb.Append("\\n"); + break; + case '\r': + sb.Append("\\r"); + break; + case '\t': + sb.Append("\\t"); + break; + case '\b': + sb.Append("\\b"); + break; + case '\f': + sb.Append("\\f"); + break; + default: + if (c < ' ' || (forceASCII && c > 127)) + { + ushort val = c; + sb.Append("\\u").Append(val.ToString("X4")); + } + else + sb.Append(c); + break; + } + } + string result = sb.ToString(); + sb.Length = 0; + return result; + } + + private static JSONNode ParseElement(string token, bool quoted) + { + if (quoted) + return token; + if (token.Length <= 5) + { + string tmp = token.ToLower(); + if (tmp == "false" || tmp == "true") + return tmp == "true"; + if (tmp == "null") + return JSONNull.CreateOrGet(); + } + double val; + if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val)) + return val; + else + return token; + } + + public static JSONNode Parse(string aJSON) + { + Stack stack = new Stack(); + JSONNode ctx = null; + int i = 0; + StringBuilder Token = new StringBuilder(); + string TokenName = ""; + bool QuoteMode = false; + bool TokenIsQuoted = false; + bool HasNewlineChar = false; + while (i < aJSON.Length) + { + switch (aJSON[i]) + { + case '{': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + stack.Push(new JSONObject()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + HasNewlineChar = false; + break; + + case '[': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + stack.Push(new JSONArray()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + HasNewlineChar = false; + break; + + case '}': + case ']': + if (QuoteMode) + { + + Token.Append(aJSON[i]); + break; + } + if (stack.Count == 0) + throw new Exception("JSON Parse: Too many closing brackets"); + + stack.Pop(); + if (Token.Length > 0 || TokenIsQuoted) + ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); + if (ctx != null) + ctx.Inline = !HasNewlineChar; + TokenIsQuoted = false; + TokenName = ""; + Token.Length = 0; + if (stack.Count > 0) + ctx = stack.Peek(); + break; + + case ':': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + TokenName = Token.ToString(); + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '"': + QuoteMode ^= true; + TokenIsQuoted |= QuoteMode; + break; + + case ',': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + if (Token.Length > 0 || TokenIsQuoted) + ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); + TokenIsQuoted = false; + TokenName = ""; + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '\r': + case '\n': + HasNewlineChar = true; + break; + + case ' ': + case '\t': + if (QuoteMode) + Token.Append(aJSON[i]); + break; + + case '\\': + ++i; + if (QuoteMode) + { + char C = aJSON[i]; + switch (C) + { + case 't': + Token.Append('\t'); + break; + case 'r': + Token.Append('\r'); + break; + case 'n': + Token.Append('\n'); + break; + case 'b': + Token.Append('\b'); + break; + case 'f': + Token.Append('\f'); + break; + case 'u': + { + string s = aJSON.Substring(i + 1, 4); + Token.Append((char)int.Parse( + s, + System.Globalization.NumberStyles.AllowHexSpecifier)); + i += 4; + break; + } + default: + Token.Append(C); + break; + } + } + break; + case '/': + if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i + 1] == '/') + { + while (++i < aJSON.Length && aJSON[i] != '\n' && aJSON[i] != '\r') ; + break; + } + Token.Append(aJSON[i]); + break; + case '\uFEFF': // remove / ignore BOM (Byte Order Mark) + break; + + default: + Token.Append(aJSON[i]); + break; + } + ++i; + } + if (QuoteMode) + { + throw new Exception("JSON Parse: Quotation marks seems to be messed up."); + } + if (ctx == null) + return ParseElement(Token.ToString(), TokenIsQuoted); + return ctx; + } + + } + // End of JSONNode + + public partial class JSONArray : JSONNode + { + private List m_List = new List(); + private bool inline = false; + public override bool Inline + { + get { return inline; } + set { inline = value; } + } + + public override JSONNodeType Tag { get { return JSONNodeType.Array; } } + public override bool IsArray { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_List.Count) + return new JSONLazyCreator(this); + return m_List[aIndex]; + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (aIndex < 0 || aIndex >= m_List.Count) + m_List.Add(value); + else + m_List[aIndex] = value; + } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this); } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + m_List.Add(value); + } + } + + public override int Count + { + get { return m_List.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = JSONNull.CreateOrGet(); + m_List.Add(aItem); + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_List.Count) + return null; + JSONNode tmp = m_List[aIndex]; + m_List.RemoveAt(aIndex); + return tmp; + } + + public override JSONNode Remove(JSONNode aNode) + { + m_List.Remove(aNode); + return aNode; + } + + public override void Clear() + { + m_List.Clear(); + } + + public override JSONNode Clone() + { + var node = new JSONArray(); + node.m_List.Capacity = m_List.Capacity; + foreach(var n in m_List) + { + if (n != null) + node.Add(n.Clone()); + else + node.Add(null); + } + return node; + } + + public override IEnumerable Children + { + get + { + foreach (JSONNode N in m_List) + yield return N; + } + } + + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('['); + int count = m_List.Count; + if (inline) + aMode = JSONTextMode.Compact; + for (int i = 0; i < count; i++) + { + if (i > 0) + aSB.Append(','); + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append(']'); + } + } + // End of JSONArray + + public partial class JSONObject : JSONNode + { + private Dictionary m_Dict = new Dictionary(); + + private bool inline = false; + public override bool Inline + { + get { return inline; } + set { inline = value; } + } + + public override JSONNodeType Tag { get { return JSONNodeType.Object; } } + public override bool IsObject { get { return true; } } + + public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); } + + + public override JSONNode this[string aKey] + { + get + { + if (m_Dict.ContainsKey(aKey)) + return m_Dict[aKey]; + else + return new JSONLazyCreator(this, aKey); + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = value; + else + m_Dict.Add(aKey, value); + } + } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + return m_Dict.ElementAt(aIndex).Value; + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (aIndex < 0 || aIndex >= m_Dict.Count) + return; + string key = m_Dict.ElementAt(aIndex).Key; + m_Dict[key] = value; + } + } + + public override int Count + { + get { return m_Dict.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = JSONNull.CreateOrGet(); + + if (aKey != null) + { + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = aItem; + else + m_Dict.Add(aKey, aItem); + } + else + m_Dict.Add(Guid.NewGuid().ToString(), aItem); + } + + public override JSONNode Remove(string aKey) + { + if (!m_Dict.ContainsKey(aKey)) + return null; + JSONNode tmp = m_Dict[aKey]; + m_Dict.Remove(aKey); + return tmp; + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + var item = m_Dict.ElementAt(aIndex); + m_Dict.Remove(item.Key); + return item.Value; + } + + public override JSONNode Remove(JSONNode aNode) + { + try + { + var item = m_Dict.Where(k => k.Value == aNode).First(); + m_Dict.Remove(item.Key); + return aNode; + } + catch + { + return null; + } + } + + public override void Clear() + { + m_Dict.Clear(); + } + + public override JSONNode Clone() + { + var node = new JSONObject(); + foreach (var n in m_Dict) + { + node.Add(n.Key, n.Value.Clone()); + } + return node; + } + + public override bool HasKey(string aKey) + { + return m_Dict.ContainsKey(aKey); + } + + public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) + { + JSONNode res; + if (m_Dict.TryGetValue(aKey, out res)) + return res; + return aDefault; + } + + public override IEnumerable Children + { + get + { + foreach (KeyValuePair N in m_Dict) + yield return N.Value; + } + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('{'); + bool first = true; + if (inline) + aMode = JSONTextMode.Compact; + foreach (var k in m_Dict) + { + if (!first) + aSB.Append(','); + first = false; + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + aSB.Append('\"').Append(Escape(k.Key)).Append('\"'); + if (aMode == JSONTextMode.Compact) + aSB.Append(':'); + else + aSB.Append(" : "); + k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append('}'); + } + + } + // End of JSONObject + + public partial class JSONString : JSONNode + { + private string m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.String; } } + public override bool IsString { get { return true; } } + + public override Enumerator GetEnumerator() { return new Enumerator(); } + + + public override string Value + { + get { return m_Data; } + set + { + m_Data = value; + } + } + + public JSONString(string aData) + { + m_Data = aData; + } + public override JSONNode Clone() + { + return new JSONString(m_Data); + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('\"').Append(Escape(m_Data)).Append('\"'); + } + public override bool Equals(object obj) + { + if (base.Equals(obj)) + return true; + string s = obj as string; + if (s != null) + return m_Data == s; + JSONString s2 = obj as JSONString; + if (s2 != null) + return m_Data == s2.m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + public override void Clear() + { + m_Data = ""; + } + } + // End of JSONString + + public partial class JSONNumber : JSONNode + { + private double m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Number; } } + public override bool IsNumber { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return m_Data.ToString(CultureInfo.InvariantCulture); } + set + { + double v; + if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) + m_Data = v; + } + } + + public override double AsDouble + { + get { return m_Data; } + set { m_Data = value; } + } + public override long AsLong + { + get { return (long)m_Data; } + set { m_Data = value; } + } + public override ulong AsULong + { + get { return (ulong)m_Data; } + set { m_Data = value; } + } + + public JSONNumber(double aData) + { + m_Data = aData; + } + + public JSONNumber(string aData) + { + Value = aData; + } + + public override JSONNode Clone() + { + return new JSONNumber(m_Data); + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append(Value); + } + private static bool IsNumeric(object value) + { + return value is int || value is uint + || value is float || value is double + || value is decimal + || value is long || value is ulong + || value is short || value is ushort + || value is sbyte || value is byte; + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (base.Equals(obj)) + return true; + JSONNumber s2 = obj as JSONNumber; + if (s2 != null) + return m_Data == s2.m_Data; + if (IsNumeric(obj)) + return Convert.ToDouble(obj) == m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + public override void Clear() + { + m_Data = 0; + } + } + // End of JSONNumber + + public partial class JSONBool : JSONNode + { + private bool m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } } + public override bool IsBoolean { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return m_Data.ToString(); } + set + { + bool v; + if (bool.TryParse(value, out v)) + m_Data = v; + } + } + public override bool AsBool + { + get { return m_Data; } + set { m_Data = value; } + } + + public JSONBool(bool aData) + { + m_Data = aData; + } + + public JSONBool(string aData) + { + Value = aData; + } + + public override JSONNode Clone() + { + return new JSONBool(m_Data); + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append((m_Data) ? "true" : "false"); + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (obj is bool) + return m_Data == (bool)obj; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + public override void Clear() + { + m_Data = false; + } + } + // End of JSONBool + + public partial class JSONNull : JSONNode + { + static JSONNull m_StaticInstance = new JSONNull(); + public static bool reuseSameInstance = true; + public static JSONNull CreateOrGet() + { + if (reuseSameInstance) + return m_StaticInstance; + return new JSONNull(); + } + private JSONNull() { } + + public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } } + public override bool IsNull { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return "null"; } + set { } + } + public override bool AsBool + { + get { return false; } + set { } + } + + public override JSONNode Clone() + { + return CreateOrGet(); + } + + public override bool Equals(object obj) + { + if (object.ReferenceEquals(this, obj)) + return true; + return (obj is JSONNull); + } + public override int GetHashCode() + { + return 0; + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONNull + + internal partial class JSONLazyCreator : JSONNode + { + private JSONNode m_Node = null; + private string m_Key = null; + public override JSONNodeType Tag { get { return JSONNodeType.None; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public JSONLazyCreator(JSONNode aNode) + { + m_Node = aNode; + m_Key = null; + } + + public JSONLazyCreator(JSONNode aNode, string aKey) + { + m_Node = aNode; + m_Key = aKey; + } + + private T Set(T aVal) where T : JSONNode + { + if (m_Key == null) + m_Node.Add(aVal); + else + m_Node.Add(m_Key, aVal); + m_Node = null; // Be GC friendly. + return aVal; + } + + public override JSONNode this[int aIndex] + { + get { return new JSONLazyCreator(this); } + set { Set(new JSONArray()).Add(value); } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this, aKey); } + set { Set(new JSONObject()).Add(aKey, value); } + } + + public override void Add(JSONNode aItem) + { + Set(new JSONArray()).Add(aItem); + } + + public override void Add(string aKey, JSONNode aItem) + { + Set(new JSONObject()).Add(aKey, aItem); + } + + public static bool operator ==(JSONLazyCreator a, object b) + { + if (b == null) + return true; + return System.Object.ReferenceEquals(a, b); + } + + public static bool operator !=(JSONLazyCreator a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj == null) + return true; + return System.Object.ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return 0; + } + + public override int AsInt + { + get { Set(new JSONNumber(0)); return 0; } + set { Set(new JSONNumber(value)); } + } + + public override float AsFloat + { + get { Set(new JSONNumber(0.0f)); return 0.0f; } + set { Set(new JSONNumber(value)); } + } + + public override double AsDouble + { + get { Set(new JSONNumber(0.0)); return 0.0; } + set { Set(new JSONNumber(value)); } + } + + public override long AsLong + { + get + { + if (longAsString) + Set(new JSONString("0")); + else + Set(new JSONNumber(0.0)); + return 0L; + } + set + { + if (longAsString) + Set(new JSONString(value.ToString())); + else + Set(new JSONNumber(value)); + } + } + + public override ulong AsULong + { + get + { + if (longAsString) + Set(new JSONString("0")); + else + Set(new JSONNumber(0.0)); + return 0L; + } + set + { + if (longAsString) + Set(new JSONString(value.ToString())); + else + Set(new JSONNumber(value)); + } + } + + public override bool AsBool + { + get { Set(new JSONBool(false)); return false; } + set { Set(new JSONBool(value)); } + } + + public override JSONArray AsArray + { + get { return Set(new JSONArray()); } + } + + public override JSONObject AsObject + { + get { return Set(new JSONObject()); } + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONLazyCreator + + public static class JSON + { + public static JSONNode Parse(string aJSON) + { + return JSONNode.Parse(aJSON); + } + } +}