From 383c879b96df5996913bcd6e148ee97e50b1ecbd Mon Sep 17 00:00:00 2001 From: LadyAliceMargatroid Date: Mon, 5 Aug 2024 13:58:33 -0700 Subject: [PATCH] Added forced tile generation Changed RandomGuaranteedScrapSpawn to use the item's weight Added some tooltips/XML --- DunGenPlus/DunGenPlus/API.cs | 32 +++++++++++++++ .../Collections/DunGenExtenderEvents.cs | 9 +++++ .../Collections/DunGenExtenderProperties.cs | 28 ++++++++++--- .../DunGenPlus/Collections/ExtenderEvent.cs | 12 ++++++ .../Collections/ForcedTileSetList.cs | 23 +++++++++++ .../Props/SpawnSyncedObjectCycle.cs | 8 ++-- .../Scrap/RandomGuaranteedScrapSpawn.cs | 32 ++++++++++----- DunGenPlus/DunGenPlus/DunGenPlus.csproj | 1 + .../Generation/DunGenPlusGenerator.cs | 39 +++++++++++++++++++ .../Patches/DungeonGeneratorPatch.cs | 4 +- .../DunGenPlus/Patches/RoundManagerPatch.cs | 2 +- DunGenPlus/DunGenPlus/Plugin.cs | 2 +- 12 files changed, 171 insertions(+), 21 deletions(-) create mode 100644 DunGenPlus/DunGenPlus/Collections/ForcedTileSetList.cs diff --git a/DunGenPlus/DunGenPlus/API.cs b/DunGenPlus/DunGenPlus/API.cs index 20b5d98..a3a00c1 100644 --- a/DunGenPlus/DunGenPlus/API.cs +++ b/DunGenPlus/DunGenPlus/API.cs @@ -11,6 +11,16 @@ namespace DunGenPlus { public class API { + /// + /// Registers the to recieve the alternate dungeon generation changes defined by . + /// + /// + /// + /// + /// + /// if was successfully added. + /// if was null or already has a registered . + /// public static bool AddDunGenExtender(DungeonFlow dungeonFlow, DunGenExtender dunGenExtender) { if (dungeonFlow == null) { Plugin.logger.LogError("dungeonFlow was null"); @@ -28,6 +38,15 @@ namespace DunGenPlus return true; } + /// + /// Registers the to recieve the alternate dungeon generation changes defined by . + /// + /// + /// + /// + /// if was successfully added. + /// if was null or already has a registered . + /// public static bool AddDunGenExtender(DunGenExtender dunGenExtender) { if (dunGenExtender == null) { Plugin.logger.LogError("dunGenExtender was null"); @@ -37,10 +56,23 @@ namespace DunGenPlus return AddDunGenExtender(dunGenExtender.DungeonFlow, dunGenExtender); } + /// + /// Checks if has a registered . + /// + /// + /// + /// if has a registered . + /// otherwise. + /// public static bool ContainsDungeonFlow(DungeonFlow dungeonFlow) { return Plugin.DunGenExtenders.ContainsKey(dungeonFlow); } + /// + /// Creates and returns an empty . + /// + /// + /// An empty . public static DunGenExtender CreateDunGenExtender(DungeonFlow dungeonFlow){ var extender = ScriptableObject.CreateInstance(); extender.DungeonFlow = dungeonFlow; diff --git a/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs index 7c8acf2..d07e321 100644 --- a/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs +++ b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderEvents.cs @@ -9,6 +9,15 @@ namespace DunGenPlus.Collections { [System.Serializable] public class DunGenExtenderEvents { + /// + /// Event handler called after is selected for dungeon generation, + /// but before the dungeon generation process begins. + /// Allows the parameter passed, , to be modified. + /// + /// The parameter passed, , is a shallow copy of . + /// Field values can be replaced without affecting the original properties. Editing a reference value will affect the original value. + /// + /// public ExtenderEvent OnModifyDunGenExtenderProperties = new ExtenderEvent(); } diff --git a/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs index 1b82376..261d34a 100644 --- a/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs +++ b/DunGenPlus/DunGenPlus/Collections/DunGenExtenderProperties.cs @@ -13,7 +13,13 @@ namespace DunGenPlus.Collections { public class DunGenExtenderProperties { public enum CopyNodeBehaviour { + /// + /// Nodes will copy from the MainRoomTilePrefab's position in the main path + /// CopyFromMainPathPosition, + /// + /// Nodes will copy from the MainRoomTilePrefab's location from the node list + 1 + /// CopyFromNodeList } @@ -21,7 +27,7 @@ namespace DunGenPlus.Collections { [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.")] + [Tooltip("The Tile Prefab where the additional main paths will start from. Cannot be null if MainPathCount is more than 1.")] public GameObject MainRoomTilePrefab; [Tooltip("CopyFromMainPathPosition means that nodes will copy from the MainRoomTilePrefab's position in the main path.\n\nCopyFromNodeList means that nodes will copy from the MainRoomTilePrefab's location from the node list + 1.")] public CopyNodeBehaviour MainPathCopyNodeBehaviour = CopyNodeBehaviour.CopyFromMainPathPosition; @@ -46,9 +52,11 @@ namespace DunGenPlus.Collections { 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("Forced Tiles")] + [Tooltip("If enabled, attempts to forcefully spawn tiles from ForcedTileSets after branching paths are generated.\n\nCan only be used if MainPathCount > 1.")] + public bool UseForcedTiles; + [Tooltip("The list of tiles that will be attempted to forcefully spawn. Each entry will spawn only one tile from it's list.\n\nIf the tile cannot be forcefully spawned, the dungeon generation will not restart.")] + public List ForcedTileSets = new List(); [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.")] @@ -66,6 +74,12 @@ namespace DunGenPlus.Collections { [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; + [Header("Miscellaneous")] + [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; + [Tooltip("If enabled, the RandomGuaranteedScrapSpawn component will be come active.\n\nThe component allows random scrap of a specified minimum value to always be spawned on a specific point.")] + public bool UseRandomGuaranteedScrapSpawn = false; + internal void SetupProperties(DungeonGenerator generator){ _normalNodeArchetypesDictioanry = new Dictionary(); _defaultNodeArchetype = null; @@ -124,7 +138,8 @@ namespace DunGenPlus.Collections { copy.AddArchetypesToNormalNodes = AddArchetypesToNormalNodes; copy.NormalNodeArchetypes = NormalNodeArchetypes; - copy.UseDoorwaySisters = UseDoorwaySisters; + copy.UseForcedTiles = UseForcedTiles; + copy.ForcedTileSets = ForcedTileSets; copy.UseLineRandomizer = UseLineRandomizer; copy.LineRandomizerTileSets = LineRandomizerTileSets; @@ -134,6 +149,9 @@ namespace DunGenPlus.Collections { copy.UseMaxShadowsRequestUpdate = UseMaxShadowsRequestUpdate; copy.MaxShadowsRequestAmount = MaxShadowsRequestAmount; + copy.UseDoorwaySisters = UseDoorwaySisters; + copy.UseRandomGuaranteedScrapSpawn = UseRandomGuaranteedScrapSpawn; + return copy; } diff --git a/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs b/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs index 4374cd7..ba799f4 100644 --- a/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs +++ b/DunGenPlus/DunGenPlus/Collections/ExtenderEvent.cs @@ -9,14 +9,26 @@ namespace DunGenPlus.Collections { internal event ParameterEvent onParameterEvent; + /// + /// Calls listeners. + /// + /// public void Invoke(T param) { onParameterEvent?.Invoke(param); } + /// + /// Adds listener. + /// + /// public void AddListener(ParameterEvent listener) { onParameterEvent += listener; } + /// + /// Removes listener. + /// + /// public void RemoveListener(ParameterEvent listener) { onParameterEvent -= listener; } diff --git a/DunGenPlus/DunGenPlus/Collections/ForcedTileSetList.cs b/DunGenPlus/DunGenPlus/Collections/ForcedTileSetList.cs new file mode 100644 index 0000000..534ee1c --- /dev/null +++ b/DunGenPlus/DunGenPlus/Collections/ForcedTileSetList.cs @@ -0,0 +1,23 @@ +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 ForcedTileSetList { + + [Tooltip("List of tiles to be forcefully spawned.")] + public List Tilesets = new List(); + [Tooltip("The weight based on the path's depth.")] + public AnimationCurve DepthWeightScale = new AnimationCurve(); + [Tooltip("The weight for the tile spawning on the main path.")] + public float MainPathWeight = 1f; + [Tooltip("The weight for the tile spawning on the branch path.")] + public float BranchPathWeight = 1f; + + } +} diff --git a/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs b/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs index 37c397c..ef4e8c7 100644 --- a/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs +++ b/DunGenPlus/DunGenPlus/Components/Props/SpawnSyncedObjectCycle.cs @@ -10,8 +10,8 @@ namespace DunGenPlus.Components.Props { public class SpawnSyncedObjectCycle : MonoBehaviour, IDungeonCompleteReceiver { - public static int cycle; - public static Dictionary cycleDictionary; + internal static int cycle; + internal 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; @@ -24,13 +24,13 @@ namespace DunGenPlus.Components.Props Spawn = GetComponent(); } - public static void UpdateCycle(int value){ + internal static void UpdateCycle(int value){ Plugin.logger.LogInfo($"Updating SpawnSyncedObject start cycle to {value}"); cycle = value; cycleDictionary = new Dictionary(); } - public int GetCycle(int id){ + internal int GetCycle(int id){ if (!cycleDictionary.TryGetValue(id, out var value)){ value = cycle; cycleDictionary.Add(id, value); diff --git a/DunGenPlus/DunGenPlus/Components/Scrap/RandomGuaranteedScrapSpawn.cs b/DunGenPlus/DunGenPlus/Components/Scrap/RandomGuaranteedScrapSpawn.cs index fb2e9b3..3d86202 100644 --- a/DunGenPlus/DunGenPlus/Components/Scrap/RandomGuaranteedScrapSpawn.cs +++ b/DunGenPlus/DunGenPlus/Components/Scrap/RandomGuaranteedScrapSpawn.cs @@ -10,24 +10,38 @@ namespace DunGenPlus.Components.Scrap { public class RandomGuaranteedScrapSpawn : MonoBehaviour { + [Tooltip("Chance for the loot to even spawn.")] + [Range(0f, 1f)] public float spawnChance = 1f; + [Tooltip("Minimum scrap value of the scrap item.")] public int minimumScrapValue = 0; - public static Dictionary> scrapItemCache; + internal static Dictionary> scrapItemRarityCache; - public static void ResetCache(){ - scrapItemCache = new Dictionary>(); + internal static void ResetCache(){ + scrapItemRarityCache = new Dictionary>(); } - public static IEnumerable GetCachedItemList(List allMoonItems, int scrapValue) { - if (!scrapItemCache.TryGetValue(scrapValue, out var list)){ - list = allMoonItems.Select(i => i.spawnableItem).Where(i => i.minValue >= scrapValue).ToArray(); - scrapItemCache.Add(scrapValue, list); + internal static IEnumerable GetCachedItemList(List allMoonItems, int scrapValue) { + if (!scrapItemRarityCache.TryGetValue(scrapValue, out var list)){ + list = allMoonItems.Where(i => i.spawnableItem.minValue >= scrapValue).ToArray(); + scrapItemRarityCache.Add(scrapValue, list); } return list; } - public (NetworkObject itemReference, int scrapValue) CreateItem(RoundManager roundManager, List allMoonItems){ + internal static Item GetRandomItem(IEnumerable list) { + var weightList = new int[list.Count()]; + for(var i = 0; i < weightList.Length; ++i) { + weightList[i] = list.ElementAt(i).rarity; + } + + var randomIndex = RoundManager.Instance.GetRandomWeightedIndex(weightList); + return list.ElementAt(randomIndex).spawnableItem; + + } + + internal (NetworkObject itemReference, int scrapValue) CreateItem(RoundManager roundManager, List allMoonItems){ var anomalyRandom = roundManager.AnomalyRandom; if (anomalyRandom.NextDouble() >= spawnChance) return (null, 0); @@ -35,7 +49,7 @@ namespace DunGenPlus.Components.Scrap { var itemListCount = itemList.Count(); if (itemListCount == 0) return (null, 0); - var randomItem = itemList.ElementAt(anomalyRandom.Next(itemListCount)); + var randomItem = GetRandomItem(itemList); var randomValue = (int)(anomalyRandom.Next(randomItem.minValue, randomItem.maxValue) * roundManager.scrapValueMultiplier); var gameObject = Instantiate(randomItem.spawnPrefab, transform.position, Quaternion.identity, roundManager.spawnedScrapContainer); diff --git a/DunGenPlus/DunGenPlus/DunGenPlus.csproj b/DunGenPlus/DunGenPlus/DunGenPlus.csproj index 73b2163..cd3d154 100644 --- a/DunGenPlus/DunGenPlus/DunGenPlus.csproj +++ b/DunGenPlus/DunGenPlus/DunGenPlus.csproj @@ -96,6 +96,7 @@ + diff --git a/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs b/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs index 7df7927..bfd1fa3 100644 --- a/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs +++ b/DunGenPlus/DunGenPlus/Generation/DunGenPlusGenerator.cs @@ -281,8 +281,47 @@ namespace DunGenPlus.Generation { ActiveAlternative = true; gen.proxyDungeon.MainPathTiles = allMainPathTiles[0]; + + AddForcedTiles(gen); } + public static void AddForcedTiles(DungeonGenerator gen){ + var forcedTileSetLists = DunGenPlusGenerator.Properties.ForcedTileSets.ToList(); + while(forcedTileSetLists.Count > 0){ + var item = forcedTileSetLists[forcedTileSetLists.Count - 1]; + + // we have to check ALL tiles + var allTiles = gen.proxyDungeon.AllTiles + .Select(t => { + var depthValue = item.DepthWeightScale.Evaluate(t.Placement.NormalizedDepth); + var weight = t.Placement.IsOnMainPath ? item.MainPathWeight : item.BranchPathWeight; + return (t, depthValue * weight * gen.RandomStream.NextDouble()); + }) + .Where(pair => pair.Item2 > 0f) + .OrderBy(pair => pair.Item2); + + // try every tile, if we somehow fail than man that sucks + foreach(var pair in allTiles){ + var t = pair.t; + var tileProxy = gen.AddTile(t, item.Tilesets, t.Placement.NormalizedDepth, null, TilePlacementResult.None); + if (tileProxy == null) continue; + + tileProxy.Placement.BranchDepth = t.Placement.BranchDepth; + tileProxy.Placement.NormalizedBranchDepth = t.Placement.NormalizedDepth; + tileProxy.Placement.GraphNode = t.Placement.GraphNode; + tileProxy.Placement.GraphLine = t.Placement.GraphLine; + + Plugin.logger.LogInfo($"Forcefully added tile {tileProxy.Prefab.name}"); + break; + } + + forcedTileSetLists.RemoveAt(forcedTileSetLists.Count - 1); + } + + Plugin.logger.LogInfo($"Forcefully added all tiles"); + + } + public static void RandomizeLineArchetypes(DungeonGenerator gen, bool randomizeMainPath){ if (!Properties.UseLineRandomizer) return; diff --git a/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs b/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs index 14e445d..20b261f 100644 --- a/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs +++ b/DunGenPlus/DunGenPlus/Patches/DungeonGeneratorPatch.cs @@ -27,10 +27,12 @@ namespace DunGenPlus.Patches { [HarmonyPatch(typeof(DungeonGenerator), "GenerateBranchPaths")] public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance, ref IEnumerator __result){ if (DunGenPlusGenerator.Active && DunGenPlusGenerator.ActiveAlternative) { - __result = DunGenPlusGenerator.GenerateAlternativeMainPaths(__instance); + __result = DunGenPlusGenerator.GenerateAlternativeMainPaths(__instance); } + } + [HarmonyTranspiler] [HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath", MethodType.Enumerator)] public static IEnumerable GenerateMainPathPatch(IEnumerable instructions){ diff --git a/DunGenPlus/DunGenPlus/Patches/RoundManagerPatch.cs b/DunGenPlus/DunGenPlus/Patches/RoundManagerPatch.cs index 02d80c1..922a433 100644 --- a/DunGenPlus/DunGenPlus/Patches/RoundManagerPatch.cs +++ b/DunGenPlus/DunGenPlus/Patches/RoundManagerPatch.cs @@ -16,7 +16,7 @@ namespace DunGenPlus.Patches { [HarmonyPrefix] [HarmonyPatch(typeof(RoundManager), "waitForScrapToSpawnToSync")] public static void waitForScrapToSpawnToSyncPatch (ref RoundManager __instance, ref NetworkObjectReference[] spawnedScrap, ref int[] scrapValues) { - if (DunGenPlusGenerator.Active) { + if (DunGenPlusGenerator.Active && DunGenPlusGenerator.Properties.UseRandomGuaranteedScrapSpawn) { var spawnedScrapList = spawnedScrap.ToList(); var scrapValuesList = scrapValues.ToList(); diff --git a/DunGenPlus/DunGenPlus/Plugin.cs b/DunGenPlus/DunGenPlus/Plugin.cs index 1a69b21..b61b465 100644 --- a/DunGenPlus/DunGenPlus/Plugin.cs +++ b/DunGenPlus/DunGenPlus/Plugin.cs @@ -24,7 +24,7 @@ namespace DunGenPlus { internal const string modGUID = "dev.ladyalice.dungenplus"; private const string modName = "Dungeon Generation Plus"; - private const string modVersion = "1.0.2"; + private const string modVersion = "1.0.4"; internal readonly Harmony Harmony = new Harmony(modGUID);