From eebf00988eef2946ee94be26abfc9c5afaf08861 Mon Sep 17 00:00:00 2001 From: LadyAliceMargatroid Date: Thu, 25 Jul 2024 21:46:18 -0700 Subject: [PATCH] Initial commit --- .gitignore | 7 + DunGenPlus/DunGenPlus.sln | 25 ++ DunGenPlus/DunGenPlus/API.cs | 26 ++ DunGenPlus/DunGenPlus/Assets.cs | 36 ++ .../Collections/DunGenExtenderEvents.cs | 15 + .../Collections/DunGenExtenderProperties.cs | 131 +++++++ .../DunGenPlus/Collections/ExtenderEvent.cs | 26 ++ .../DunGenPlus/Collections/NodeArchetype.cs | 19 + .../DunGenPlus/Components/DoorwayCleanup.cs | 72 ++++ .../DCSConnectorBlockerSpawnedPrefab.cs | 35 ++ .../DCSRemoveDoorwayConnectedDoorway.cs | 27 ++ .../DCSRemoveDoorwaySpawnedPrefab.cs | 30 ++ .../DCSRemoveGameObjectsConnectedDoorway.cs | 30 ++ .../DoorwayCleanupScript.cs | 14 + .../DoorwayCleanupScriptDoorwayCompare.cs | 44 +++ .../DunGenPlus/Components/DoorwaySisters.cs | 64 ++++ .../Components/MainRoomDoorwayGroups.cs | 36 ++ .../Props/SpawnSyncedObjectCycle.cs | 50 +++ DunGenPlus/DunGenPlus/DunGenExtender.cs | 22 ++ DunGenPlus/DunGenPlus/DunGenPlus.csproj | 119 ++++++ .../Generation/DoorwaySistersRule.cs | 90 +++++ .../Generation/DunGenPlusGenerator.cs | 346 ++++++++++++++++++ .../DunGenPlus/Managers/DoorwayManager.cs | 46 +++ .../Patches/DoorwayConnectionPatch.cs | 63 ++++ .../Patches/DungeonGeneratorPatch.cs | 162 ++++++++ DunGenPlus/DunGenPlus/Plugin.cs | 67 ++++ .../DunGenPlus/Properties/AssemblyInfo.cs | 36 ++ .../DunGenPlus/Utils/TranspilerUtilities.cs | 191 ++++++++++ DunGenPlus/DunGenPlus/Utils/Utility.cs | 33 ++ LICENSE | 42 +-- 30 files changed, 1883 insertions(+), 21 deletions(-) create mode 100644 .gitignore create mode 100644 DunGenPlus/DunGenPlus.sln create mode 100644 DunGenPlus/DunGenPlus/API.cs create mode 100644 DunGenPlus/DunGenPlus/Assets.cs create mode 100644 DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs create mode 100644 DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs create mode 100644 DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs create mode 100644 DunGenPlus/DunGenPlus/Collections/NodeArchetype.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanup.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSConnectorBlockerSpawnedPrefab.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwayConnectedDoorway.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwaySpawnedPrefab.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveGameObjectsConnectedDoorway.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScript.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScriptDoorwayCompare.cs create mode 100644 DunGenPlus/DunGenPlus/Components/DoorwaySisters.cs create mode 100644 DunGenPlus/DunGenPlus/Components/MainRoomDoorwayGroups.cs create mode 100644 DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs create mode 100644 DunGenPlus/DunGenPlus/DunGenExtender.cs create mode 100644 DunGenPlus/DunGenPlus/DunGenPlus.csproj create mode 100644 DunGenPlus/DunGenPlus/Generation/DoorwaySistersRule.cs create mode 100644 DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs create mode 100644 DunGenPlus/DunGenPlus/Managers/DoorwayManager.cs create mode 100644 DunGenPlus/DunGenPlus/Patches/DoorwayConnectionPatch.cs create mode 100644 DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs create mode 100644 DunGenPlus/DunGenPlus/Plugin.cs create mode 100644 DunGenPlus/DunGenPlus/Properties/AssemblyInfo.cs create mode 100644 DunGenPlus/DunGenPlus/Utils/TranspilerUtilities.cs create mode 100644 DunGenPlus/DunGenPlus/Utils/Utility.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..473fee7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +Updates/ +Pictures/ +bin/ +.vs/ +obj/ + +/ScarletMansion/ScarletMansion/scarletmansion \ No newline at end of file diff --git a/DunGenPlus/DunGenPlus.sln b/DunGenPlus/DunGenPlus.sln new file mode 100644 index 0000000..725ac19 --- /dev/null +++ b/DunGenPlus/DunGenPlus.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35013.160 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DunGenPlus", "DunGenPlus\DunGenPlus.csproj", "{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8E7870E-8528-46EE-84B6-8328BFCC093B} + EndGlobalSection +EndGlobal diff --git a/DunGenPlus/DunGenPlus/API.cs b/DunGenPlus/DunGenPlus/API.cs new file mode 100644 index 0000000..e70a481 --- /dev/null +++ b/DunGenPlus/DunGenPlus/API.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using DunGen.Graph; + +namespace DunGenPlus +{ + public class API { + + public static bool AddDunGenExtender(DungeonFlow dungeonFlow, DunGenExtender dunGenExtender) { + if (Plugin.DunGenExtenders.ContainsKey(dungeonFlow)) { + Plugin.logger.LogWarning($"Already contains DunGenExtender asset for {dungeonFlow.name}"); + return false; + } + + Plugin.DunGenExtenders.Add(dungeonFlow, dunGenExtender); + Plugin.logger.LogInfo($"Added DunGenExtender asset for {dungeonFlow.name}"); + + return true; + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Assets.cs b/DunGenPlus/DunGenPlus/Assets.cs new file mode 100644 index 0000000..1445bf4 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Assets.cs @@ -0,0 +1,36 @@ +using DunGenPlus; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BepInEx; +using UnityEngine; +using LethalLevelLoader; + +namespace DunGenPlus { + internal class Assets { + + public static void LoadAssets(){ + foreach (string text in Directory.GetFiles(Paths.PluginPath, "*.lethalbundle", SearchOption.AllDirectories)) { + FileInfo fileInfo = new FileInfo(text); + LethalLevelLoader.AssetBundleLoader.AddOnLethalBundleLoadedListener(AutoAddLethalBundle, fileInfo.Name); + } + } + + static void AutoAddLethalBundle(AssetBundle assetBundle){ + var extenders = assetBundle.LoadAllAssets(); + var content = assetBundle.LoadAllAssets(); + + if (content.Length == 0) { + Plugin.logger.LogWarning($".lethalbundle does not contain any ExtendedContent. Unless you are manually creating and adding your ExtendedDungeonFlow with code, the DunGenExtender will probably not work."); + } + + foreach(var e in extenders){ + API.AddDunGenExtender(e.DungeonFlow, e); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs new file mode 100644 index 0000000..7c8acf2 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DunGenPlus.Collections { + + [System.Serializable] + public class DunGenExtenderEvents { + + public ExtenderEvent OnModifyDunGenExtenderProperties = new ExtenderEvent(); + + } +} diff --git a/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs new file mode 100644 index 0000000..7b5d839 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs @@ -0,0 +1,131 @@ +using DunGen.Graph; +using DunGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Collections { + + [System.Serializable] + public class DunGenExtenderProperties { + + [Header("Main Path")] + [Tooltip("The number of main paths.\n\n1 means no additional main paths\n3 means two additional main paths\netc.")] + [Range(1, 9)] + public int MainPathCount = 1; + [Tooltip("The Tile Prefab where the additional main paths will start from. Cannot be null if MainPathCount is more than 1.\n\nHighly advice for this Tile Prefab to have multiple doorways.")] + public GameObject MainRoomTilePrefab; + + [Header("Dungeon Bounds")] + [Tooltip("If enabled, restricts the dungeon's generation to the bounds described below.\n\nThis will help in condensing the dungeon, but it will increase the chance of dungeon generation failure (potentially guarantees failure if the bounds is too small).")] + public bool UseDungeonBounds = false; + [Tooltip("The base size of the bounds.")] + public Vector3 DungeonSizeBase = new Vector3(120f, 40f, 80f); + [Tooltip("The factor that's multiplied with the base size AND the dungeon's size. The resulting value is added to the base size of the bounds.\n\n0 means that the bound size is not influenced by the dungeon's size and is therefore a constant.")] + public Vector3 DungeonSizeFactor = new Vector3(1f, 0f, 1f); + [Tooltip("The base positional offset of the bounds.")] + public Vector3 DungeonPositionOffset; + [Tooltip("The pivot of the bounds.")] + public Vector3 DungeonPositionPivot = new Vector3(0.5f, 0f, 0.5f); + + [Header("Archetypes on Normal Nodes")] + [Tooltip("If enabled, adds archetypes to the normal nodes in the DungeonFlow.\n\nBy default, nodes cannot have branching paths since they don't have archetype references. This allows nodes to have branching paths.")] + public bool AddArchetypesToNormalNodes = true; + public List NormalNodeArchetypes; + internal Dictionary _normalNodeArchetypesDictioanry; + internal NodeArchetype _defaultNodeArchetype; + + [Header("Doorway Sisters")] + [Tooltip("If enabled, the DoorwaySisters component will become active.\n\nThe component prevents an intersecting doorway from generating if it's 'sister' doorway already generated and both doorways would lead to the same neighboring tile.\n\nThis is designed for the scenario where, two neighboring doorways would lead to the same tile, one doorway is a locked door and the other is an open doorway. This would defeat the purpose of the locked door, and such as, this feature exists if needed.\n\nThis feature slows down dungeon generation slightly when enabled.")] + public bool UseDoorwaySisters = false; + + [Header("Line Randomizer")] + [Tooltip("If enabled, every archetype in LineRandomizerArchetypes will have the last LineRandomizerTakeCount tilesets replaced by a randomly selected set of tilesets from LineRandomizerTileSets. This applies for both archetype's TileSets and BranchCapTileSets.\n\nThis is designed for the scenario where dungeon generation takes a long time due to the combination of too many tiles and/or doorways in those tiles. This can reduce dungeon generation time while keeping some of the randomness of dungeon generation.\n\nAs stated previously, this WILL replace the last LineRandomizerTakeCount tilesets in the archetype's TileSets and BranchCapTileSets. As such you must guarantee that those elements can be replaced.")] + public bool UseLineRandomizer = false; + [Tooltip("The archetypes whose tilesets will be replaced.\n\nThese archetypes should ideally used in the Lines section of DungeonFlow, but it's a free country.")] + public List LineRandomizerArchetypes; + [Tooltip("The tilesets that will be used for replacement.")] + public List LineRandomizerTileSets; + [Tooltip("The amount of tilesets that will be replaced from the archetypes, starting from the last element to the first element.\n\nAs stated previously, this WILL replace the tilesets in the archetype's TileSets and BranchCapTileSets. As such you must guarantee that those elements can be replaced.")] + public int LineRandomizerTakeCount = 3; + + [Header("Max Shadows Request")] + [Tooltip("If enabled, updates the MaxShadowsRequest to MaxShadowsRequestAmount when your dungeon loads.\n\nThis is designed for the scenario where your dungeon, for whatever reason, has too many lights nearby and causes the annoying 'Max shadow requests count reached' warning to spam the logs.")] + public bool UseMaxShadowsRequestUpdate = false; + [Tooltip("The amount of MaxShadowsRequest.\n\n4 is the game's default value. I find 8 to be more than acceptable.")] + public int MaxShadowsRequestAmount = 8; + + internal void SetupProperties(DungeonGenerator generator){ + _normalNodeArchetypesDictioanry = new Dictionary(); + _defaultNodeArchetype = null; + + foreach(var n in NormalNodeArchetypes) { + if (_normalNodeArchetypesDictioanry.ContainsKey(n.label)) { + Plugin.logger.LogError($"Label {n.label} already exists. Ignoring latest entry."); + continue; + } + _normalNodeArchetypesDictioanry.Add(n.label, n); + + if (string.IsNullOrWhiteSpace(n.label)) { + _defaultNodeArchetype = n; + } + } + } + + internal DungeonArchetype GetRandomArchetype(string label, RandomStream randomStream) { + NodeArchetype node; + if (!_normalNodeArchetypesDictioanry.TryGetValue(label, out node)) { + node = _defaultNodeArchetype; + } + + if (node != null) { + var archetypes = node.archetypes; + var count = archetypes.Count; + if (count == 0) return null; + + var index = randomStream.Next(0, count); + return archetypes[index]; + } + + return null; + } + + internal Bounds GetDungeonBounds(float dungeonScale) { + var size = DungeonSizeBase + Vector3.Scale(DungeonSizeBase * (dungeonScale - 1), DungeonSizeFactor); + var offset = DungeonPositionOffset + Vector3.Scale(size, DungeonPositionPivot); + return new Bounds(offset, size); + } + + internal DunGenExtenderProperties Copy() { + var copy = new DunGenExtenderProperties(); + + copy.MainPathCount = MainPathCount; + copy.MainRoomTilePrefab = MainRoomTilePrefab; + + copy.UseDungeonBounds = UseDungeonBounds; + copy.DungeonSizeBase = DungeonSizeBase; + copy.DungeonSizeFactor = DungeonSizeFactor; + copy.DungeonPositionOffset = DungeonPositionOffset; + copy.DungeonPositionPivot = DungeonPositionPivot; + + copy.AddArchetypesToNormalNodes = AddArchetypesToNormalNodes; + copy.NormalNodeArchetypes = NormalNodeArchetypes; + + copy.UseDoorwaySisters = UseDoorwaySisters; + + copy.UseLineRandomizer = UseLineRandomizer; + copy.LineRandomizerTileSets = LineRandomizerTileSets; + copy.LineRandomizerArchetypes = LineRandomizerArchetypes; + copy.LineRandomizerTakeCount = LineRandomizerTakeCount; + + copy.UseMaxShadowsRequestUpdate = UseMaxShadowsRequestUpdate; + copy.MaxShadowsRequestAmount = MaxShadowsRequestAmount; + + return copy; + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs b/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs new file mode 100644 index 0000000..4374cd7 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DunGenPlus.Collections { + public class ExtenderEvent { + + internal event ParameterEvent onParameterEvent; + + public void Invoke(T param) { + onParameterEvent?.Invoke(param); + } + + public void AddListener(ParameterEvent listener) { + onParameterEvent += listener; + } + + public void RemoveListener(ParameterEvent listener) { + onParameterEvent -= listener; + } + + public delegate void ParameterEvent(T param); + } +} diff --git a/DunGenPlus/DunGenPlus/Collections/NodeArchetype.cs b/DunGenPlus/DunGenPlus/Collections/NodeArchetype.cs new file mode 100644 index 0000000..a3a8eb1 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Collections/NodeArchetype.cs @@ -0,0 +1,19 @@ +using DunGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Collections { + + [System.Serializable] + public class NodeArchetype { + [Tooltip("The normal node with this label will gain a randomly chosen archetype.\n\nIf empty, this becomes the default choice for any normal node without a NodeArchetype specified in this list.")] + public string label; + [Tooltip("The list of archetypes. One will be randomly chosen.")] + public List archetypes; + } + +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanup.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanup.cs new file mode 100644 index 0000000..a357ec2 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanup.cs @@ -0,0 +1,72 @@ +using DunGen; +using DunGenPlus.Components.DoorwayCleanupScripting; +using DunGenPlus.Managers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components { + public class DoorwayCleanup : MonoBehaviour, IDungeonCompleteReceiver { + + [Header("Doorway References")] + [Tooltip("The doorway reference.")] + public Doorway doorway; + [Tooltip("The connectors scene objects of the doorway.\n\nHighly advise to empty the corresponding list in the doorway.")] + public List connectors; + [Tooltip("The blockers scene objects of the doorway.\n\nHighly advise to empty the corresponding list in the doorway.")] + public List blockers; + [Tooltip("The doorway gameobject target for the DoorwayCleanupScripts. Can be null.")] + public GameObject doorwayGameObject; + + [Header("Overrides")] + [Tooltip("Mainly for code purposes. Forces the connectors to be active.")] + public bool overrideConnector; + [Tooltip("Mainly for code purposes. Forces the blockers to be active.")] + public bool overrideBlocker; + [Tooltip("Mainly for code purposes. Forces the doorway gameobject to be disabled.")] + public bool overrideNoDoorway; + + public void OnDungeonComplete(Dungeon dungeon) { + SetBlockers(true); + DoorwayManager.AddDoorwayCleanup(this); + } + + public void Cleanup(){ + // start up like in original + SwitchConnectorBlocker(doorway.ConnectedDoorway != null); + + var cleanupList = GetComponentsInChildren(); + foreach(var c in cleanupList) c.Cleanup(this); + + if (overrideNoDoorway) SwitchDoorwayGameObject(false); + + // clean up like in original + foreach(var c in connectors){ + if (!c.activeSelf) UnityEngine.Object.DestroyImmediate(c, false); + } + + foreach(var b in blockers){ + if (!b.activeSelf) UnityEngine.Object.DestroyImmediate(b, false); + } + } + + public void SetBlockers(bool state){ + foreach(var b in blockers) b.SetActive(state); + } + + public void SwitchConnectorBlocker(bool isConnector){ + if (overrideConnector) isConnector = true; + if (overrideBlocker) isConnector = false; + + foreach(var c in connectors) c.SetActive(isConnector); + foreach(var b in blockers) b.SetActive(!isConnector); + } + + public void SwitchDoorwayGameObject(bool isActive){ + doorwayGameObject?.SetActive(isActive); + } + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSConnectorBlockerSpawnedPrefab.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSConnectorBlockerSpawnedPrefab.cs new file mode 100644 index 0000000..55a47e1 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSConnectorBlockerSpawnedPrefab.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components.DoorwayCleanupScripting { + public class DCSConnectorBlockerSpawnedPrefab : DoorwayCleanupScript { + + public enum Action { SwitchToConnector, SwitchToBlocker }; + + [Header("Calls switch action\nif Doorway instantiates a Connector/Blocker prefab with the target's name")] + [Header("Switch Action")] + public Action switchAction; + + [Header("Target")] + public GameObject target; + + public override void Cleanup(DoorwayCleanup parent) { + var result = false; + foreach(Transform t in parent.doorway.transform){ + if (t.gameObject.activeSelf && t.name.Contains(target.name)) { + result = true; + break; + } + } + + if (result) { + parent.SwitchConnectorBlocker(switchAction == Action.SwitchToConnector); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwayConnectedDoorway.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwayConnectedDoorway.cs new file mode 100644 index 0000000..02b1058 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwayConnectedDoorway.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components.DoorwayCleanupScripting { + public class DCSRemoveDoorwayConnectedDoorway : DoorwayCleanupScriptDoorwayCompare { + + [Header("Removes Doorway Gameobject\nif the neighboring doorway's priority matches the operation comparison")] + [Header("Operation Comparison")] + public int doorwayPriority; + public Operation operation = Operation.Equal; + + public override void Cleanup(DoorwayCleanup parent) { + var doorway = parent.doorway; + if (doorway.connectedDoorway == null) return; + var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority); + + if (result) { + parent.SwitchDoorwayGameObject(false); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwaySpawnedPrefab.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwaySpawnedPrefab.cs new file mode 100644 index 0000000..2f2dbbe --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveDoorwaySpawnedPrefab.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components.DoorwayCleanupScripting { + public class DCSRemoveDoorwaySpawnedPrefab : DoorwayCleanupScript { + + [Header("Removes Doorway Gameobject\nif Doorway instantiates a Connector/Blocker prefab with the target's name")] + [Header("Target")] + public GameObject target; + + public override void Cleanup(DoorwayCleanup parent) { + var result = false; + foreach(Transform t in parent.doorway.transform){ + if (t.gameObject.activeSelf && t.name.Contains(target.name)) { + result = true; + break; + } + } + + if (result) { + parent.SwitchDoorwayGameObject(false); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveGameObjectsConnectedDoorway.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveGameObjectsConnectedDoorway.cs new file mode 100644 index 0000000..82a0f1b --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DCSRemoveGameObjectsConnectedDoorway.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components.DoorwayCleanupScripting { + public class DCSRemoveGameObjectsConnectedDoorway : DoorwayCleanupScriptDoorwayCompare { + + [Header("Removes target GameObjects\nif the neighboring doorway's priority matches the operation comparison")] + + [Header("Operation Comparison")] + public int doorwayPriority; + public Operation operation = Operation.Equal; + + [Header("Targets")] + public List targets; + + public override void Cleanup(DoorwayCleanup parent) { + var doorway = parent.doorway; + if (doorway.connectedDoorway == null) return; + var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority); + if (result) { + foreach(var t in targets) t.SetActive(false); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScript.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScript.cs new file mode 100644 index 0000000..edd1b49 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScript.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components.DoorwayCleanupScripting { + public abstract class DoorwayCleanupScript : MonoBehaviour { + + public abstract void Cleanup(DoorwayCleanup parent); + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScriptDoorwayCompare.cs b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScriptDoorwayCompare.cs new file mode 100644 index 0000000..8fef87c --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwayCleanupScripting/DoorwayCleanupScriptDoorwayCompare.cs @@ -0,0 +1,44 @@ +using DunGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DunGenPlus.Components.DoorwayCleanupScripting { + public abstract class DoorwayCleanupScriptDoorwayCompare : DoorwayCleanupScript { + + public enum Operation { Equal, NotEqual, LessThan, GreaterThan } + + public Func GetOperation(Operation operation){ + switch(operation){ + case Operation.Equal: + return EqualOperation; + case Operation.NotEqual: + return NotEqualOperation; + case Operation.LessThan: + return LessThanOperation; + case Operation.GreaterThan: + return GreaterThanOperation; + } + return null; + } + + public bool EqualOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority == doorwayPriority; + } + + public bool NotEqualOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority != doorwayPriority; + } + + public bool LessThanOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority < doorwayPriority; + } + + public bool GreaterThanOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority > doorwayPriority; + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/DoorwaySisters.cs b/DunGenPlus/DunGenPlus/Components/DoorwaySisters.cs new file mode 100644 index 0000000..2581e80 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/DoorwaySisters.cs @@ -0,0 +1,64 @@ +using DunGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components { + public class DoorwaySisters : MonoBehaviour { + + private Doorway _self; + public Doorway Self { + get { + if (_self == null) { + _self = GetComponent(); + } + return _self; + } + } + + [Tooltip("The list of 'sister' doorways.\n\nUseDoorwaySisters must be toggled in DunGenExtender for this component to be used.\n\nThis doorway will not generate if it's an intersecting doorway, any of it's 'sister' doorways are generated, and both this doorway and the 'sister' doorway lead to the same tile.")] + public List sisters; + + void OnValidate(){ + var sis = sisters.Select(s => s.GetComponent()); + foreach(var s in sis) { + if (s == null) continue; + + s.TryAddSisterDoorway(Self); + } + } + + public void TryAddSisterDoorway(Doorway doorway){ + if (sisters.Contains(doorway)) return; + sisters.Add(doorway); + } + + public void OnDrawGizmosSelected(){ + var center = transform.position + Vector3.up; + if (sisters == null) return; + + foreach(var sis in sisters){ + var target = sis.transform.position + Vector3.up; + var comp = sis.GetComponent(); + + var self = Self; + if (self == null) { + Gizmos.color = Color.magenta; + } else if (comp == null || comp.sisters == null){ + Gizmos.color = Color.yellow; + } else if (!comp.sisters.Contains(self)) { + Gizmos.color = Color.red; + } else { + Gizmos.color = Color.green; + } + + Gizmos.DrawLine(center, target); + Gizmos.DrawSphere((center + target) * 0.5f, 0.25f); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/MainRoomDoorwayGroups.cs b/DunGenPlus/DunGenPlus/Components/MainRoomDoorwayGroups.cs new file mode 100644 index 0000000..e8482a7 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/MainRoomDoorwayGroups.cs @@ -0,0 +1,36 @@ +using DunGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components { + public class MainRoomDoorwayGroups : MonoBehaviour { + + [System.Serializable] + public class DoorwayList { + [Tooltip("For organizing purposes. Has no effect.")] + public string name; + [Tooltip("The group of doorways.")] + public List doorways; + + public bool Contains(Doorway target) { + return doorways.Contains(target); + } + } + + [Tooltip("When an additional main path is being generated, it will get the doorway used for the previous main path, find it's corresponding group below, and prevents the dungeon generation from using that group's doorways until the main paths are all generated.\n\nIf you want this feature, this must be attached to the tile that will act as the MainRoomTilePrefab.\n\nThis is designed for the scenario where you would like the main paths to be generated more evenly throughout the MainRoomTilePrefab.")] + public List doorwayLists; + public List doorwayListFirst => doorwayLists.Count > 0 ? doorwayLists[0].doorways : null; + + public List GrabDoorwayGroup(Doorway target){ + foreach(var a in doorwayLists){ + if (a.Contains(target)) return a.doorways; + } + return null; + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs b/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs new file mode 100644 index 0000000..37c397c --- /dev/null +++ b/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs @@ -0,0 +1,50 @@ +using DunGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace DunGenPlus.Components.Props +{ + public class SpawnSyncedObjectCycle : MonoBehaviour, IDungeonCompleteReceiver { + + public static int cycle; + public static Dictionary cycleDictionary; + + [Tooltip("The SpawnSyncedObject reference.\n\nWhen the dungeon generation finishes, the spawnPrefab of the referenced SpawnSyncedObject will change to one of the Props based on a cycle. The starting value is random.\n\nThis is designed for the scenario where you have multiple very similar networked gameobjects that serve the same purpose, and you just want them all to spawn equally for diversity sake.")] + public SpawnSyncedObject Spawn; + [Tooltip("The unique id for this script's cycle.\n\nWhen the dungeon generation finishes, a random cycle value is calculated for each Id. Each script will reference their Id's corresponding cycle value to determine their Prop, and advance the cycle value by 1.")] + public int Id; + [Tooltip("The list of props that would selected based on a cycle.")] + public List Props = new List(); + + void Reset(){ + Spawn = GetComponent(); + } + + public static void UpdateCycle(int value){ + Plugin.logger.LogInfo($"Updating SpawnSyncedObject start cycle to {value}"); + cycle = value; + cycleDictionary = new Dictionary(); + } + + public int GetCycle(int id){ + if (!cycleDictionary.TryGetValue(id, out var value)){ + value = cycle; + cycleDictionary.Add(id, value); + } + + cycleDictionary[id] = value + 1; + Plugin.logger.LogInfo($"Cycle{id}: {value}"); + return value; + } + + public void OnDungeonComplete(Dungeon dungeon) { + var index = GetCycle(Id) % Props.Count; + var prefab = Props[index]; + Spawn.spawnPrefab = prefab; + } + } +} diff --git a/DunGenPlus/DunGenPlus/DunGenExtender.cs b/DunGenPlus/DunGenPlus/DunGenExtender.cs new file mode 100644 index 0000000..43903bb --- /dev/null +++ b/DunGenPlus/DunGenPlus/DunGenExtender.cs @@ -0,0 +1,22 @@ +using DunGen; +using DunGen.Graph; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGenPlus.Collections; + +namespace DunGenPlus { + + [CreateAssetMenu(fileName = "DunGenExtender", menuName = "DunGenExtender", order = 1)] + public class DunGenExtender : ScriptableObject { + + [Tooltip("DunGenExtender will only influence this DungeonFlow")] + public DungeonFlow DungeonFlow; + public DunGenExtenderProperties Properties; + public DunGenExtenderEvents Events; + + } +} diff --git a/DunGenPlus/DunGenPlus/DunGenPlus.csproj b/DunGenPlus/DunGenPlus/DunGenPlus.csproj new file mode 100644 index 0000000..b1a1465 --- /dev/null +++ b/DunGenPlus/DunGenPlus/DunGenPlus.csproj @@ -0,0 +1,119 @@ + + + + + Debug + AnyCPU + {13CDE60E-1975-463B-9DA1-CCB3F3EBABD8} + Library + Properties + DunGenPlus + DunGenPlus + v4.8 + 512 + true + + + true + embedded + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\Libraries\0Harmony.dll + + + ..\..\..\Libraries\Assembly-CSharp-firstpass.dll + + + ..\..\..\Libraries\Assembly-CSharp-publicized.dll + + + ..\..\..\Libraries\BepInEx.dll + + + ..\..\..\Libraries\BepInEx.Harmony.dll + + + ..\..\..\Libraries\LethalLevelLoader.dll + + + + + + + + + + + ..\..\..\Libraries\Unity.Collections.dll + + + False + ..\..\..\Libraries\Unity.RenderPipelines.Core.Runtime.dll + + + False + ..\..\..\Libraries\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + False + ..\..\..\Libraries\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + ..\..\..\Libraries\UnityEngine.dll + + + False + ..\..\..\Libraries\UnityEngine.AssetBundleModule.dll + + + ..\..\..\Libraries\UnityEngine.CoreModule.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy "$(TargetPath)" "C:\Users\Jose Garcia\AppData\Roaming\r2modmanPlus-local\LethalCompany\profiles\SDM Debug\BepInEx\plugins\Unknown-DunGenPlus\$(TargetName).dll" + + \ No newline at end of file diff --git a/DunGenPlus/DunGenPlus/Generation/DoorwaySistersRule.cs b/DunGenPlus/DunGenPlus/Generation/DoorwaySistersRule.cs new file mode 100644 index 0000000..e59815f --- /dev/null +++ b/DunGenPlus/DunGenPlus/Generation/DoorwaySistersRule.cs @@ -0,0 +1,90 @@ +using DunGen; +using DunGenPlus.Components; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DunGenPlus.Generation { + + internal static class DoorwaySistersRule { + + public class Data { + public DoorwaySisters info; + public List proxies; + } + + public static Dictionary doorwayDictionary; + public static Dictionary doorwayProxyDictionary; + + public static void UpdateCache(IEnumerable list){ + if (!DunGenPlusGenerator.Active || !DunGenPlusGenerator.Properties.UseDoorwaySisters) return; + + Plugin.logger.LogInfo("Updating DoorwayProxy cache for DoorwaySistersRule"); + doorwayDictionary = new Dictionary(); + doorwayProxyDictionary = new Dictionary(); + + foreach(var a in list){ + + var doorway = a.DoorwayComponent; + if (doorwayDictionary.TryGetValue(doorway, out var data)){ + + data.proxies.Add(a); + doorwayProxyDictionary.Add(a, data); + + } else { + + var proxies = new List(); + proxies.Add(a); + var item = new Data { + info = doorway.GetComponent(), + proxies = proxies + }; + + doorwayProxyDictionary.Add(a, item); + doorwayDictionary.Add(a.DoorwayComponent, item); + + } + } + } + + public static bool CanDoorwaysConnect(bool result, TileProxy tileA, TileProxy tileB, DoorwayProxy doorwayA, DoorwayProxy doorwayB){ + + if (!result) return false; + if (!DunGenPlusGenerator.Active || !DunGenPlusGenerator.Properties.UseDoorwaySisters) return true; + + var infoA = doorwayProxyDictionary[doorwayA].info; + var infoB = doorwayProxyDictionary[doorwayB].info; + + // deny if any sister doorway is already in use + // cause it feels like dumb otherwise + if (CheckIfSisterActive(infoA, tileB)){ + return false; + } + + if (CheckIfSisterActive(infoB, tileA)){ + return false; + } + + // allow like normal + return true; + } + + public static bool CheckIfSisterActive(DoorwaySisters info, TileProxy targetTile){ + if (info == null || info.sisters == null) return false; + + foreach(var sis in info.sisters){ + var proxies = doorwayDictionary[sis].proxies; + foreach(var proxy in proxies){ + var result = proxy.ConnectedDoorway != null && proxy.ConnectedDoorway.TileProxy == targetTile; + if (result) return true; + } + } + + return false; + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs b/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs new file mode 100644 index 0000000..dc2dc16 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs @@ -0,0 +1,346 @@ +using DunGen.Graph; +using DunGen; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Collections; +using UnityEngine; +using DunGenPlus.Collections; +using DunGenPlus.Components; +using System.Security.Permissions; +using DunGenPlus.Managers; +using UnityEngine.Rendering; +using UnityEngine.Rendering.HighDefinition; +using BepInEx.Logging; +using static UnityEngine.Rendering.HighDefinition.ScalableSettingLevelParameter; + +[assembly: SecurityPermission( SecurityAction.RequestMinimum, SkipVerification = true )] +namespace DunGenPlus.Generation { + internal class DunGenPlusGenerator { + public static DunGenExtender Instance { get; internal set; } + public static DunGenExtenderProperties Properties { get; internal set; } + public static bool Active { get; internal set; } + public static bool ActiveAlternative { get; internal set; } + + internal static HDRenderPipelineAsset previousHDRPAsset; + internal static HDRenderPipelineAsset newHDRPAsset; + + public static void Activate(DungeonGenerator generator, DunGenExtender extender){ + Instance = extender; + Active = true; + ActiveAlternative = true; + + var props = extender.Properties.Copy(); + Instance.Events.OnModifyDunGenExtenderProperties.Invoke(props); + props.SetupProperties(generator); + Properties = props; + + if (Properties.UseDungeonBounds) { + generator.RestrictDungeonToBounds = Properties.UseDungeonBounds; + var bounds = Properties.GetDungeonBounds(generator.LengthMultiplier); + generator.TilePlacementBounds = bounds; + Plugin.logger.LogInfo($"Dungeon Bounds: {bounds}"); + } + + if (Properties.UseMaxShadowsRequestUpdate) { + Plugin.logger.LogInfo($"Updating HDRP asset: setting max shadows request to {Properties.MaxShadowsRequestAmount}"); + try { + previousHDRPAsset = QualitySettings.renderPipeline as HDRenderPipelineAsset; + newHDRPAsset = ScriptableObject.Instantiate(previousHDRPAsset); + + var settings = newHDRPAsset.currentPlatformRenderPipelineSettings; + settings.hdShadowInitParams.maxScreenSpaceShadowSlots = Properties.MaxShadowsRequestAmount; + newHDRPAsset.currentPlatformRenderPipelineSettings = settings; + + QualitySettings.renderPipeline = newHDRPAsset; + } catch (Exception e) { + Plugin.logger.LogError("Failed to update HDRP asset"); + Plugin.logger.LogError(e.ToString()); + } + } + + + DoorwayManager.ResetList(); + } + + public static void Deactivate(){ + Instance = null; + Properties = null; + Active = false; + ActiveAlternative = false; + + if (previousHDRPAsset && QualitySettings.renderPipeline == newHDRPAsset) { + Plugin.logger.LogInfo("Restoring original HDRP asset"); + + QualitySettings.renderPipeline = previousHDRPAsset; + previousHDRPAsset = null; + newHDRPAsset = null; + } + } + + public static IEnumerator GenerateAlternativeMainPaths(DungeonGenerator gen) { + + var altCount = Properties.MainPathCount - 1; + + // default behaviour in case the multiple main paths are not considered + if (!Active) { + ActiveAlternative = false; + yield return gen.Wait(gen.GenerateBranchPaths()); + ActiveAlternative = true; + yield break; + } + + if (altCount <= 0) { + Plugin.logger.LogInfo($"Switching to default dungeon branch generation due to MainPathCount being {altCount + 1}"); + ActiveAlternative = false; + yield return gen.Wait(gen.GenerateBranchPaths()); + ActiveAlternative = true; + yield break; + } + + if (Properties.MainRoomTilePrefab == null) { + Plugin.logger.LogWarning($"Switching to default dungeon branch generation due to MainRoomTilePrefab being null"); + ActiveAlternative = false; + yield return gen.Wait(gen.GenerateBranchPaths()); + ActiveAlternative = true; + yield break; + } + + var allMainPathTiles = new List>(); + allMainPathTiles.Add(gen.proxyDungeon.MainPathTiles.ToList()); + + // main room is the true main room and not the fake room + // this MUST have multiple doorways as you can imagine + var mainRoom = gen.proxyDungeon.MainPathTiles.FirstOrDefault(t => t.Prefab == Properties.MainRoomTilePrefab); + if (mainRoom == null) { + Plugin.logger.LogWarning($"Switching to default dungeon branch generation due to MainRoomTilePrefab not spawning on the main path"); + ActiveAlternative = false; + yield return gen.Wait(gen.GenerateBranchPaths()); + ActiveAlternative = true; + yield break; + } + + var doorwayGroups = mainRoom.Prefab.GetComponentInChildren(); + + // index of MaxValue is how we tell which doorway proxy is fake + var fakeDoorwayProxy = new DoorwayProxy(mainRoom, int.MaxValue, mainRoom.doorways[0].DoorwayComponent, Vector3.zero, Quaternion.identity); + + // nodes + var nodesSorted = gen.DungeonFlow.Nodes.OrderBy(n => n.Position).ToList(); + var startingNodeIndex = nodesSorted.FindIndex(n => n.TileSets.SelectMany(t => t.TileWeights.Weights).Any(t => t.Value == Properties.MainRoomTilePrefab)); + if (startingNodeIndex == -1) { + Plugin.logger.LogWarning($"Switching to default dungeon branch generation due to MainRoomTilePrefab not existing in the Nodes' tilesets"); + ActiveAlternative = false; + yield return gen.Wait(gen.GenerateBranchPaths()); + ActiveAlternative = true; + yield break; + } + + //FixDoorwaysToAllFloors(mainRoom, doorwayGroups); + + gen.ChangeStatus(GenerationStatus.MainPath); + + for (var b = 0; b < altCount; ++b) { + RandomizeLineArchetypes(gen, true); + var previousTile = mainRoom; + var targetLength = Mathf.RoundToInt(gen.DungeonFlow.Length.GetRandom(gen.RandomStream) * gen.LengthMultiplier); + var archetypes = new List(targetLength); + + var newMainPathTiles = new List(); + newMainPathTiles.Add(mainRoom); + + var nodes = nodesSorted.Skip(startingNodeIndex + 1); + var nodesVisited = new List(nodes.Count()); + + // most of this code is a mix of the GenerateMainPath() + // and GenerateBranch() code + for(var t = 1; t < targetLength; ++t){ + var lineDepthRatio = Mathf.Clamp01((float)t / (targetLength - 1)); + var lineAtDepth = gen.DungeonFlow.GetLineAtDepth(lineDepthRatio); + if (lineAtDepth == null){ + yield return gen.Wait(gen.InnerGenerate(true)); + yield break; + } + + if (lineAtDepth != gen.previousLineSegment){ + gen.currentArchetype = lineAtDepth.GetRandomArchetype(gen.RandomStream, archetypes); + gen.previousLineSegment = lineAtDepth; + } + + // terrible solution but FUCK it + // and yet it worked + // this is how my last node cannot be a target of pruning + GraphNode graphNode = null; + DungeonArchetype archetype = null; + foreach(var g in nodes) { + if (lineDepthRatio >= g.Position && !nodesVisited.Contains(g)) { + graphNode = g; + nodesVisited.Add(g); + break; + } + } + + List useableTileSets; + if (graphNode != null) { + archetype = ModifyMainBranchNodeArchetype(null, graphNode, gen.RandomStream); + useableTileSets = graphNode.TileSets; + } else { + archetype = gen.currentArchetype; + useableTileSets = archetype.TileSets; + } + + // places fake doorways at the first node + if (doorwayGroups && t == 1){ + foreach(var d in mainRoom.UsedDoorways) { + if (d.ConnectedDoorway.Index != int.MaxValue) { + var groups = doorwayGroups.GrabDoorwayGroup(d.DoorwayComponent); + if (groups == null) continue; + + foreach(var doorway in mainRoom.UnusedDoorways){ + if (groups.Contains(doorway.DoorwayComponent)){ + doorway.ConnectedDoorway = fakeDoorwayProxy; + } + } + } + } + } + + var tileProxy = gen.AddTile(previousTile, useableTileSets, lineDepthRatio, archetype, TilePlacementResult.None); + + if (tileProxy == null) { + Plugin.logger.LogInfo($"Alt. main branch gen failed at {b}:{lineDepthRatio}"); + yield return gen.Wait(gen.InnerGenerate(true)); + yield break; + } + + if (lineDepthRatio >= 1f){ + Plugin.logger.LogInfo($"Alt. main branch at {b} ended with {tileProxy.PrefabTile.name}"); + } + + tileProxy.Placement.BranchDepth = t; + tileProxy.Placement.NormalizedBranchDepth = lineDepthRatio; + + if (graphNode != null) { + tileProxy.Placement.GraphNode = graphNode; + tileProxy.Placement.GraphLine = null; + } else { + tileProxy.Placement.GraphNode = null; + tileProxy.Placement.GraphLine = lineAtDepth; + } + + previousTile = tileProxy; + newMainPathTiles.Add(tileProxy); + + if (gen.ShouldSkipFrame(true)) yield return gen.GetRoomPause(); + } + + allMainPathTiles.Add(newMainPathTiles); + + } + + // okay lets fix the fakes + foreach(var doorway in mainRoom.UsedDoorways){ + if (doorway.ConnectedDoorway.Index == int.MaxValue) { + doorway.ConnectedDoorway = null; + } + } + + ActiveAlternative = false; + Plugin.logger.LogInfo($"Created {altCount} alt. paths, creating branches now"); + gen.ChangeStatus(GenerationStatus.Branching); + + // this is major trickery and it works still + for(var b = 0; b < altCount + 1; ++b){ + Plugin.logger.LogInfo($"Branch {b}"); + RandomizeLineArchetypes(gen, false); + gen.proxyDungeon.MainPathTiles = allMainPathTiles[b]; + yield return gen.Wait(gen.GenerateBranchPaths()); + } + + ActiveAlternative = true; + + gen.proxyDungeon.MainPathTiles = allMainPathTiles[0]; + } + + public static void RandomizeLineArchetypes(DungeonGenerator gen, bool randomizeMainPath){ + if (!Properties.UseLineRandomizer) return; + + var flow = Instance.DungeonFlow; + var lines = flow.Lines; + var tilesetsUsed = new Dictionary(); + foreach(var t in Properties.LineRandomizerTileSets){ + tilesetsUsed.Add(t, 0); + } + + foreach(var a in Properties.LineRandomizerArchetypes) { + var tiles = randomizeMainPath ? a.TileSets : a.BranchCapTileSets; + RandomizeArchetype(gen, tiles, tilesetsUsed); + } + } + + public static void RandomizeArchetype(DungeonGenerator gen, List targetTileSet, Dictionary tilesetsUsed){ + // get 3 random + var newTiles = Properties.LineRandomizerTileSets + .OrderBy(t => tilesetsUsed[t] + gen.RandomStream.NextDouble()) + .Take(Properties.LineRandomizerTakeCount); + + var i = targetTileSet.Count - 1; + foreach(var n in newTiles){ + targetTileSet[i] = n; + --i; + + tilesetsUsed[n] += 1; + } + } + + public static DungeonArchetype ModifyMainBranchNodeArchetype(DungeonArchetype archetype, GraphNode node, RandomStream randomStream){ + if (!DunGenPlusGenerator.Active) return archetype; + + if (Properties.AddArchetypesToNormalNodes && node.NodeType == NodeType.Normal) { + return Properties.GetRandomArchetype(node.Label, randomStream);; + } + return archetype; + } + + public static void FixDoorwaysToAllFloors(TileProxy mainRoom, MainRoomDoorwayGroups doorwayGroups) { + var first = doorwayGroups.doorwayListFirst; + if (first == null) return; + + foreach(var target in mainRoom.UsedDoorways){ + if (target.ConnectedDoorway.Index == int.MaxValue && !first.Contains(target.DoorwayComponent)) { + target.ConnectedDoorway = null; + } + } + } + + /* + public static GraphNode ModifyGraphNode(GraphNode node) { + if (!Patch.active) return node; + + if (node.Label == "Hallway Entrance 1") { + return Assets.networkObjectList.gardenEntranceGraphNode; + } + return node; + } + + public static TileProxy FixTilesToAllFloors(TileProxy mainTile) { + if (!Patch.active) return mainTile; + + var groups = mainTile.Prefab.GetComponentInChildren(); + var first = groups.groupFirst; + + foreach(var target in mainTile.doorways){ + if (target.ConnectedDoorway != null && target.ConnectedDoorway.Index == int.MaxValue && !first.Contains(target.DoorwayComponent)) { + target.ConnectedDoorway = null; + } + } + + return mainTile; + } + */ + + } +} diff --git a/DunGenPlus/DunGenPlus/Managers/DoorwayManager.cs b/DunGenPlus/DunGenPlus/Managers/DoorwayManager.cs new file mode 100644 index 0000000..5ee59f9 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Managers/DoorwayManager.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen.Adapters; +using DunGenPlus.Components; +using DunGenPlus.Generation; +using DunGenPlus.Utils; + +namespace DunGenPlus.Managers { + public static class DoorwayManager { + + public static ActionList onMainEntranceTeleportSpawnedEvent = new ActionList("onMainEntranceTeleportSpawned"); + public static List doorwayCleanupList; + + public static void ResetList(){ + doorwayCleanupList = new List(); + } + + public static void AddDoorwayCleanup(DoorwayCleanup cleanup){ + doorwayCleanupList.Add(cleanup); + } + + public static void onMainEntranceTeleportSpawnedFunction(){ + if (DunGenPlusGenerator.Active) { + foreach(var d in doorwayCleanupList){ + d.SetBlockers(false); + d.Cleanup(); + } + + try{ + var dungeonGen = RoundManager.Instance.dungeonGenerator; + var navmesh = dungeonGen.transform.parent.GetComponentInChildren(); + navmesh.Run(dungeonGen.Generator); + Plugin.logger.LogInfo("Rebuild nav mesh"); + } catch (Exception e){ + Plugin.logger.LogError("Failed to rebuild nav mesh"); + Plugin.logger.LogError(e.ToString()); + } + + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Patches/DoorwayConnectionPatch.cs b/DunGenPlus/DunGenPlus/Patches/DoorwayConnectionPatch.cs new file mode 100644 index 0000000..602c7fc --- /dev/null +++ b/DunGenPlus/DunGenPlus/Patches/DoorwayConnectionPatch.cs @@ -0,0 +1,63 @@ +using DunGen; +using DunGenPlus.Utils; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using DunGenPlus.Generation; + +namespace DunGenPlus.Patches { + internal class DoorwayConnectionPatch { + + [HarmonyPatch(typeof(DungeonProxy), "ConnectOverlappingDoorways")] + [HarmonyPrefix] + public static void ConnectOverlappingDoorwaysPrePatch(ref DungeonProxy __instance){ + var enumerable = __instance.AllTiles.SelectMany(t => t.Doorways); + DoorwaySistersRule.UpdateCache(enumerable); + } + + + [HarmonyTranspiler] + [HarmonyPatch(typeof(DungeonProxy), "ConnectOverlappingDoorways")] + public static IEnumerable ConnectOverlappingDoorwaysPatch(IEnumerable instructions){ + var callFunction = typeof(DunGen.Graph.DungeonFlow).GetMethod("CanDoorwaysConnect", BindingFlags.Instance | BindingFlags.Public); + + var sequence = new InstructionSequence("doorway connect", false); + sequence.AddBasic(OpCodes.Callvirt, callFunction); + sequence.AddBasic(OpCodes.Brfalse); + + foreach(var instruction in instructions){ + + if (sequence.VerifyStage(instruction)){ + + var method = typeof(DoorwaySistersRule).GetMethod("CanDoorwaysConnect", BindingFlags.Static | BindingFlags.Public); + var getTileProxy = typeof(DoorwayProxy).GetMethod("get_TileProxy", BindingFlags.Instance | BindingFlags.Public); + + yield return new CodeInstruction(OpCodes.Ldloc_2); + yield return new CodeInstruction(OpCodes.Callvirt, getTileProxy); + yield return new CodeInstruction(OpCodes.Ldloc_S, 4); + yield return new CodeInstruction(OpCodes.Callvirt, getTileProxy); + + yield return new CodeInstruction(OpCodes.Ldloc_2); + yield return new CodeInstruction(OpCodes.Ldloc_S, 4); + + yield return new CodeInstruction(OpCodes.Call, method); + + yield return instruction; + + continue; + } + + yield return instruction; + } + + sequence.ReportComplete(); + } + + + } +} diff --git a/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs b/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs new file mode 100644 index 0000000..14e445d --- /dev/null +++ b/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs @@ -0,0 +1,162 @@ +using DunGen; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Collections; +using DunGenPlus.Utils; +using DunGenPlus.Generation; +using DunGenPlus.Managers; + +namespace DunGenPlus.Patches { + internal class DungeonGeneratorPatch { + + [HarmonyPostfix] + [HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath")] + public static void GenerateMainPathPatch(ref DungeonGenerator __instance, ref IEnumerator __result){ + if (DunGenPlusGenerator.Active && DunGenPlusGenerator.ActiveAlternative) { + DunGenPlusGenerator.RandomizeLineArchetypes(__instance, true); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(DungeonGenerator), "GenerateBranchPaths")] + public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance, ref IEnumerator __result){ + if (DunGenPlusGenerator.Active && DunGenPlusGenerator.ActiveAlternative) { + __result = DunGenPlusGenerator.GenerateAlternativeMainPaths(__instance); + } + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath", MethodType.Enumerator)] + public static IEnumerable GenerateMainPathPatch(IEnumerable instructions){ + + var addArchFunction = typeof(List).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + + var archSequence = new InstructionSequence("archetype node"); + archSequence.AddOperandTypeCheck(OpCodes.Ldfld, typeof(List)); + archSequence.AddBasic(OpCodes.Ldnull); + archSequence.AddBasic(OpCodes.Callvirt, addArchFunction); + + foreach(var instruction in instructions){ + + if (archSequence.VerifyStage(instruction)){ + + var randomStreamMethod = typeof(DungeonGenerator).GetMethod("get_RandomStream", BindingFlags.Public | BindingFlags.Instance); + var modifyMethod = typeof(DunGenPlusGenerator).GetMethod("ModifyMainBranchNodeArchetype", BindingFlags.Public | BindingFlags.Static); + + yield return new CodeInstruction(OpCodes.Ldloc_S, 8); + yield return new CodeInstruction(OpCodes.Ldloc_1); + yield return new CodeInstruction(OpCodes.Call, randomStreamMethod); + yield return new CodeInstruction(OpCodes.Call, modifyMethod); + yield return instruction; + + continue; + } + + yield return instruction; + } + + archSequence.ReportComplete(); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(RoundManager), "FinishGeneratingLevel")] + public static void GenerateBranchPathsPatch(){ + if (DunGenPlusGenerator.Active) { + Plugin.logger.LogInfo("Alt. InnerGenerate() function complete"); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(RoundManager), "SetPowerOffAtStart")] + public static void SetPowerOffAtStartPatch(){ + DoorwayManager.onMainEntranceTeleportSpawnedEvent.Call(); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(DungeonGenerator), "PostProcess")] + public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance){ + if (DunGenPlusGenerator.Active) { + var value = __instance.RandomStream.Next(999); + Components.Props.SpawnSyncedObjectCycle.UpdateCycle(value); + } + } + + /* + [HarmonyTranspiler] + [HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath", MethodType.Enumerator)] + public static IEnumerable GenerateMainPathPatch(IEnumerable instructions){ + + var addArchFunction = typeof(List).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + //var addNodeFunction = typeof(List).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + + var archSequence = new InstructionSequence("archetype node"); + archSequence.AddOperandTypeCheck(OpCodes.Ldfld, typeof(List)); + archSequence.AddBasic(OpCodes.Ldnull); + archSequence.AddBasic(OpCodes.Callvirt, addArchFunction); + + var nodeSequence = new InstructionSequence("graph node"); + nodeSequence.AddBasicLocal(OpCodes.Ldloc_S, 12); + nodeSequence.AddBasicLocal(OpCodes.Stloc_S, 8); + + var limitSequence = new InstructionSequence("limit nodes"); + limitSequence.AddBasic(OpCodes.Ldnull); + limitSequence.AddBasicLocal(OpCodes.Stloc_S, 13); + limitSequence.AddBasic(OpCodes.Ldloc_1); + limitSequence.AddBasicLocal(OpCodes.Ldloc_S, 13); + + foreach(var instruction in instructions){ + + if (archSequence.VerifyStage(instruction)){ + + var method = typeof(GeneratePath).GetMethod("ModifyMainBranchNodeArchetype", BindingFlags.Public | BindingFlags.Static); + + yield return new CodeInstruction(OpCodes.Ldloc_S, 8); + yield return new CodeInstruction(OpCodes.Call, method); + yield return instruction; + + continue; + } + + + if (nodeSequence.VerifyStage(instruction)){ + + var method = typeof(GeneratePath).GetMethod("ModifyGraphNode", BindingFlags.Public | BindingFlags.Static); + + yield return new CodeInstruction(OpCodes.Call, method); + yield return instruction; + + continue; + } + + if (limitSequence.VerifyStage(instruction)){ + + var method = typeof(GeneratePath).GetMethod("LimitTilesToFirstFloor", BindingFlags.Public | BindingFlags.Static); + var field = typeof(DungeonGenerator).Assembly.GetType("DunGen.DungeonGenerator+d__100").GetField("5__8", BindingFlags.NonPublic | BindingFlags.Instance); + + yield return instruction; + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Ldfld, field); + + + yield return new CodeInstruction(OpCodes.Call, method); + + continue; + } + + yield return instruction; + } + + archSequence.ReportComplete(); + nodeSequence.ReportComplete(); + limitSequence.ReportComplete(); + } + */ + + } +} diff --git a/DunGenPlus/DunGenPlus/Plugin.cs b/DunGenPlus/DunGenPlus/Plugin.cs new file mode 100644 index 0000000..cb761d3 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Plugin.cs @@ -0,0 +1,67 @@ +using BepInEx; +using BepInEx.Logging; +using DunGen; +using DunGen.Graph; +using DunGenPlus.Generation; +using DunGenPlus.Managers; +using DunGenPlus.Patches; +using HarmonyLib; +using LethalLevelLoader; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Assertions; + +namespace DunGenPlus { + + [BepInPlugin(modGUID, modName, modVersion)] + [BepInProcess("Lethal Company.exe")] + public class Plugin : BaseUnityPlugin { + + internal const string modGUID = "ImoutoSama.DungeonGenerationPlus"; + private const string modName = "Dungeon Generation Plus"; + private const string modVersion = "1.0.0"; + + internal readonly Harmony Harmony = new Harmony(modGUID); + + internal static Plugin Instance {get; private set;} + + internal static ManualLogSource logger { get; private set; } + + internal static Dictionary DunGenExtenders = new Dictionary(); + + void Awake() { + if (Instance == null) Instance = this; + + logger = BepInEx.Logging.Logger.CreateLogSource(modGUID); + logger.LogInfo($"Plugin {modName} has been added!"); + + Harmony.PatchAll(typeof(DungeonGeneratorPatch)); + Harmony.PatchAll(typeof(DoorwayConnectionPatch)); + + Assets.LoadAssets(); + DungeonManager.GlobalDungeonEvents.onBeforeDungeonGenerate.AddListener(OnDunGenExtenderLoad); + DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.onMainEntranceTeleportSpawnedFunction); + } + + void OnDunGenExtenderLoad(RoundManager roundManager) { + DunGenPlusGenerator.Deactivate(); + + var generator = roundManager.dungeonGenerator.Generator; + var flow = generator.DungeonFlow; + if (DunGenExtenders.TryGetValue(flow, out var value)) { + Plugin.logger.LogInfo($"Loading DunGenExtender for {flow.name}"); + DunGenPlusGenerator.Activate(generator, value); + return; + } + + Plugin.logger.LogInfo($"Did not load a DunGenExtenderer"); + DunGenPlusGenerator.Deactivate(); + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Properties/AssemblyInfo.cs b/DunGenPlus/DunGenPlus/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fa6c9c1 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下を通して制御されます +// 制御されます。アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("DunGenPlus")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DunGenPlus")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("13cde60e-1975-463b-9da1-ccb3f3ebabd8")] + +// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// リビジョン +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DunGenPlus/DunGenPlus/Utils/TranspilerUtilities.cs b/DunGenPlus/DunGenPlus/Utils/TranspilerUtilities.cs new file mode 100644 index 0000000..0f29595 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Utils/TranspilerUtilities.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using HarmonyLib; +using GameNetcodeStuff; +using System.Reflection; +using System.Reflection.Emit; +using BepInEx.Logging; +using UnityEngine; +using DunGenPlus; + +namespace DunGenPlus.Utils { + + internal class InjectionDictionary { + + public string name; + public List instructions; + public CodeInstruction[] injections; + + int counter; + + public InjectionDictionary(string name, MethodInfo methodInjection, params CodeInstruction[] instructions) { + this.name = name; + this.injections = new CodeInstruction[] { new CodeInstruction(OpCodes.Call, methodInjection) } ; + this.instructions = instructions.ToList(); + } + + public InjectionDictionary(string name, CodeInstruction[] codeInjections, params CodeInstruction[] instructions) { + this.name = name; + this.injections = codeInjections; + this.instructions = instructions.ToList(); + } + + public void ResetCounter(){ + counter = 0; + } + + public void AddCounter() { + counter++; + } + + public void Report(string debugFunction, int? expectedCounter){ + if (counter == 0) { + Plugin.logger.LogError($"{debugFunction} could not inject {name}. Probably scary"); + } else if (!expectedCounter.HasValue) { + Plugin.logger.LogInfo($"{debugFunction} inject {name} {counter} time(s)"); + } else if (expectedCounter.Value != counter){ + Plugin.logger.LogWarning($"{debugFunction} inject {name} {counter} time(s) (Expected {expectedCounter.Value}). Probably not an error but be warned"); + } + } + + } + + internal class InstructionSequence { + + public static ManualLogSource logger => Plugin.logger; + + List> seq; + string name; + string extraErrorMessage; + int stage = 0; + bool completed = false; + bool single; + + public InstructionSequence(string name, bool single = true, string extraErrorMessage = default(string)){ + this.name = name; + this.single = single; + this.extraErrorMessage = extraErrorMessage; + seq = new List>(); + } + + public void Add(Func next){ + seq.Add(next); + } + + public void AddBasic(OpCode opcode){ + seq.Add((i) => i.opcode == opcode); + } + + public void AddBasic(OpCode opcode, object operand){ + seq.Add((i) => i.opcode == opcode && i.operand == operand); + } + + public void AddBasicLocal(OpCode opcode, int operand){ + seq.Add((i) => i.opcode == opcode && (i.operand as LocalBuilder).LocalIndex == operand); + } + + public void AddOperandTypeCheck(OpCode opcode, Type operandType){ + seq.Add((i) => { + var fieldInfo = i.operand as FieldInfo; + if (i.opcode == opcode && fieldInfo != null) { + return fieldInfo.FieldType == operandType; + } + return false; + }); + } + + + public void AddBasicWithAlternateMethodName(OpCode opcode, object operand, string methodName){ + seq.Add((i) => { + if (i.opcode == opcode && i.operand == operand) return true; + + var mth = i.operand as MethodInfo; + if (mth != null && mth.Name == methodName) return true; + + return false; + }); + + } + + public void AddSpecial(OpCode opcode, Func extra){ + seq.Add((i) => i.opcode == opcode && extra.Invoke(i)); + } + + public void AddQuickInjection(MethodInfo methodInfo){ + + } + + public bool VerifyStage(CodeInstruction current){ + var s = seq[stage]; + if (s.Invoke(current)) { + //Plugin.logger.LogInfo($"{name}({stage}): current.ToString()"); + stage++; + } else { + stage = 0; + } + + if (stage >= seq.Count){ + + if (completed && single){ + throw new Exception($"Found multiple valid {name} instructions"); + } + + stage = 0; + completed = true; + return true; + } + + return false; + } + + public void ReportComplete(){ + if (completed == false){ + var errorM = string.IsNullOrWhiteSpace(extraErrorMessage) ? "BIG PROBLEM!" : extraErrorMessage; + logger.LogError($"HarmonyTranspiler for {name} has failed. {errorM}"); + } + } + + } + + internal class TranspilerUtilities { + + public static IEnumerable InjectMethod(IEnumerable instructions, InjectionDictionary injection, string debugFunction, int? expectedCounter = default){ + var targets = injection.instructions; + var codeInjections = injection.injections; + injection.ResetCounter(); + + foreach(var i in instructions){ + foreach(var t in targets){ + if (i.opcode == t.opcode && i.operand == t.operand){ + yield return i; + foreach(var c in codeInjections) yield return c; + injection.AddCounter(); + goto GoNext; + } + } + yield return i; + + GoNext:; + } + + injection.Report(debugFunction, expectedCounter); + } + + public static bool IsInstructionNearFloatValue(CodeInstruction instruction, float value){ + return Mathf.Abs((float)instruction.operand - value) < 0.1f; + } + + public static void PrintInstructions(IEnumerable instructions) { + foreach(var i in instructions){ + var opString = i.opcode.ToString(); + var objString = i.operand != null ? i.operand.ToString() : "NULL"; + Plugin.logger.LogInfo($"{opString}: {objString}"); + } + } + + } +} diff --git a/DunGenPlus/DunGenPlus/Utils/Utility.cs b/DunGenPlus/DunGenPlus/Utils/Utility.cs new file mode 100644 index 0000000..8657432 --- /dev/null +++ b/DunGenPlus/DunGenPlus/Utils/Utility.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DunGenPlus.Utils { + public class ActionList { + public string name; + public List<(string name, Action action)> actionList; + + public ActionList(string name){ + this.name = name; + actionList = new List<(string, Action)>(); + } + + public void AddEvent(string name, Action act){ + actionList.Add((name, act)); + } + + public void Call(){ + foreach(var pair in actionList){ + try { + pair.action.Invoke(); + } catch (Exception e) { + Plugin.logger.LogError($"Error with event {name}/{pair.name}"); + Plugin.logger.LogError(e.ToString()); + } + } + } + } + +} diff --git a/LICENSE b/LICENSE index baee873..835a683 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Creative Commons Attribution-ShareAlike 4.0 International Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. @@ -8,11 +8,13 @@ Creative Commons public licenses provide a standard set of terms and conditions Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. -Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. +Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. -Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License +Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. -By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. +Creative Commons Attribution-ShareAlike 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 – Definitions. @@ -20,7 +22,7 @@ Section 1 – Definitions. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. - c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. + c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. @@ -28,7 +30,7 @@ Section 1 – Definitions. f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. - g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. + g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. @@ -36,13 +38,11 @@ Section 1 – Definitions. j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. - k. NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. + k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. - l. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. + l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. - m. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. - - n. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. + m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 – Scope. @@ -50,9 +50,9 @@ Section 2 – Scope. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: - A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and + A. reproduce and Share the Licensed Material, in whole or in part; and - B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. + B. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. @@ -76,7 +76,7 @@ Section 2 – Scope. 2. Patent and trademark rights are not licensed under this Public License. - 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. + 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 – License Conditions. @@ -108,7 +108,7 @@ Your exercise of the Licensed Rights is expressly made subject to the following b. ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. - 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. + 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. @@ -118,7 +118,7 @@ Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: - a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; + a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and @@ -139,15 +139,15 @@ Section 6 – Term and Termination. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: - 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or + 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or - 2. upon express reinstatement by the Licensor. + 2. upon express reinstatement by the Licensor. - For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. + c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. - c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. + d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 – Other Terms and Conditions.