Added forced tile generation

Changed RandomGuaranteedScrapSpawn to use the item's weight
Added some tooltips/XML
This commit is contained in:
LadyAliceMargatroid 2024-08-05 13:58:33 -07:00
parent c7f604494e
commit 383c879b96
12 changed files with 171 additions and 21 deletions

View File

@ -11,6 +11,16 @@ namespace DunGenPlus
{
public class API {
/// <summary>
/// Registers the <paramref name="dungeonFlow"/> to recieve the alternate dungeon generation changes defined by <paramref name="dunGenExtender"/>.
/// </summary>
/// <param name="dungeonFlow"></param>
/// <param name="dunGenExtender"></param>
///
/// <returns>
/// <see langword="true"/> if <paramref name="dunGenExtender"/> was successfully added.
/// <see langword="false"/> if <paramref name="dungeonFlow"/> was null or already has a registered <see cref="DunGenExtender"/>.
/// </returns>
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;
}
/// <summary>
/// Registers the <see cref="DunGenExtender.DungeonFlow"/> to recieve the alternate dungeon generation changes defined by <paramref name="dunGenExtender"/>.
/// </summary>
/// <param name="dunGenExtender"></param>
///
/// <returns>
/// <see langword="true"/> if <paramref name="dunGenExtender"/> was successfully added.
/// <see langword="false"/> if <see cref="DunGenExtender.DungeonFlow"/> was null or already has a registered <see cref="DunGenExtender"/>.
/// </returns>
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);
}
/// <summary>
/// Checks if <paramref name="dungeonFlow"/> has a registered <see cref="DunGenExtender"/>.
/// </summary>
/// <param name="dungeonFlow"></param>
/// <returns>
/// <see langword="true"/> if <paramref name="dungeonFlow"/> has a registered <see cref="DunGenExtender"/>.
/// <see langword="false"/> otherwise.
/// </returns>
public static bool ContainsDungeonFlow(DungeonFlow dungeonFlow) {
return Plugin.DunGenExtenders.ContainsKey(dungeonFlow);
}
/// <summary>
/// Creates and returns an empty <see cref="DunGenExtender"/>.
/// </summary>
/// <param name="dungeonFlow"></param>
/// <returns>An empty <see cref="DunGenExtender"/>.</returns>
public static DunGenExtender CreateDunGenExtender(DungeonFlow dungeonFlow){
var extender = ScriptableObject.CreateInstance<DunGenExtender>();
extender.DungeonFlow = dungeonFlow;

View File

@ -9,6 +9,15 @@ namespace DunGenPlus.Collections {
[System.Serializable]
public class DunGenExtenderEvents {
/// <summary>
/// Event handler called after <see cref="DunGenExtender.DungeonFlow"/> is selected for dungeon generation,
/// but before the dungeon generation process begins.
/// Allows the parameter passed, <see cref="DunGenExtenderProperties"/>, to be modified.
/// <para>
/// The parameter passed, <see cref="DunGenExtenderProperties"/>, is a shallow copy of <see cref="DunGenExtender.Properties"/>.
/// Field values can be replaced without affecting the original properties. Editing a reference value will affect the original value.
/// </para>
/// </summary>
public ExtenderEvent<DunGenExtenderProperties> OnModifyDunGenExtenderProperties = new ExtenderEvent<DunGenExtenderProperties>();
}

View File

@ -13,7 +13,13 @@ namespace DunGenPlus.Collections {
public class DunGenExtenderProperties {
public enum CopyNodeBehaviour {
/// <summary>
/// Nodes will copy from the MainRoomTilePrefab's position in the main path
/// </summary>
CopyFromMainPathPosition,
/// <summary>
/// Nodes will copy from the MainRoomTilePrefab's location from the node list + 1
/// </summary>
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<string, NodeArchetype> _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<ForcedTileSetList> ForcedTileSets = new List<ForcedTileSetList>();
[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<string, NodeArchetype>();
_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;
}

View File

@ -9,14 +9,26 @@ namespace DunGenPlus.Collections {
internal event ParameterEvent onParameterEvent;
/// <summary>
/// Calls listeners.
/// </summary>
/// <param name="param"></param>
public void Invoke(T param) {
onParameterEvent?.Invoke(param);
}
/// <summary>
/// Adds listener.
/// </summary>
/// <param name="listener"></param>
public void AddListener(ParameterEvent listener) {
onParameterEvent += listener;
}
/// <summary>
/// Removes listener.
/// </summary>
/// <param name="listener"></param>
public void RemoveListener(ParameterEvent listener) {
onParameterEvent -= listener;
}

View File

@ -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<TileSet> Tilesets = new List<TileSet>();
[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;
}
}

View File

@ -10,8 +10,8 @@ namespace DunGenPlus.Components.Props
{
public class SpawnSyncedObjectCycle : MonoBehaviour, IDungeonCompleteReceiver {
public static int cycle;
public static Dictionary<int, int> cycleDictionary;
internal static int cycle;
internal static Dictionary<int, int> 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<SpawnSyncedObject>();
}
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<int, int>();
}
public int GetCycle(int id){
internal int GetCycle(int id){
if (!cycleDictionary.TryGetValue(id, out var value)){
value = cycle;
cycleDictionary.Add(id, value);

View File

@ -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<int, IEnumerable<Item>> scrapItemCache;
internal static Dictionary<int, IEnumerable<SpawnableItemWithRarity>> scrapItemRarityCache;
public static void ResetCache(){
scrapItemCache = new Dictionary<int, IEnumerable<Item>>();
internal static void ResetCache(){
scrapItemRarityCache = new Dictionary<int, IEnumerable<SpawnableItemWithRarity>>();
}
public static IEnumerable<Item> GetCachedItemList(List<SpawnableItemWithRarity> 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<SpawnableItemWithRarity> GetCachedItemList(List<SpawnableItemWithRarity> 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<SpawnableItemWithRarity> allMoonItems){
internal static Item GetRandomItem(IEnumerable<SpawnableItemWithRarity> 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<SpawnableItemWithRarity> 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);

View File

@ -96,6 +96,7 @@
<Compile Include="Assets.cs" />
<Compile Include="Collections\DunGenExtenderEvents.cs" />
<Compile Include="Collections\ExtenderEvent.cs" />
<Compile Include="Collections\ForcedTileSetList.cs" />
<Compile Include="Collections\NodeArchetype.cs" />
<Compile Include="Components\DoorwayCleanup.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DCSRemoveDoorwayConnectedDoorway.cs" />

View File

@ -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;

View File

@ -29,8 +29,10 @@ namespace DunGenPlus.Patches {
if (DunGenPlusGenerator.Active && DunGenPlusGenerator.ActiveAlternative) {
__result = DunGenPlusGenerator.GenerateAlternativeMainPaths(__instance);
}
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath", MethodType.Enumerator)]
public static IEnumerable<CodeInstruction> GenerateMainPathPatch(IEnumerable<CodeInstruction> instructions){

View File

@ -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();

View File

@ -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);