From 7289b6292db45808995199333611f0db95335646 Mon Sep 17 00:00:00 2001 From: CCraigen Date: Tue, 6 Jun 2023 23:25:04 -0500 Subject: [PATCH 1/4] Adds the ability to inject additional spawnpoints into levels using a json file in the level zip. Also includes an in-game editor and json exporter. --- GameMod/GameMod.cs | 6 + GameMod/GameMod.csproj | 1 + GameMod/MPObserver.cs | 5 + GameMod/MPSpawnExtension.cs | 236 ++++++++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 GameMod/MPSpawnExtension.cs diff --git a/GameMod/GameMod.cs b/GameMod/GameMod.cs index d2479378..998da3c2 100644 --- a/GameMod/GameMod.cs +++ b/GameMod/GameMod.cs @@ -82,6 +82,12 @@ public static void Initialize() if (FindArg("-poor-mans-profiler")) { PoorMansProfiler.Initialize(harmony); } + + MPSpawnExtensionVis.visualizing = FindArg("-spawnpoint-editor"); + if (MPSpawnExtensionVis.visualizing) + { + uConsole.RegisterCommand("export-spawns", "Exports spawnpoints from the editor to a .json file in the OLmod directory", new uConsole.DebugCommand(MPSpawnExtensionVis.Export)); + } } public static bool FindArg(string arg) diff --git a/GameMod/GameMod.csproj b/GameMod/GameMod.csproj index 60257134..4490488f 100644 --- a/GameMod/GameMod.csproj +++ b/GameMod/GameMod.csproj @@ -104,6 +104,7 @@ + diff --git a/GameMod/MPObserver.cs b/GameMod/MPObserver.cs index d65dbd4c..bca0209d 100644 --- a/GameMod/MPObserver.cs +++ b/GameMod/MPObserver.cs @@ -588,6 +588,11 @@ join p in Overload.NetworkManager.m_Players on f.Key equals p.netId MPObserver.SetObservedPlayer(player); } } + // MPSpawnExtensionVis binding + if (cc_type == CCInput.SMASH_ATTACK && MPSpawnExtensionVis.visualizing && Controls.JustPressed(CCInput.SMASH_ATTACK)) + { + MPSpawnExtensionVis.TriggerSpawnToggle(); + } } } } diff --git a/GameMod/MPSpawnExtension.cs b/GameMod/MPSpawnExtension.cs new file mode 100644 index 00000000..6704aee8 --- /dev/null +++ b/GameMod/MPSpawnExtension.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Emit; +using HarmonyLib; +using Newtonsoft.Json; +using Overload; +using UnityEngine; + +namespace GameMod +{ + public static class MPSpawnExtension + { + // ***************************** + // INPUT + // ***************************** + + public static List LoadExtraSpawnpoints() + { + if (!GameplayManager.IsMultiplayer || !GameplayManager.Level.IsAddOn) + { + return null; + } + + List ExtraSpawns; + string payload = ""; + var filepath = Path.Combine(Path.GetDirectoryName(GameplayManager.Level.FilePath), $"{GameplayManager.Level.FileName}-spawnpoints.json"); + + string text3 = null; + byte[] array = Mission.LoadAddonData(GameplayManager.Level.ZipPath, filepath, ref text3, new string[] { ".json" }); + if (array != null) + { + payload = System.Text.Encoding.UTF8.GetString(array); + } + else + { + return null; + } + + try + { + ExtraSpawns = JsonConvert.DeserializeObject>(payload); + Debug.Log($"{ExtraSpawns.Count} additional spawnpoints parsed from the current level's embedded json file"); + } + catch (Exception e) + { + Debug.LogError("ERROR: Unable to parse additional player spawns, continuing with originals (error message: " + e.Message + ")"); + return null; + } + + return ExtraSpawns; + } + + public static void AddPlayerSpawns(LevelData ld) + { + List ExtraSpawns = LoadExtraSpawnpoints(); + + if (ExtraSpawns != null) + { + LevelData.SpawnPoint[] NewSpawnPoints = ld.m_player_spawn_points.Concat(ExtraSpawns).ToArray(); + ld.m_player_spawn_points = NewSpawnPoints; + } + } + } + + [HarmonyPatch(typeof(LevelData), "Awake")] + public static class MPSpawnExtension_LevelData_Awake + { + public static IEnumerable Transpiler(IEnumerable codes) + { + int count = 0; + foreach (var code in codes) + { + yield return code; + + if (code.opcode == OpCodes.Call && code.operand == AccessTools.Method(typeof(LevelData), "SetSpawnPointSegments")) + { + count++; + } + if (count == 2) + { + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(MPSpawnExtension), "AddPlayerSpawns")); + + count++; + } + } + } + } + + + // ***************************** + // Output + // ***************************** + + public static class MPSpawnExtensionVis + { + public static bool visualizing = false; + + public static List ExtraSpawns = new List(); + + public static void TriggerSpawnToggle() + { + bool adding = true; + + // if we're within 3U of an added spawn, remove it instead of adding a new one + foreach (SpawnpointVis spv in ExtraSpawns) + { + if (Vector3.Distance(GameManager.m_player_ship.c_transform_position, spv.position) <= 3f) + { + adding = false; + GameObject.Destroy(spv.visualizer); + ExtraSpawns.Remove(spv); + break; + } + } + + // if we're within 3U of an original spawn, don't add a new one + foreach (LevelData.SpawnPoint sp in GameManager.m_level_data.m_player_spawn_points) + { + if (Vector3.Distance(GameManager.m_player_ship.c_transform_position, sp.position) <= 3f) + { + adding = false; + } + } + + if (adding) + { + SpawnpointVis vis = new SpawnpointVis + { + position = GameManager.m_player_ship.c_transform_position, + rotation = GameManager.m_player_ship.c_transform_rotation, + spawnpoint = new LevelData.SpawnPoint(), + visualizer = GameObject.Instantiate(MPShips.selected.collider[0], GameManager.m_player_ship.c_transform_position, GameManager.m_player_ship.c_transform_rotation) + }; + + vis.spawnpoint.position = vis.position; + vis.spawnpoint.orientation = vis.rotation; + + MeshRenderer mr = vis.visualizer.AddComponent(); + mr.sharedMaterial = UIManager.gm.m_energy_material; + Material m = mr.material; + m.color = Color.green; + m.SetColor("_GlowColor", Color.green); + m.SetColor("_EdgeColor", Color.green); + mr.enabled = true; + + ExtraSpawns.Add(vis); + } + } + + public static void Export() + { + if (ExtraSpawns.Count != 0) + { + string filepath = Path.Combine(Config.OLModDir, $"{GameplayManager.Level.FileName}-spawnpoints.json"); + string extension = Path.GetExtension(filepath); + + int i = 0; + while (File.Exists(filepath)) + { + if (i == 0) + filepath = filepath.Replace(extension, "(" + ++i + ")" + extension); + else + filepath = filepath.Replace("(" + i + ")" + extension, "(" + ++i + ")" + extension); + } + + List payload = new List(); + + foreach (SpawnpointVis spv in ExtraSpawns) + { + payload.Add(spv.spawnpoint); + } + Debug.Log("Exported spawnpoints to filepath: " + filepath); + File.AppendAllText(filepath, JsonConvert.SerializeObject(payload, Formatting.Indented, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })); + } + else + { + Debug.Log("No additional spawnpoints, nothing to export!"); + } + } + } + + public struct SpawnpointVis + { + public Vector3 position; + public Quaternion rotation; + public LevelData.SpawnPoint spawnpoint; + public GameObject visualizer; + } + + [HarmonyPatch(typeof(Client), "OnMatchStart")] + class MPSpawnExtension_Client_OnMatchStart + { + static void Postfix() + { + if (GameplayManager.IsDedicatedServer() || !GameManager.m_local_player.m_spectator) + return; + + if (MPSpawnExtensionVis.visualizing) + { + MPSpawnExtensionVis.ExtraSpawns.Clear(); + + foreach (LevelData.SpawnPoint sp in GameManager.m_level_data.m_player_spawn_points) + { + GameObject go = GameObject.Instantiate(MPShips.selected.collider[0], sp.position, sp.orientation); + + MeshRenderer mr = go.AddComponent(); + mr.sharedMaterial = UIManager.gm.m_energy_material; + } + } + } + } + + [HarmonyPatch(typeof(UIElement), "DrawHUD")] + class MPSpawnExtension_UIElement_DrawHUD + { + static void Postfix(UIElement __instance) + { + if (MPSpawnExtensionVis.visualizing && MPObserver.Enabled) + { + Vector2 vector = default(Vector2); + vector.x = UIManager.UI_LEFT + 20f; + vector.y = UIManager.UI_TOP + 120f; + __instance.DrawStringSmall("Spawnpoint Edit Mode", vector, 0.55f, StringOffset.LEFT, UIManager.m_col_damage, 1f); + vector.y += 18f; + __instance.DrawStringSmall("Use SMASH ATTACK to add or remove spawnpoints", vector, 0.4f, StringOffset.LEFT, UIManager.m_col_damage, 1f); + vector.y += 15f; + __instance.DrawStringSmall("Use command \"export-spawns\" when finished", vector, 0.4f, StringOffset.LEFT, UIManager.m_col_damage, 1f); + vector.y += 20f; + __instance.DrawStringSmall("Additional spawnpoints: " + MPSpawnExtensionVis.ExtraSpawns.Count, vector, 0.4f, StringOffset.LEFT, UIManager.m_col_damage, 1f); + } + } + } +} From 1f071c042e1e7ebf9ed12edcc210baaefa3715ef Mon Sep 17 00:00:00 2001 From: CCraigen Date: Sun, 24 Sep 2023 21:32:00 -0500 Subject: [PATCH 2/4] Spawnpoint extension json is now loaded from an online source rather than the map zip --- GameMod/MPDownloadLevel.cs | 20 ++++--- GameMod/MPRespawn.cs | 1 + GameMod/MPSpawnExtension.cs | 115 ++++++++++++++++++++++++++---------- 3 files changed, 98 insertions(+), 38 deletions(-) diff --git a/GameMod/MPDownloadLevel.cs b/GameMod/MPDownloadLevel.cs index bc0273b7..84524609 100644 --- a/GameMod/MPDownloadLevel.cs +++ b/GameMod/MPDownloadLevel.cs @@ -230,9 +230,9 @@ class MPDownloadLoadScene { static IEnumerator WaitLevel(string name) { - while (MPDownloadLevel.DownloadBusy) + while (MPDownloadLevel.DownloadBusy || MPSpawnExtension.DownloadBusy) yield return null; - if (GameManager.MultiplayerMission.FindAddOnLevelNumByIdStringHash(name) >= 0) // test here to prevent loop + if (!name.Contains(":") || Config.NoDownload || GameManager.MultiplayerMission.FindAddOnLevelNumByIdStringHash(name) >= 0) // test here to prevent loop { Debug.Log("Level downloaded, loading scene"); Overload.NetworkManager.LoadScene(name); @@ -247,13 +247,17 @@ static IEnumerator WaitLevel(string name) static bool Prefix(string name) { - if (!name.Contains(":") || Config.NoDownload) - return true; - if (!MPDownloadLevel.DownloadBusy && GameManager.MultiplayerMission.FindAddOnLevelNumByIdStringHash(name) < 0) - MPDownloadLevel.StartGetLevel(name); - if (MPDownloadLevel.DownloadBusy) + MPSpawnExtension.CheckExtraSpawnpoints(name); // we want to trigger even for stock levels + + if (name.Contains(":") && !Config.NoDownload) + { + if (!MPDownloadLevel.DownloadBusy && GameManager.MultiplayerMission.FindAddOnLevelNumByIdStringHash(name) < 0) + MPDownloadLevel.StartGetLevel(name); + } + + if (MPDownloadLevel.DownloadBusy || MPSpawnExtension.DownloadBusy) { - Debug.Log("Level still downloading when loading match scene, delay scene load " + name); + //Debug.Log("Level still downloading when loading match scene, delay scene load " + name); GameManager.m_gm.StartCoroutine(WaitLevel(name)); return false; } diff --git a/GameMod/MPRespawn.cs b/GameMod/MPRespawn.cs index 83ad58c8..37e1c1f9 100644 --- a/GameMod/MPRespawn.cs +++ b/GameMod/MPRespawn.cs @@ -30,6 +30,7 @@ class MPRespawn_InitBeforeEachMatch { private static void Prefix() { MPRespawn.spawnPointsFromItems.Clear(); MPRespawn.spawnPointDistances.Clear(); + MPSpawnExtension.ResetForNewLevel(); } } diff --git a/GameMod/MPSpawnExtension.cs b/GameMod/MPSpawnExtension.cs index 6704aee8..576480ea 100644 --- a/GameMod/MPSpawnExtension.cs +++ b/GameMod/MPSpawnExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,58 +8,96 @@ using Newtonsoft.Json; using Overload; using UnityEngine; +using UnityEngine.Networking; namespace GameMod { public static class MPSpawnExtension { + const string BASE_URL = "https://raw.githubusercontent.com/CCraigen/ol-map-revisions/main/spawnpoints/"; + + public static List spawnpoints = new List(); + public static bool DownloadBusy = false; + public static bool DownloadChecked = false; + + // ***************************** // INPUT // ***************************** - public static List LoadExtraSpawnpoints() + + public static void ResetForNewLevel() { - if (!GameplayManager.IsMultiplayer || !GameplayManager.Level.IsAddOn) + //Debug.Log("CCF resetting in MPSpawnExtension"); + spawnpoints.Clear(); + DownloadChecked = false; + } + + public static void CheckExtraSpawnpoints(string name) + { + if (!GameplayManager.IsMultiplayer || DownloadChecked) { - return null; + return; } - List ExtraSpawns; - string payload = ""; - var filepath = Path.Combine(Path.GetDirectoryName(GameplayManager.Level.FilePath), $"{GameplayManager.Level.FileName}-spawnpoints.json"); + DownloadChecked = true; - string text3 = null; - byte[] array = Mission.LoadAddonData(GameplayManager.Level.ZipPath, filepath, ref text3, new string[] { ".json" }); - if (array != null) + string[] split = name.Split(':'); + if (split == null) { - payload = System.Text.Encoding.UTF8.GetString(array); + Debug.Log("Invalid level name in CheckExtraSpawnpoints, skipping."); + return; } - else + + string levelname = split[0].ToUpperInvariant(); + + if (levelname.EndsWith(".MP")) { - return null; + levelname = levelname.Remove(levelname.Length - 3); } - try + GameManager.m_gm.StartCoroutine(LoadSpawnpointFile(levelname)); + } + + public static IEnumerator LoadSpawnpointFile(string name) + { + DownloadBusy = true; // piggybacking off of the download code delay + + //Debug.Log("CCF requesting url " + BASE_URL + $"{name}-spawnpoints.json"); + + UnityWebRequest www = UnityWebRequest.Get(BASE_URL + $"{name}-spawnpoints.json"); + www.timeout = 3; + yield return www.SendWebRequest(); + + if (www.isNetworkError) { - ExtraSpawns = JsonConvert.DeserializeObject>(payload); - Debug.Log($"{ExtraSpawns.Count} additional spawnpoints parsed from the current level's embedded json file"); + Debug.Log(www.error); + DownloadBusy = false; + yield break; } - catch (Exception e) + if (www.responseCode == 200) // we found a file { - Debug.LogError("ERROR: Unable to parse additional player spawns, continuing with originals (error message: " + e.Message + ")"); - return null; + try + { + spawnpoints = JsonConvert.DeserializeObject>(www.downloadHandler.text); + Debug.Log($"{spawnpoints.Count} additional spawnpoints parsed from the current level's online json file"); + } + catch (Exception e) + { + Debug.LogError("ERROR: Unable to parse additional player spawns, continuing with originals (error message: " + e.Message + ")"); + spawnpoints.Clear(); + } } - return ExtraSpawns; + DownloadBusy = false; } - public static void AddPlayerSpawns(LevelData ld) + public static void LoadExtraSpawnpoints(LevelData ld) { - List ExtraSpawns = LoadExtraSpawnpoints(); - - if (ExtraSpawns != null) + //Debug.Log("CCF additional spawn count: " + spawnpoints.Count); + if (spawnpoints.Count != 0) { - LevelData.SpawnPoint[] NewSpawnPoints = ld.m_player_spawn_points.Concat(ExtraSpawns).ToArray(); + LevelData.SpawnPoint[] NewSpawnPoints = ld.m_player_spawn_points.Concat(spawnpoints).ToArray(); ld.m_player_spawn_points = NewSpawnPoints; } } @@ -81,19 +120,20 @@ public static IEnumerable Transpiler(IEnumerable(); mr.sharedMaterial = UIManager.gm.m_energy_material; + + if (MPSpawnExtension.spawnpoints.Contains(sp)) + { + Material m = mr.material; + m.color = Color.green; + m.SetColor("_GlowColor", Color.green); + m.SetColor("_EdgeColor", Color.green); + + MPSpawnExtensionVis.ExtraSpawns.Add(new SpawnpointVis() { position = sp.position, rotation = sp.orientation, spawnpoint = sp, visualizer = go }); + } + + mr.enabled = true; } } } From 355919b0643f6793cc30c300eaf6243a3928140d Mon Sep 17 00:00:00 2001 From: CCraigen Date: Tue, 21 Nov 2023 00:45:43 -0600 Subject: [PATCH 3/4] Updated the spawn-extension url to point at the community repo --- GameMod/MPSpawnExtension.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GameMod/MPSpawnExtension.cs b/GameMod/MPSpawnExtension.cs index 576480ea..8ae1b62e 100644 --- a/GameMod/MPSpawnExtension.cs +++ b/GameMod/MPSpawnExtension.cs @@ -14,7 +14,7 @@ namespace GameMod { public static class MPSpawnExtension { - const string BASE_URL = "https://raw.githubusercontent.com/CCraigen/ol-map-revisions/main/spawnpoints/"; + const string BASE_URL = "https://raw.githubusercontent.com/overload-development-community/ol-map-revisions/main/spawnpoints/"; public static List spawnpoints = new List(); public static bool DownloadBusy = false; @@ -84,7 +84,7 @@ public static IEnumerator LoadSpawnpointFile(string name) } catch (Exception e) { - Debug.LogError("ERROR: Unable to parse additional player spawns, continuing with originals (error message: " + e.Message + ")"); + Debug.LogError("WARNING: Unable to parse additional player spawns, continuing with originals (error message: " + e.Message + ")"); spawnpoints.Clear(); } } From cbe34a1e5f1134bfa2b0787444ac899431b119f4 Mon Sep 17 00:00:00 2001 From: CCraigen Date: Thu, 14 Mar 2024 22:09:17 -0500 Subject: [PATCH 4/4] Fixes for spawnpoint loading exceptions --- GameMod/MPSpawnExtension.cs | 3 ++- GameMod/MPTweaks.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GameMod/MPSpawnExtension.cs b/GameMod/MPSpawnExtension.cs index 8ae1b62e..7b6aa123 100644 --- a/GameMod/MPSpawnExtension.cs +++ b/GameMod/MPSpawnExtension.cs @@ -15,6 +15,7 @@ namespace GameMod public static class MPSpawnExtension { const string BASE_URL = "https://raw.githubusercontent.com/overload-development-community/ol-map-revisions/main/spawnpoints/"; + //const string BASE_URL = "https://raw.githubusercontent.com/CCraigen/ol-map-revisions/main/spawnpoints/"; // testing repo public static List spawnpoints = new List(); public static bool DownloadBusy = false; @@ -35,7 +36,7 @@ public static void ResetForNewLevel() public static void CheckExtraSpawnpoints(string name) { - if (!GameplayManager.IsMultiplayer || DownloadChecked) + if (DownloadChecked || !(GameplayManager.IsDedicatedServer() || GameplayManager.IsMultiplayer)) { return; } diff --git a/GameMod/MPTweaks.cs b/GameMod/MPTweaks.cs index b9fff2c9..91256355 100644 --- a/GameMod/MPTweaks.cs +++ b/GameMod/MPTweaks.cs @@ -160,7 +160,7 @@ class MPTweaksLoadScene { private static void Postfix() { - if (!GameplayManager.IsDedicatedServer()) + if (MPDownloadLevel.DownloadBusy || MPSpawnExtension.DownloadBusy || !GameplayManager.IsDedicatedServer()) return; Debug.Log("MPTweaksLoadScene"); RobotManager.ReadMultiplayerModeFile();