Added configs for items and enemies

Critical damage renamed to marked for death and all share the same mechanic now
Revelant now slows down to speed over a fixed short amount instead the nonworking jank before
Moved item/enemy injection to DunGenPlus
This commit is contained in:
LadyAliceMargatroid 2024-12-15 09:23:53 -08:00
parent e38be14578
commit fd731baf2f
33 changed files with 551 additions and 470 deletions

View File

@ -62,17 +62,18 @@ namespace ScarletMansion {
public static Enemy maid; public static Enemy maid;
// item values // item values
public class GlobalItem { public class GlobalItem {
public Item item; public Item item;
private int _itemId; private int _itemId;
public GlobalItem(Item item) { public Func<PluginConfig.Item> configItemEntryFunc;
public GlobalItem(Item item, Func<PluginConfig.Item> configItemEntryFunc) {
this.item = item; this.item = item;
_itemId = -1; _itemId = -1;
this.configItemEntryFunc = configItemEntryFunc;
} }
public int itemId { public int itemId {
get { get {
// cache time // cache time
@ -92,22 +93,41 @@ namespace ScarletMansion {
_itemId = value; _itemId = value;
} }
} }
public PluginConfig.Item GetConfigItemEntry(){
return configItemEntryFunc();
}
} }
public class ScrapItem : GlobalItem { public class ScrapItem : GlobalItem {
public Func<int> rarityFunc; public bool SpawnsOnMap => configScrapItemEntryFunc != null;
public ScrapItem(Item item, Func<int> func) : base (item){ public Func<PluginConfig.ScrapItem> configScrapItemEntryFunc;
rarityFunc = func;
public ScrapItem(Item item, Func<PluginConfig.ScrapItem> configScrapItemEntryFunc) : base (item, configScrapItemEntryFunc){
this.configScrapItemEntryFunc = configScrapItemEntryFunc;
}
public void UpdateItemValue(){
if (SpawnsOnMap) {
var config = GetConfigScrapItemEntry();
item.minValue = config.valueRange.min;
item.maxValue = config.valueRange.max;
}
} }
public SpawnableItemWithRarity GetItemRarity(){ public SpawnableItemWithRarity GetItemRarity(){
var configEntry = GetConfigScrapItemEntry();
var item = new SpawnableItemWithRarity(); var item = new SpawnableItemWithRarity();
item.spawnableItem = this.item; item.spawnableItem = this.item;
item.rarity = rarityFunc(); item.rarity = configEntry != null ? configEntry.spawnWeight : 0;
return item; return item;
} }
public PluginConfig.ScrapItem GetConfigScrapItemEntry(){
return configScrapItemEntryFunc();
}
} }
public class Flashlight : GlobalItem { public class Flashlight : GlobalItem {
@ -118,12 +138,17 @@ namespace ScarletMansion {
public int lethalHelmetIndex = -1; public int lethalHelmetIndex = -1;
public int scarletHelmetIndex = -1; public int scarletHelmetIndex = -1;
public Flashlight(string baseName, int lethalHelmetIndex) : base(null) { public Flashlight(string baseName, int lethalHelmetIndex, Func<PluginConfig.Item> configEntryFunc) : base(null, configEntryFunc) {
assetName = $"Scarlet{baseName}"; assetName = $"Scarlet{baseName}";
displayName = $"D. {baseName}"; displayName = $"D. {baseName}";
this.lethalHelmetIndex = lethalHelmetIndex; this.lethalHelmetIndex = lethalHelmetIndex;
} }
public bool ContainsItemCheckConfig(Item compareItem){
if (!GetConfigItemEntry().enabled) return false;
return ContainsItem(compareItem);
}
public bool ContainsItem(Item compareItem) { public bool ContainsItem(Item compareItem) {
return compareItem == item || compareItem == lethalVanillaItem; return compareItem == item || compareItem == lethalVanillaItem;
} }
@ -132,18 +157,16 @@ namespace ScarletMansion {
public static List<GlobalItem> globalItems; public static List<GlobalItem> globalItems;
public static List<ScrapItem> scrapItems; public static List<ScrapItem> scrapItems;
public static Dictionary<string, Func<int>> itemRarityTable = new Dictionary<string, Func<int>>(){ public static Dictionary<string, Func<PluginConfig.ScrapItem>> itemConfigTable = new Dictionary<string, Func<PluginConfig.ScrapItem>>(){
{ "Deco. crystal", () => PluginConfig.Instance.crystalWeightValue }, { "Deco. crystal", () => PluginConfig.Instance.crystalValue },
{ "Shattered deco. crystal", () => PluginConfig.Instance.crystalBrokenWeightValue }, { "Shattered deco. crystal", () => PluginConfig.Instance.crystalBrokenValue },
{ "Demonic painting", () => 0 }, { "Doll Snow Globe", () => PluginConfig.Instance.snowGlobeValue }
{ "Maid's knife", () => 0 },
{ "Doll Snow Globe", () => PluginConfig.Instance.snowGlobeWeightValue }
}; };
public static GlobalItem key;
public static Flashlight flashlight; public static Flashlight flashlight;
public static Flashlight flashlightBB; public static Flashlight flashlightBB;
public static GlobalItem key;
public static GlobalItem GetGlobalItem(Item item){ public static GlobalItem GetGlobalItem(Item item){
return globalItems.FirstOrDefault(x => x.item == item); return globalItems.FirstOrDefault(x => x.item == item);
@ -155,6 +178,12 @@ namespace ScarletMansion {
return null; return null;
} }
public static Flashlight GetFlashlightCheckConfig(Item item){
if (flashlight.ContainsItemCheckConfig(item)) return flashlight;
if (flashlightBB.ContainsItemCheckConfig(item)) return flashlightBB;
return null;
}
// game references // game references
public static ItemGroup genericItemGroup; public static ItemGroup genericItemGroup;
@ -207,16 +236,10 @@ namespace ScarletMansion {
globalItems = new List<GlobalItem>(); globalItems = new List<GlobalItem>();
scrapItems = new List<ScrapItem>(); scrapItems = new List<ScrapItem>();
foreach(var i in networkObjectList.items){
var entry = new GlobalItem(i);
globalItems.Add(entry);
Items.RegisterItem(i);
Plugin.logger.LogDebug($"Global Item {i.itemName} registered");
}
foreach(var i in networkObjectList.scrapItems) { foreach(var i in networkObjectList.scrapItems) {
var entry = new ScrapItem(i, GetItemRarityFunction(i)); var function = GetItemConfigFunction(i);
var entry = new ScrapItem(i, function);
scrapItems.Add(entry); scrapItems.Add(entry);
globalItems.Add(entry); globalItems.Add(entry);
@ -225,9 +248,12 @@ namespace ScarletMansion {
Plugin.logger.LogDebug($"Scrap Item {i.itemName} registered"); Plugin.logger.LogDebug($"Scrap Item {i.itemName} registered");
} }
flashlight = new Flashlight("Pro Flashlight", 0); key = new GlobalItem(networkObjectList.items[0], () => PluginConfig.Instance.scarletKeyValue);
flashlightBB = new Flashlight("Flashlight", 1); globalItems.Add(key);
key = new GlobalItem(networkObjectList.items[0]); Items.RegisterItem(key.item);
flashlight = new Flashlight("Pro Flashlight", 0, () => PluginConfig.Instance.decoProFlashlightValue);
flashlightBB = new Flashlight("Flashlight", 1, () => PluginConfig.Instance.decoFlashlightValue);
globalItems.Add(flashlight); globalItems.Add(flashlight);
globalItems.Add(flashlightBB); globalItems.Add(flashlightBB);
@ -239,15 +265,12 @@ namespace ScarletMansion {
onAssetsLoadEvent.Call(); onAssetsLoadEvent.Call();
} }
public static Func<int> GetItemRarityFunction(Item item){ public static Func<PluginConfig.ScrapItem> GetItemConfigFunction(Item item){
var name = item.itemName; var name = item.itemName;
if (itemConfigTable.TryGetValue(name, out var result)){
if (itemRarityTable.ContainsKey(name)){ return result;
return itemRarityTable[name];
} }
return null;
Plugin.logger.LogError($"Could not find rarity function for {name}. Setting to default value 10");
return () => 10;
} }

View File

@ -1,18 +1,4 @@
using System; using HarmonyLib;
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 System.Collections;
using DunGen;
using LethalLevelLoader;
using DunGen.Graph;
namespace ScarletMansion.DunGenPatch { namespace ScarletMansion.DunGenPatch {

View File

@ -42,8 +42,6 @@ namespace ScarletMansion.DunGenPatch {
var mainPathLength = generator.DungeonFlow.Length; var mainPathLength = generator.DungeonFlow.Length;
Plugin.logger.LogDebug($"Length of main path be: {GetLength(mainPathLength, scale)}"); Plugin.logger.LogDebug($"Length of main path be: {GetLength(mainPathLength, scale)}");
GamePatch.LoadAssetsIntoLevelPatch.ModifyLevel(StartOfRound.Instance.currentLevel);
} }
public static string GetLength(IntRange range, float multi){ public static string GetLength(IntRange range, float multi){
@ -62,12 +60,9 @@ namespace ScarletMansion.DunGenPatch {
var localPlayer = StartOfRound.Instance.localPlayerController; var localPlayer = StartOfRound.Instance.localPlayerController;
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(localPlayer); var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(localPlayer);
if (scarletPlayer != null){ if (scarletPlayer != null){
scarletPlayer.stabbedSelf = false; scarletPlayer.markedForDeath = false;
scarletPlayer.fellInPit = false;
} }
} }
public static void UpdateDunGenExtenderProperties(DunGenExtenderProperties props, EventCallbackScenario callback) { public static void UpdateDunGenExtenderProperties(DunGenExtenderProperties props, EventCallbackScenario callback) {

View File

@ -11,15 +11,8 @@ namespace ScarletMansion.GamePatch.Components {
public static Dictionary<PlayerControllerB, ScarletPlayerControllerB> playerControllers; public static Dictionary<PlayerControllerB, ScarletPlayerControllerB> playerControllers;
// self
public PlayerControllerB player; public PlayerControllerB player;
public bool markedForDeath;
// animation
//public AnimatorOverrideController playerOverrideController;
// second-chance states
public bool stabbedSelf;
public bool fellInPit;
public static ScarletPlayerControllerB GetScarletPlayerScript(PlayerControllerB player) { public static ScarletPlayerControllerB GetScarletPlayerScript(PlayerControllerB player) {
if (!playerControllers.TryGetValue(player, out var scarlet)) { if (!playerControllers.TryGetValue(player, out var scarlet)) {

View File

@ -37,14 +37,14 @@ namespace ScarletMansion.GamePatch.Components
// teleporting them out for a second time in life // teleporting them out for a second time in life
// won't be easy though kek // won't be easy though kek
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(playerControllerB); var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(playerControllerB);
if (scarletPlayer != null && !scarletPlayer.fellInPit && !playerControllerB.criticallyInjured) { if (scarletPlayer != null && !scarletPlayer.markedForDeath && !playerControllerB.criticallyInjured) {
var selfPos = scarletPlayer.transform.position; var selfPos = scarletPlayer.transform.position;
var farthestAINode = RoundManager.Instance.insideAINodes var farthestAINode = RoundManager.Instance.insideAINodes
.Select(n => n.transform.position) .Select(n => n.transform.position)
.OrderByDescending(n => (selfPos - n).magnitude).FirstOrDefault(); .OrderByDescending(n => (selfPos - n).magnitude).FirstOrDefault();
playerControllerB.TeleportPlayer(farthestAINode); playerControllerB.TeleportPlayer(farthestAINode);
var damage = ScarletNetworkManagerUtility.GetCriticalDamageToPlayer(playerControllerB, false); var damage = ScarletNetworkManagerUtility.GetMarkedDamageToPlayer(playerControllerB);
playerControllerB.DamagePlayer(damage, false, true, CauseOfDeath.Suffocation); playerControllerB.DamagePlayer(damage, false, true, CauseOfDeath.Suffocation);
if (playerControllerB.isPlayerDead) { if (playerControllerB.isPlayerDead) {
@ -54,7 +54,9 @@ namespace ScarletMansion.GamePatch.Components
StopSinkingLocalPlayer(playerControllerB); StopSinkingLocalPlayer(playerControllerB);
ScarletNetworkManager.Instance.CreateSpawnAudioPrefab(farthestAINode, playerControllerB.actualClientId); ScarletNetworkManager.Instance.CreateSpawnAudioPrefab(farthestAINode, playerControllerB.actualClientId);
scarletPlayer.fellInPit = true; ScarletNetworkManagerUtility.MarkPlayerForDeath(playerControllerB);
scarletPlayer.markedForDeath = true;
} }
// just straight up murder // just straight up murder
else { else {

View File

@ -26,6 +26,8 @@ namespace ScarletMansion {
public override void Start(){ public override void Start(){
base.Start(); base.Start();
maxChaseSpeed = PluginConfig.Instance.revEnemyValue.speed;
if (IsOwner && KnightSpawnManager.Instance) { if (IsOwner && KnightSpawnManager.Instance) {
var index = KnightSpawnManager.Instance.GetSpawnPointIndex(); var index = KnightSpawnManager.Instance.GetSpawnPointIndex();
if (index == -1) return; if (index == -1) return;
@ -66,7 +68,7 @@ namespace ScarletMansion {
var trueSpeed = currentBehaviourStateIndex == 0 ? maxChaseSpeed : slowChaseSpeed; var trueSpeed = currentBehaviourStateIndex == 0 ? maxChaseSpeed : slowChaseSpeed;
if (IsOwner) { if (IsOwner) {
agent.speed = Mathf.MoveTowards(agent.speed, trueSpeed, 4.5f * Time.deltaTime); agent.speed = Mathf.MoveTowards(agent.speed, trueSpeed, (maxChaseSpeed - slowChaseSpeed) * 4f * Time.deltaTime);
} }
currentAnimSpeed = Mathf.Lerp(currentAnimSpeed, trueSpeed * 0.4597f, 4f * Time.deltaTime); currentAnimSpeed = Mathf.Lerp(currentAnimSpeed, trueSpeed * 0.4597f, 4f * Time.deltaTime);
@ -78,10 +80,12 @@ namespace ScarletMansion {
var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false); var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false);
if (playerControllerB != null && playerControllerB.IsOwner && playerControllerB == targetPlayer) { if (playerControllerB != null && playerControllerB.IsOwner && playerControllerB == targetPlayer) {
var damage = ScarletNetworkManagerUtility.GetCriticalDamageToPlayer(playerControllerB, false); var damage = ScarletNetworkManagerUtility.GetMarkedDamageToPlayer(playerControllerB);
playerControllerB.DamagePlayer(damage, true, true, CauseOfDeath.Mauling, 1, false, default(Vector3)); playerControllerB.DamagePlayer(damage, true, true, CauseOfDeath.Mauling, 1, false, default(Vector3));
playerControllerB.JumpToFearLevel(1f, true); playerControllerB.JumpToFearLevel(1f, true);
ScarletNetworkManagerUtility.MarkPlayerForDeath(playerControllerB);
if (playerControllerB.isPlayerDead) { if (playerControllerB.isPlayerDead) {
Assets.onPlayerDeath.Call(new ModPatch.CoronerParameters(playerControllerB, ModPatch.CoronerDeathEnum.GhostKnight)); Assets.onPlayerDeath.Call(new ModPatch.CoronerParameters(playerControllerB, ModPatch.CoronerDeathEnum.GhostKnight));
} }

View File

@ -20,7 +20,7 @@ namespace ScarletMansion.GamePatch.Enemies {
private bool hasStopped; private bool hasStopped;
public AnimationStopPoints animStopPoints; public AnimationStopPoints animStopPoints;
private float currentChaseSpeed = 14.5f * 0.9f; private float currentChaseSpeed = 13f;
private float currentAnimSpeed = 1f; private float currentAnimSpeed = 1f;
private PlayerControllerB previousTarget; private PlayerControllerB previousTarget;
private bool wasOwnerLastFrame; private bool wasOwnerLastFrame;
@ -51,6 +51,8 @@ namespace ScarletMansion.GamePatch.Enemies {
public override void Start(){ public override void Start(){
base.Start(); base.Start();
currentChaseSpeed = PluginConfig.Instance.knightEnemyValue.speed;
if (IsOwner && KnightSpawnManager.Instance) { if (IsOwner && KnightSpawnManager.Instance) {
var index = KnightSpawnManager.Instance.GetSpawnPointIndex(); var index = KnightSpawnManager.Instance.GetSpawnPointIndex();
if (index == -1) return; if (index == -1) return;

View File

@ -92,6 +92,8 @@ namespace ScarletMansion.GamePatch.Enemies {
// Token: 0x040000EA RID: 234 // Token: 0x040000EA RID: 234
public float idleMovementSpeedBase = 3.5f; public float idleMovementSpeedBase = 3.5f;
public float chaseMovementSpeedBase = 5.5f;
// Token: 0x040000EB RID: 235 // Token: 0x040000EB RID: 235
public float timeSinceChangingItem; public float timeSinceChangingItem;
@ -244,6 +246,9 @@ namespace ScarletMansion.GamePatch.Enemies {
MaidVariant.murderMusicAudio = chaseMusic; MaidVariant.murderMusicAudio = chaseMusic;
} }
enemyHP = PluginConfig.Instance.maidEnemyValue.health;
chaseMovementSpeedBase = PluginConfig.Instance.maidEnemyValue.speed;
if (StartOfRound.Instance.connectedPlayersAmount == 0){ if (StartOfRound.Instance.connectedPlayersAmount == 0){
enemyHP = 2; enemyHP = 2;
idleMovementSpeedBase *= 0.75f; idleMovementSpeedBase *= 0.75f;
@ -267,8 +272,10 @@ namespace ScarletMansion.GamePatch.Enemies {
knifeRender.SetActive(false); knifeRender.SetActive(false);
if (IsServer) { if (IsServer) {
if (PluginConfig.Instance.revEnemyValue.enabled){
var enemy = ScarletNetworkManagerUtility.CreateEnemyWithType<KnightGhostVariant>(knightGhostEnemy, transform.position, 0f); var enemy = ScarletNetworkManagerUtility.CreateEnemyWithType<KnightGhostVariant>(knightGhostEnemy, transform.position, 0f);
enemy.FindAndTunnelPlayer(transform.position); enemy.FindAndTunnelPlayer(transform.position);
}
var newKnife = Object.Instantiate(knifePrefab, base.transform.position + Vector3.up * 0.5f, Quaternion.identity, RoundManager.Instance.spawnedScrapContainer); var newKnife = Object.Instantiate(knifePrefab, base.transform.position + Vector3.up * 0.5f, Quaternion.identity, RoundManager.Instance.spawnedScrapContainer);
newKnife.GetComponent<NetworkObject>().Spawn(); newKnife.GetComponent<NetworkObject>().Spawn();
@ -367,7 +374,7 @@ namespace ScarletMansion.GamePatch.Enemies {
return; return;
} }
agent.speed = 5.5f; agent.speed = chaseMovementSpeedBase;
isRunning = true; isRunning = true;
// lost player in chase // lost player in chase

View File

@ -289,9 +289,6 @@ namespace ScarletMansion.GamePatch {
return n; return n;
}).ToArray(); }).ToArray();
scarletPrefab.GetComponent<MeshRenderer>().sharedMaterials = mats; scarletPrefab.GetComponent<MeshRenderer>().sharedMaterials = mats;
Assets.dungeonExtended.OverrideKeyPrefab = scarletPrefab;
} }
} catch (Exception e) { } catch (Exception e) {
@ -384,6 +381,12 @@ namespace ScarletMansion.GamePatch {
PluginConfig.Instance.paintingCountValue PluginConfig.Instance.paintingCountValue
); );
foreach(var scrap in Assets.scrapItems){
scrap.UpdateItemValue();
}
Assets.dungeonExtended.OverrideKeyPrefab = Assets.key.GetConfigItemEntry().enabled ? Assets.key.item.spawnPrefab : typeof(DungeonLoader).GetField("defaultKeyPrefab", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetValue(null) as GameObject;
Plugin.logger.LogDebug("Set networked config values"); Plugin.logger.LogDebug("Set networked config values");
} }

View File

@ -97,7 +97,7 @@ namespace ScarletMansion.GamePatch.Items {
var items = playerHeldBy.ItemSlots; var items = playerHeldBy.ItemSlots;
for(var i = 0; i < items.Length; ++i){ for(var i = 0; i < items.Length; ++i){
var item = items[i] as FlashlightItem; var item = items[i] as FlashlightItem;
if (item != null && Assets.GetFlashlight(item.itemProperties) != null) { if (item != null && Assets.GetFlashlightCheckConfig(item.itemProperties) != null) {
Plugin.logger.LogDebug($"Flashlight slot {i}"); Plugin.logger.LogDebug($"Flashlight slot {i}");
return (item, i); return (item, i);
} }

View File

@ -176,12 +176,9 @@ namespace ScarletMansion.GamePatch.Items
} }
public int GetDamageToPlayer(PlayerControllerB player) { public int GetDamageToPlayer(PlayerControllerB player) {
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(player); var damage = ScarletNetworkManagerUtility.GetMarkedDamageToPlayer(player);
if (scarletPlayer == null) return 95; ScarletNetworkManagerUtility.MarkPlayerForDeath(player);
return damage;
var forceKill = scarletPlayer.stabbedSelf;
scarletPlayer.stabbedSelf = true;
return ScarletNetworkManagerUtility.GetCriticalDamageToPlayer(player, forceKill);
} }
[ServerRpc] [ServerRpc]

View File

@ -4,169 +4,83 @@ using System.Linq;
using UnityEngine; using UnityEngine;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Reflection; using System.Reflection;
using DunGenPlus.Managers;
using ScarletMansion.GamePatch.Components;
namespace ScarletMansion.GamePatch { namespace ScarletMansion.GamePatch {
public class LoadAssetsIntoLevelPatch { public class LoadAssetsIntoLevelPatch {
// alright new strat public static void AddItemsLocal(RoundManager roundManager){
// at the start, we create an alternate enemy (and item) list foreach(var item in Assets.scrapItems){
// we highjack the enemy (and item) list variable where ever it is called (base game or advanced company) if (item.SpawnsOnMap) {
// if the highjacked list matches the one we created, which it should (and we grab from advanced company too if needed) var config = item.GetConfigScrapItemEntry();
// then we replace if (config.enabled && config.spawnWeight > 0 && !config.spawnOnAllMoons) {
var entry = item.GetItemRarity();
public static InjectionDictionary enemiesInjection = new InjectionDictionary( ScrapItemManager.AddItem(entry);
"Enemies", }
typeof(LoadAssetsIntoLevelPatch).GetMethod("GetEnemies", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public), }
new CodeInstruction(OpCodes.Ldfld, typeof(SelectableLevel).GetField("Enemies")) }
);
public static InjectionDictionary itemsInjection = new InjectionDictionary(
"Items",
typeof(LoadAssetsIntoLevelPatch).GetMethod("GetItems", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public),
new CodeInstruction(OpCodes.Ldfld, typeof(SelectableLevel).GetField("spawnableScrap"))
);
[HarmonyTranspiler]
[HarmonyPatch(typeof(EnemyVent), "SyncVentSpawnTimeClientRpc")]
public static IEnumerable<CodeInstruction> SyncVentSpawnTimeClientRpcPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "SyncVentSpawnTimeClientRpc", 1);
} }
[HarmonyTranspiler] public static void AddItemsGlobal(RoundManager roundManager){
[HarmonyPatch(typeof(RoundManager), "AssignRandomEnemyToVent")] foreach(var item in Assets.scrapItems){
public static IEnumerable<CodeInstruction> AssignRandomEnemyToVentPatch(IEnumerable<CodeInstruction> instructions){ if (item.SpawnsOnMap) {
return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "AssignRandomEnemyToVent", 10); var config = item.GetConfigScrapItemEntry();
if (config.enabled && config.spawnWeight > 0 && config.spawnOnAllMoons) {
var entry = item.GetItemRarity();
ScrapItemManager.AddItem(entry);
}
}
}
} }
[HarmonyTranspiler] public static void AddEnemiesLocal(RoundManager roundManager) {
[HarmonyPatch(typeof(RoundManager), "EnemyCannotBeSpawned")]
public static IEnumerable<CodeInstruction> EnemyCannotBeSpawnedPatch(IEnumerable<CodeInstruction> instructions){ var currentEnemies = roundManager.currentLevel.Enemies;
return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "EnemyCannotBeSpawned", 4);
void AddEnemy(Assets.Enemy enemy, string sourceEnemyName, string targetEnemyName, PluginConfig.SpawnableEnemy enemyConfig) {
const int minBaseWeight = 10;
if (!enemyConfig.enabled) {
Plugin.logger.LogDebug($"{sourceEnemyName} is disblaed. Not loading onto moon.");
return;
} }
[HarmonyTranspiler]
[HarmonyPatch(typeof(RoundManager), "ResetEnemyTypesSpawnedCounts")]
public static IEnumerable<CodeInstruction> ResetEnemyTypesSpawnedCountsPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "ResetEnemyTypesSpawnedCounts", 4);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(RoundManager), "SetChallengeFileRandomModifiers")]
public static IEnumerable<CodeInstruction> SetChallengeFileRandomModifiersPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "SetChallengeFileRandomModifiersEnemy", 3);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(RoundManager), "SpawnEnemyGameObject")]
public static IEnumerable<CodeInstruction> SpawnEnemyGameObjectPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "SpawnEnemyGameObject", 3);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(GiftBoxItem), "Start")]
public static IEnumerable<CodeInstruction> GiftBoxItemStartPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "GiftBoxItem", 5);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(RoundManager), "SetChallengeFileRandomModifiers")]
public static IEnumerable<CodeInstruction> SetChallengeFileRandomModifiersItemPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "SetChallengeFileRandomModifiersItem", 1);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")]
public static IEnumerable<CodeInstruction> SpawnScrapInLevelPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "SpawnScrapInLevel", 4);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DunGenPlus.Patches.RoundManagerPatch), "waitForScrapToSpawnToSyncPatch")]
public static IEnumerable<CodeInstruction> waitForScrapToSpawnToSyncPatch(IEnumerable<CodeInstruction> instructions){
return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "waitForScrapToSpawnToSyncPatch", 1);
}
public static List<SpawnableEnemyWithRarity> lastEnemiesRarity;
public static List<SpawnableEnemyWithRarity> currentEnemiesRarity;
public static List<SpawnableItemWithRarity> lastItemsRarity;
public static List<SpawnableItemWithRarity> currentItemsRarity;
public static void ModifyLevel(SelectableLevel level){
lastEnemiesRarity = level.Enemies;
lastItemsRarity = level.spawnableScrap;
currentEnemiesRarity = lastEnemiesRarity.ToList();
currentItemsRarity = lastItemsRarity.ToList();
void AddEnemy(Assets.Enemy enemy, string sourceEnemyName, string targetEnemyName, int baseWeight, int minBaseWeight, float weightStealPercentage) {
if (enemy != null) { if (enemy != null) {
var target = currentEnemiesRarity var target = currentEnemies
.Where(c => c.enemyType.name.ToLowerInvariant() == targetEnemyName) .Where(c => c.enemyType.name.ToLowerInvariant() == targetEnemyName)
.FirstOrDefault(); .FirstOrDefault();
if (target == null){ if (target == null){
Plugin.logger.LogDebug($"No enemy {targetEnemyName} in level, using default rarity of {minBaseWeight}."); Plugin.logger.LogDebug($"No enemy {targetEnemyName} in level, using default rarity of {minBaseWeight}.");
var entry = enemy.GetItemEntry(baseWeight + minBaseWeight);
Plugin.logger.LogDebug($"Adding enemy {sourceEnemyName} with weight {entry.rarity}"); var entry = enemy.GetItemEntry(enemyConfig.spawnWeightBase + minBaseWeight);
currentEnemiesRarity.Add(entry); EnemyManager.AddEnemy(entry);
} else { } else {
currentEnemiesRarity.Remove(target); Plugin.logger.LogError($"{sourceEnemyName}: {enemyConfig.spawnWeightStealPercentage}");
var entryRarity = Mathf.RoundToInt(target.rarity * weightStealPercentage); var entryRarity = Mathf.RoundToInt(target.rarity * enemyConfig.spawnWeightStealPercentage);
var prevEntry = new SpawnableEnemyWithRarity(); var prevEntry = new SpawnableEnemyWithRarity();
prevEntry.enemyType = target.enemyType; prevEntry.enemyType = target.enemyType;
prevEntry.rarity = target.rarity - entryRarity; prevEntry.rarity = target.rarity - entryRarity;
var newEntry = enemy.GetItemEntry(baseWeight + entryRarity); var newEntry = enemy.GetItemEntry(enemyConfig.spawnWeightBase + entryRarity);
Plugin.logger.LogDebug($"Adding enemy {sourceEnemyName} with weight {newEntry.rarity}"); EnemyManager.AddEnemy(newEntry);
Plugin.logger.LogDebug($"Setting enemy {targetEnemyName} with weight {prevEntry.rarity}"); EnemyManager.AddEnemy(prevEntry);
currentEnemiesRarity.Add(newEntry);
currentEnemiesRarity.Add(prevEntry);
} }
} else { } else {
Plugin.logger.LogError($"Failed to load custom enemy {sourceEnemyName} as their reference is missing"); Plugin.logger.LogError($"Failed to load custom enemy {sourceEnemyName} as their reference is missing");
} }
} }
AddEnemy(Assets.knight, "knight", "springman", PluginConfig.Instance.knightWeightBaseValue, 10, PluginConfig.Instance.knightWeightStealPercentageValue); AddEnemy(Assets.knight, "knight", "springman", PluginConfig.Instance.knightEnemyValue);
AddEnemy(Assets.maid, "maid", "butler", PluginConfig.Instance.maidWeightBaseValue, 10, PluginConfig.Instance.maidWeightStealPercentageValue); AddEnemy(Assets.maid, "maid", "butler", PluginConfig.Instance.maidEnemyValue);
foreach(var i in Assets.scrapItems){ ScarletBedroom.CreateRandomEnemyList(currentEnemies);
var entry = i.GetItemRarity();
if (entry.rarity > 0) {
Plugin.logger.LogDebug($"Adding item {entry.spawnableItem.itemName} with weight {entry.rarity}");
currentItemsRarity.Add(entry);
} }
}
Plugin.logger.LogDebug($"Loaded custom enemies and items for {level.sceneName}");
Components.ScarletBedroom.CreateRandomEnemyList(currentEnemiesRarity);
}
public static List<SpawnableEnemyWithRarity> GetEnemies(List<SpawnableEnemyWithRarity> target){
if (DunGenPatch.Patch.active && target == lastEnemiesRarity) return currentEnemiesRarity;
return target;
}
public static List<SpawnableItemWithRarity> GetItems(List<SpawnableItemWithRarity> target){
if (DunGenPatch.Patch.active && target == lastItemsRarity) return currentItemsRarity;
return target;
}
} }
} }

View File

@ -347,13 +347,26 @@ namespace ScarletMansion {
public static class ScarletNetworkManagerUtility { public static class ScarletNetworkManagerUtility {
public static int GetCriticalDamageToPlayer(PlayerControllerB player, bool forceKill) { public static int GetMarkedDamageToPlayer(PlayerControllerB player) {
if (!player.criticallyInjured && !forceKill) { if (IsPlayerMarkedForDeath(player)) return player.health;
if (!player.criticallyInjured) {
return player.health - 5; return player.health - 5;
} }
return player.health; return player.health;
} }
public static bool IsPlayerMarkedForDeath(PlayerControllerB player){
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(player);
if (scarletPlayer) return scarletPlayer.markedForDeath;
return false;
}
public static void MarkPlayerForDeath(PlayerControllerB player){
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(player);
if (scarletPlayer) scarletPlayer.markedForDeath = true;
}
public static int GetFlashlightId(FlashlightItem flashlightItem){ public static int GetFlashlightId(FlashlightItem flashlightItem){
var flashlight = Assets.GetFlashlight(flashlightItem.itemProperties); var flashlight = Assets.GetFlashlight(flashlightItem.itemProperties);

View File

@ -23,7 +23,7 @@ namespace ScarletMansion {
[BepInDependency("imabatby.lethallevelloader", "1.2.0.3")] [BepInDependency("imabatby.lethallevelloader", "1.2.0.3")]
[BepInDependency("evaisa.lethallib", "0.13.2")] [BepInDependency("evaisa.lethallib", "0.13.2")]
[BepInDependency("dev.ladyalice.dungenplus", "1.2.0")] [BepInDependency("dev.ladyalice.dungenplus", "1.3.0")]
//[BepInDependency(ModCompability.advancedCompanyGuid, BepInDependency.DependencyFlags.SoftDependency)] //[BepInDependency(ModCompability.advancedCompanyGuid, BepInDependency.DependencyFlags.SoftDependency)]
[BepInDependency(ModCompability.lethalConfigGuid, BepInDependency.DependencyFlags.SoftDependency)] [BepInDependency(ModCompability.lethalConfigGuid, BepInDependency.DependencyFlags.SoftDependency)]
@ -34,7 +34,7 @@ namespace ScarletMansion {
public class Plugin : BaseUnityPlugin { public class Plugin : BaseUnityPlugin {
public const string modGUID = "dev.ladyalice.scarletmansion"; public const string modGUID = "dev.ladyalice.scarletmansion";
private const string modName = "Scarlet Mansion"; private const string modName = "Scarlet Mansion";
private const string modVersion = "2.0.0"; private const string modVersion = "2.1.0";
public readonly Harmony harmony = new Harmony(modGUID); public readonly Harmony harmony = new Harmony(modGUID);
@ -130,6 +130,12 @@ namespace ScarletMansion {
Assets.dungeonExtended = extendedDungeon; Assets.dungeonExtended = extendedDungeon;
extendedDungeon.DungeonEvents.onBeforeDungeonGenerate.AddListener(GeneratePathPatch.GeneratePatch); extendedDungeon.DungeonEvents.onBeforeDungeonGenerate.AddListener(GeneratePathPatch.GeneratePatch);
extendedDungeon.DungeonEvents.onBeforeDungeonGenerate.AddListener(LoadAssetsIntoLevelPatch.AddItemsLocal);
DungeonManager.GlobalDungeonEvents.onBeforeDungeonGenerate.AddListener(LoadAssetsIntoLevelPatch.AddItemsGlobal);
extendedDungeon.DungeonEvents.onBeforeDungeonGenerate.AddListener(LoadAssetsIntoLevelPatch.AddEnemiesLocal);
//DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.onMainEntranceTeleportSpawnedFunction); //DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.onMainEntranceTeleportSpawnedFunction);
DunGenPlus.API.AddDunGenExtender(Assets.dungeon, Assets.dunGenExtender); DunGenPlus.API.AddDunGenExtender(Assets.dungeon, Assets.dunGenExtender);

View File

@ -33,7 +33,7 @@ namespace ScarletMansion {
public const string dungeonGenerationBPathTwoPrefix = "DunGen Branching Path 2"; public const string dungeonGenerationBPathTwoPrefix = "DunGen Branching Path 2";
public const string dungeonGenerationBPathThreePrefix = "DunGen Branching Path 3"; public const string dungeonGenerationBPathThreePrefix = "DunGen Branching Path 3";
public const string dungeonLootPrefix = "Dungeon Loot"; public const string dungeonItemPrefix = "Dungeon Items";
public const string dungeonEnemiesPrefix = "Dungeon Enemies"; public const string dungeonEnemiesPrefix = "Dungeon Enemies";
public const string dungeonFeaturesPrefix = "Dungeon Features"; public const string dungeonFeaturesPrefix = "Dungeon Features";
@ -186,7 +186,7 @@ namespace ScarletMansion {
dungeonGenerationBPathPrefix, dungeonGenerationBPathPrefix,
"Use Branch Path Multi Sim", "Use Branch Path Multi Sim",
true, true,
"If enabled, the dungeon generation will prioritize branch paths that connect to already generated tiles.\nThis increases the chance of circular/looping paths. Slows dungeon generation times a bit at the end.", "If enabled, the dungeon generation will prioritize branch paths that connect to already generated tiles.\nThis increases the chance of circular/looping paths. Slows dungeon generation times a bit at the end.\n\nOnly works when MainPathCount is more than 1.",
null null
); );
public static ConfigEntryBundle<int> branchSimCount = new ConfigEntryBundle<int>( public static ConfigEntryBundle<int> branchSimCount = new ConfigEntryBundle<int>(
@ -229,246 +229,6 @@ namespace ScarletMansion {
public BranchingPathRange branchPathSectionTwoValue = new BranchingPathRange("section two"); public BranchingPathRange branchPathSectionTwoValue = new BranchingPathRange("section two");
public BranchingPathRange branchPathSectionThreeValue = new BranchingPathRange("section three"); public BranchingPathRange branchPathSectionThreeValue = new BranchingPathRange("section three");
// loot
public static ConfigEntryBundle<float> lootMultiplier = new ConfigEntryBundle<float>(
dungeonLootPrefix,
"Loot Multiplier",
1.4f,
"Multiplies the total amount of loot for the dungeon.",
null,
new AcceptableValueRange<float>(0.25f, 4f)
);
public static ConfigEntryBundle<int> crystalWeight = new ConfigEntryBundle<int>(
dungeonLootPrefix,
"Decorative Crystal Weight",
50,
"The decorative crystal's spawn weight. Calculating spawn chance (%) is difficult as the total scrap weight for each moon varies from ~600 to ~850.",
null,
new AcceptableValueRange<int>(0, 999)
);
public static ConfigEntryBundle<int> crystalBrokenWeight = new ConfigEntryBundle<int>(
dungeonLootPrefix,
"Shattered Decorative Crystal Weight",
5,
"The shattered decorative crystal's spawn weight. Calculating spawn chance (%) is difficult as the total scrap weight for each moon varies from ~600 to ~850.",
null,
new AcceptableValueRange<int>(0, 999)
);
public static ConfigEntryBundle<int> snowGlobeWeight = new ConfigEntryBundle<int>(
dungeonLootPrefix,
"Doll Snow Globe Weight",
40,
"The doll snow globe's spawn weight. Calculating spawn chance (%) is difficult as the total scrap weight for each moon varies from ~600 to ~850.",
null,
new AcceptableValueRange<int>(0, 999)
);
public float lootMultiplierValue;
public int crystalWeightValue;
public int crystalBrokenWeightValue;
public int snowGlobeWeightValue;
// enemies
public static ConfigEntryBundle<float> mapHazardsMultiplier = new ConfigEntryBundle<float>(
dungeonEnemiesPrefix,
"Map Hazards Multiplier",
1.6f,
"Multiplies the total amount of map hazards (landmines, turrets) for the dungeon.",
null,
new AcceptableValueRange<float>(0.25f, 4f)
);
public static ConfigEntryBundle<int> minIndoorEnemySpawnCount = new ConfigEntryBundle<int>(
dungeonEnemiesPrefix,
"Minimum Indoor Enemy Spawn Count",
1,
"Increases the minimum amount of indoor enemies that spawn with each spawn wave. For reference, Eclipse is +3.",
null,
new AcceptableValueRange<int>(0, 3)
);
public static ConfigEntryBundle<float> knightWeightStealPercentage = new ConfigEntryBundle<float>(
dungeonEnemiesPrefix,
"Knight Weight Steal Percentage",
0.75f,
"The percentage of spawn weight that the knight steals from the coil-head for that moon.\nSetting this 0 means that the coil-head's weight is unaffected and the knight's weight is based entirely by Knight Weight Base.\nSetting this 1 means the knight effectively replaces the coil-head.\nIf the moon doesn't spawn the coil-head, the knight's base weight is set to 10.",
null,
new AcceptableValueRange<float>(0f, 1f)
);
public static ConfigEntryBundle<int> knightWeightBase = new ConfigEntryBundle<int>(
dungeonEnemiesPrefix,
"Knight Weight Base",
0,
"The base spawn weight of the knight. This is added onto the spawn weight stolen from the coil-head, or the base weight of 10 if the moon doesn't spawn the coil-head.",
null,
new AcceptableValueRange<int>(0, 999)
);
public static ConfigEntryBundle<float> maidWeightStealPercentage = new ConfigEntryBundle<float>(
dungeonEnemiesPrefix,
"Maid Weight Steal Percentage",
0.75f,
"The percentage of spawn weight that the maid steals from the butler for that moon.\nSetting this 0 means that the butler's weight is unaffected and the maid's weight is based entirely by Maid Weight Base.\nSetting this 1 means the maid effectively replaces the butler.\nIf the moon doesn't spawn the bulter, the maid's base weight is set to 10.",
null,
new AcceptableValueRange<float>(0f, 1f)
);
public static ConfigEntryBundle<int> maidWeightBase = new ConfigEntryBundle<int>(
dungeonEnemiesPrefix,
"Maid Weight Base",
0,
"The base spawn weight of the maid. This is added onto the spawn weight stolen from the butler, or the base weight of 10 if the moon doesn't spawn the butler.",
null,
new AcceptableValueRange<int>(0, 999)
);
public float mapHazardsMultiplierValue;
public int minIndoorEnemySpawnCountValue;
public float knightWeightStealPercentageValue;
public int knightWeightBaseValue;
public float maidWeightStealPercentageValue;
public int maidWeightBaseValue;
// features
public static ConfigEntryBundle<int> shovelDamage = new ConfigEntryBundle<int>(
dungeonFeaturesPrefix,
"Door Shovel Damage",
45,
"The amount of damage done by swinging your shovel at a SDM door. Door health equals to 100.",
null,
new AcceptableValueRange<int>(0, 100)
);
public static ConfigEntryBundle<int> shotgunDamage = new ConfigEntryBundle<int>(
dungeonFeaturesPrefix,
"Door Shotgun Damage",
100,
"The amount of damage done by shotgun blasting at a SDM door. Door health equals to 100.",
null,
new AcceptableValueRange<int>(0, 100)
);
public static ConfigEntryBundle<float> lockedDoorEnemyDamageMultiplier = new ConfigEntryBundle<float>(
dungeonFeaturesPrefix,
"Locked Door Enemy Damage Multiplier",
0.5f,
"The damage multiplier applied to an enemy's attacks against a locked SDM door.\nSetting the value to 0 means that enemies can't break through a locked SDM door like in vanilla.",
null,
new AcceptableValueRange<float>(0f, 1f)
);
public static ConfigEntryBundle<bool> useSDMFireExits = new ConfigEntryBundle<bool>(
dungeonFeaturesPrefix,
"Use SDM Fire Exits",
true,
"If enabled, the dungeon will spawn its custom fire exits. If disabled, the dungeon will instead spawn the vanilla fire exits."
);
public int shovelDamageValue;
public int shotgunDamageValue;
public float lockedDoorEnemyDamageMultiplierValue;
public bool useSDMFireExitsValue;
// painting
public static ConfigEntryBundle<int> paintingValue = new ConfigEntryBundle<int>(
dungeonPaintingEventPrefix,
"Painting Value",
100,
"The scrap value of demonic paintings.",
null,
new AcceptableValueRange<int>(0, 400)
);
public static ConfigEntryBundle<int> paintingCount = new ConfigEntryBundle<int>(
dungeonPaintingEventPrefix,
"Painting Count",
2,
"The maximum amount of demonic paintings that spawn in the dungeon.",
null,
new AcceptableValueRange<int>(0, 10)
);
public static ConfigEntryBundleMinMax<int> paintingExtraLoot = new ConfigEntryBundleMinMax<int>(
dungeonPaintingEventPrefix,
"Painting Extra Loot Min",
"Painting Extra Loot Max",
0,
2,
"The minimum allowed amount of extra loot that spawns after grabbing the demonic painting.",
"The maximum allowed amount of extra loot that spawns after grabbing the demonic painting.",
null,
new AcceptableValueRange<int>(0, 3)
);
public static ConfigEntryBundle<bool> paintingSpawnEnemy = new ConfigEntryBundle<bool>(
dungeonPaintingEventPrefix,
"Spawn Enemy",
true,
"If enabled, an enemy will spawn when the bedroom's painting event ends."
);
public static ConfigEntryBundle<string> paintingEnemyList = new ConfigEntryBundle<string>(
dungeonPaintingEventPrefix,
"Enemy List",
"default",
$"The enemies that can spawn when the bedroom's painting event ends, in the form of a comma separated list of enemy names along with optional parameters separated or '@'.It can accept either the enemy's internal or display name.\n\nThe available parameters are:\nSpawn Logic: 's#' where '#' is either '0' for normal spawn logic or '1' for special spawn logic. Only the knight and jester have special spawn logic. When their special spawn logic is enabled, the knight will spawn properly in the bedroom and the jester will spawn already cranking.\n\nThe following string \"default\" is a preset that uses the following string \"{ScarletBedroom.ENEMY_SPAWN_LIST_DEFAULT}\"."
);
public static ConfigEntryBundle<bool> paintingEnemyEvilSkin = new ConfigEntryBundle<bool>(
dungeonPaintingEventPrefix,
"Invader Apperance",
true,
"Enemies spawned from the bedroom's painting event has a pitch black and red appearance. Only valid for the default enemy list."
);
public static ConfigEntryBundle<bool> facilityMeltdownActive = new ConfigEntryBundle<bool>(
dungeonPaintingEventPrefix,
"Activate Facility Meltdown",
false,
"If enabled, facility meltdown will activate when the bedroom's painting event ends. Requires FacilityMeltdown v2.4.7 or higher."
);
public int paintingValueValue;
public int paintingCountValue;
public IntRange paintingExtraLootValue = new IntRange("painting extra loot");
public bool paintingSpawnEnemyValue;
public string paintingEnemyListValue;
public bool paintingEnemyEvilSkinValue;
public bool facilityMeltdownActiveValue;
// treasure room
public static ConfigEntryBundle<int> treasureRoomCount = new ConfigEntryBundle<int>(
dungeonTreasureRoomPrefix,
"Treasure Room Count",
2,
"The maximum amount of treasure rooms that spawn in the dungeon.",
null,
new AcceptableValueRange<int>(0, 10)
);
public static ConfigEntryBundleMinMax<int> treasureRoomLoot = new ConfigEntryBundleMinMax<int>(
dungeonTreasureRoomPrefix,
"Treasure Room Loot Min",
"Treasure Room Loot Max",
2,
3,
"The minimum allowed amount of loot that spawns in the treasure room.",
"The maximum allowed amount of loot that spawns in the treasure room.",
null,
new AcceptableValueRange<int>(0, 6)
);
public int treasureRoomCountValue;
public IntRange treasureRoomLootValue = new IntRange("treasure room loot");
// lighting // lighting
public static ConfigEntryBundle<int> hallwayLightsWeight = new ConfigEntryBundle<int>( public static ConfigEntryBundle<int> hallwayLightsWeight = new ConfigEntryBundle<int>(
dungeonLightingPrefix, dungeonLightingPrefix,
@ -532,6 +292,198 @@ namespace ScarletMansion {
public int lightsSpawnTwoWeightValue; public int lightsSpawnTwoWeightValue;
//public int lightsSpawnThreeWeightValue; //public int lightsSpawnThreeWeightValue;
// loot
public static ConfigEntryBundle<float> lootMultiplier = new ConfigEntryBundle<float>(
dungeonItemPrefix,
"Loot Multiplier",
1.4f,
"Multiplies the total amount of loot for the dungeon.",
null,
new AcceptableValueRange<float>(0.25f, 4f)
);
public static ConfigEntryBundleItem decoFlashlight = new ConfigEntryBundleItem(dungeonItemPrefix, "Deco Pro Flashlight ", "Deco Pro Flashlight", 0, 0);
public static ConfigEntryBundleItem decoProFlashlight = new ConfigEntryBundleItem(dungeonItemPrefix, "Deco Flashlight ", "Deco Flashlight", 0, 0);
public static ConfigEntryBundleItem scarletKey = new ConfigEntryBundleItem(dungeonItemPrefix, "Scarlet Key ", "Scarlet Key", 0, 0);
public float lootMultiplierValue;
public Item decoFlashlightValue = new Item("Deco Flashlight");
public Item decoProFlashlightValue = new Item("Deco Pro Flashlight");
public Item scarletKeyValue = new Item("Scarlet Key");
public static ConfigEntryBundleScrapItem crystal = new ConfigEntryBundleScrapItem("Dungeon Item Deco Crystal", "Decorative Crystal", 110, 150, 50);
public static ConfigEntryBundleScrapItem crystalBroken = new ConfigEntryBundleScrapItem("Dungeon Item Shattered Deco Crystal", "Shattered Decorative Crystal", 60, 80, 5);
public static ConfigEntryBundleScrapItem snowGlobe = new ConfigEntryBundleScrapItem("Dungeon Item Snow Globe", "Snow Globe", 150, 320, 40);
public ScrapItem crystalValue = new ScrapItem("Crystal");
public ScrapItem crystalBrokenValue = new ScrapItem("Shattered Crystal");
public ScrapItem snowGlobeValue = new ScrapItem("Snow Globe");
// enemies
public static ConfigEntryBundle<float> mapHazardsMultiplier = new ConfigEntryBundle<float>(
dungeonEnemiesPrefix,
"Map Hazards Multiplier",
1.6f,
"Multiplies the total amount of map hazards (landmines, turrets) for the dungeon.",
null,
new AcceptableValueRange<float>(0.25f, 4f)
);
public static ConfigEntryBundle<int> minIndoorEnemySpawnCount = new ConfigEntryBundle<int>(
dungeonEnemiesPrefix,
"Minimum Indoor Enemy Spawn Count",
1,
"Increases the minimum amount of indoor enemies that spawn with each spawn wave. For reference, Eclipse is +3.",
null,
new AcceptableValueRange<int>(0, 3)
);
public float mapHazardsMultiplierValue;
public int minIndoorEnemySpawnCountValue;
public static ConfigEntryBundleSpawnableEnemy knightEnemy = new ConfigEntryBundleSpawnableEnemy("Dungeon Enemy Knight", "Knight", "Coil-head", 0, 13f);
public static ConfigEntryBundleSpawnableEnemy maidEnemy = new ConfigEntryBundleSpawnableEnemy("Dungeon Enemy Maid", "Maid", "Butler", 6, 5.5f);
public static ConfigEntryBundleEnemy revEnemy = new ConfigEntryBundleEnemy("Dungeon Enemy Revenant", "Revenant", 0, 14.5f);
public SpawnableEnemy knightEnemyValue = new SpawnableEnemy();
public SpawnableEnemy maidEnemyValue = new SpawnableEnemy();
public Enemy revEnemyValue = new Enemy();
// features
public static ConfigEntryBundle<int> shovelDamage = new ConfigEntryBundle<int>(
dungeonFeaturesPrefix,
"Door Shovel Damage",
45,
"The amount of damage done by swinging your shovel at a SDM door. Door health equals to 100.",
null,
new AcceptableValueRange<int>(0, 100)
);
public static ConfigEntryBundle<int> shotgunDamage = new ConfigEntryBundle<int>(
dungeonFeaturesPrefix,
"Door Shotgun Damage",
100,
"The amount of damage done by shotgun blasting at a SDM door. Door health equals to 100.",
null,
new AcceptableValueRange<int>(0, 100)
);
public static ConfigEntryBundle<float> lockedDoorEnemyDamageMultiplier = new ConfigEntryBundle<float>(
dungeonFeaturesPrefix,
"Locked Door Enemy Damage Multiplier",
0.5f,
"The damage multiplier applied to an enemy's attacks against a locked SDM door.\nSetting the value to 0 means that enemies can't break through a locked SDM door like in vanilla.",
null,
new AcceptableValueRange<float>(0f, 1f)
);
public static ConfigEntryBundle<bool> useSDMFireExits = new ConfigEntryBundle<bool>(
dungeonFeaturesPrefix,
"Use SDM Fire Exits",
true,
"If enabled, the dungeon will spawn its custom fire exits. If disabled, the dungeon will instead spawn the vanilla fire exits."
);
public int shovelDamageValue;
public int shotgunDamageValue;
public float lockedDoorEnemyDamageMultiplierValue;
public bool useSDMFireExitsValue;
// painting
public static ConfigEntryBundle<int> paintingCount = new ConfigEntryBundle<int>(
dungeonPaintingEventPrefix,
"Painting Count",
2,
"The maximum amount of demonic paintings that spawn in the dungeon.",
null,
new AcceptableValueRange<int>(0, 10)
);
public static ConfigEntryBundle<int> paintingValue = new ConfigEntryBundle<int>(
dungeonPaintingEventPrefix,
"Painting Value",
100,
"The scrap value of demonic paintings.",
null,
new AcceptableValueRange<int>(0, 400)
);
public static ConfigEntryBundleMinMax<int> paintingExtraLoot = new ConfigEntryBundleMinMax<int>(
dungeonPaintingEventPrefix,
"Painting Extra Loot Min",
"Painting Extra Loot Max",
0,
2,
"The minimum allowed amount of extra loot that spawns after grabbing the demonic painting.",
"The maximum allowed amount of extra loot that spawns after grabbing the demonic painting.",
null,
new AcceptableValueRange<int>(0, 3)
);
public static ConfigEntryBundle<bool> paintingSpawnEnemy = new ConfigEntryBundle<bool>(
dungeonPaintingEventPrefix,
"Spawn Enemy",
true,
"If enabled, an enemy will spawn when the bedroom's painting event ends."
);
public static ConfigEntryBundle<string> paintingEnemyList = new ConfigEntryBundle<string>(
dungeonPaintingEventPrefix,
"Enemy List",
"default",
$"The enemies that can spawn when the bedroom's painting event ends, in the form of a comma separated list of enemy names along with optional parameters separated or '@'.It can accept either the enemy's internal or display name.\n\nThe available parameters are:\nSpawn Logic: 's#' where '#' is either '0' for normal spawn logic or '1' for special spawn logic. Only the knight and jester have special spawn logic. When their special spawn logic is enabled, the knight will spawn properly in the bedroom and the jester will spawn already cranking.\n\nThe following string \"default\" is a preset that uses the following string \"{ScarletBedroom.ENEMY_SPAWN_LIST_DEFAULT}\"."
);
public static ConfigEntryBundle<bool> paintingEnemyEvilSkin = new ConfigEntryBundle<bool>(
dungeonPaintingEventPrefix,
"Invader Apperance",
true,
"Enemies spawned from the bedroom's painting event has a pitch black and red appearance. Only valid for the default enemy list."
);
public static ConfigEntryBundle<bool> facilityMeltdownActive = new ConfigEntryBundle<bool>(
dungeonPaintingEventPrefix,
"Activate Facility Meltdown",
false,
"If enabled, facility meltdown will activate when the bedroom's painting event ends. Requires FacilityMeltdown v2.4.7 or higher."
);
public int paintingCountValue;
public int paintingValueValue;
public IntRange paintingExtraLootValue = new IntRange("painting extra loot");
public bool paintingSpawnEnemyValue;
public string paintingEnemyListValue;
public bool paintingEnemyEvilSkinValue;
public bool facilityMeltdownActiveValue;
// treasure room
public static ConfigEntryBundle<int> treasureRoomCount = new ConfigEntryBundle<int>(
dungeonTreasureRoomPrefix,
"Treasure Room Count",
2,
"The maximum amount of treasure rooms that spawn in the dungeon.",
null,
new AcceptableValueRange<int>(0, 10)
);
public static ConfigEntryBundleMinMax<int> treasureRoomLoot = new ConfigEntryBundleMinMax<int>(
dungeonTreasureRoomPrefix,
"Treasure Room Loot Min",
"Treasure Room Loot Max",
2,
3,
"The minimum allowed amount of loot that spawns in the treasure room.",
"The maximum allowed amount of loot that spawns in the treasure room.",
null,
new AcceptableValueRange<int>(0, 6)
);
public int treasureRoomCountValue;
public IntRange treasureRoomLootValue = new IntRange("treasure room loot");
public static string GetEnumNames<T>() where T: Enum { public static string GetEnumNames<T>() where T: Enum {
var str = string.Empty; var str = string.Empty;
var enums = Enum.GetNames(typeof(T)).Select(e => $"\"{e}\""); var enums = Enum.GetNames(typeof(T)).Select(e => $"\"{e}\"");

View File

@ -12,6 +12,7 @@ using BepInEx.Configuration;
using BepInEx.Logging; using BepInEx.Logging;
using UnityEngine; using UnityEngine;
using System.Reflection; using System.Reflection;
using static ScarletMansion.PluginConfig;
namespace ScarletMansion { namespace ScarletMansion {
public partial class PluginConfig : SyncedInstance<PluginConfig> { public partial class PluginConfig : SyncedInstance<PluginConfig> {
@ -52,6 +53,7 @@ namespace ScarletMansion {
public override string ToString() { public override string ToString() {
return $"({min}, {max})"; return $"({min}, {max})";
} }
} }
[System.Serializable] [System.Serializable]
@ -67,6 +69,7 @@ namespace ScarletMansion {
public DunGen.IntRange GetDungenIntRange(){ public DunGen.IntRange GetDungenIntRange(){
return new DunGen.IntRange(min, max); return new DunGen.IntRange(min, max);
} }
} }
[System.Serializable] [System.Serializable]
@ -106,6 +109,37 @@ namespace ScarletMansion {
} }
} }
[System.Serializable]
public class Enemy {
public bool enabled;
public int health;
public float speed;
}
[System.Serializable]
public class SpawnableEnemy : Enemy {
public int spawnWeightBase;
public float spawnWeightStealPercentage;
}
[System.Serializable]
public class Item {
public bool enabled;
public IntRange valueRange;
public Item(string name) {
valueRange = new IntRange($"{name} value range");
}
}
[System.Serializable]
public class ScrapItem : Item {
public int spawnWeight;
public bool spawnOnAllMoons;
public ScrapItem(string name) : base(name) { }
}
public abstract class ConfigEntryBundleBase { public abstract class ConfigEntryBundleBase {
public class FieldPropertyInfo { public class FieldPropertyInfo {
@ -138,7 +172,7 @@ namespace ScarletMansion {
} }
} }
public abstract ConfigEntryBase[] GetConfigs(); public abstract IEnumerable<ConfigEntryBase> GetConfigs();
public abstract void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget); public abstract void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget);
} }
@ -196,8 +230,8 @@ namespace ScarletMansion {
extra.CallAction(instance, true); extra.CallAction(instance, true);
} }
public override ConfigEntryBase[] GetConfigs() { public override IEnumerable<ConfigEntryBase> GetConfigs() {
return new ConfigEntryBase[] { config }; yield return config;
} }
} }
@ -219,8 +253,9 @@ namespace ScarletMansion {
max.Bind(cfg, instance, new FieldPropertyInfo(maxProp), memberInfo.GetValue(memberTarget)); max.Bind(cfg, instance, new FieldPropertyInfo(maxProp), memberInfo.GetValue(memberTarget));
} }
public override ConfigEntryBase[] GetConfigs() { public override IEnumerable<ConfigEntryBase> GetConfigs() {
return new ConfigEntryBase[] { min.config, max.config }; yield return min.config;
yield return max.config;
} }
} }
@ -262,8 +297,157 @@ namespace ScarletMansion {
depth.Bind(cfg, instance, new FieldPropertyInfo(depthField), memberInfo.GetValue(memberTarget)); depth.Bind(cfg, instance, new FieldPropertyInfo(depthField), memberInfo.GetValue(memberTarget));
} }
public override ConfigEntryBase[] GetConfigs() { public override IEnumerable<ConfigEntryBase> GetConfigs() {
return new ConfigEntryBase[] { count.min.config, count.max.config, depth.min.config, depth.max.config }; foreach(var a in count.GetConfigs()) {
yield return a;
}
foreach(var a in depth.GetConfigs()) {
yield return a;
}
}
}
public class ConfigEntryBundleEnemy : ConfigEntryBundleBase {
public ConfigEntryBundle<bool> enabled;
public ConfigEntryBundle<int> health;
public ConfigEntryBundle<float> speed;
public ConfigEntryBundleEnemy(string section, string enemyName, int baseHealth, float baseSpeed, ConfigEntryBundleExtraParameters extra = null){
var enabledDesc = $"If disabled, {enemyName} is disabled from spawning.";
var healthDesc = $"The health of the {enemyName}. A shovel is 1 damage.";
var speedDesc = $"The speed of the {enemyName} during their killing/aggressive stance. The player's default speed is 4.6.";
enabled = new ConfigEntryBundle<bool>(section, "Enabled", true, enabledDesc, extra);
health = new ConfigEntryBundle<int>(section, "Health", baseHealth, healthDesc, extra, new AcceptableValueRange<int>(1, 20));
speed = new ConfigEntryBundle<float>(section, "Speed", baseSpeed, speedDesc, extra, new AcceptableValueRange<float>(1f, 20f));
}
public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) {
var type = memberInfo.GetFieldPropertyType();
var enabledProp = type.GetField("enabled", BindingFlags.Public | BindingFlags.Instance);
var healthProp = type.GetField("health", BindingFlags.Public | BindingFlags.Instance);
var speedProp = type.GetField("speed", BindingFlags.Public | BindingFlags.Instance);
enabled.Bind(cfg, instance, new FieldPropertyInfo(enabledProp), memberInfo.GetValue(memberTarget));
if (health.defaultValue > 0) health.Bind(cfg, instance, new FieldPropertyInfo(healthProp), memberInfo.GetValue(memberTarget));
speed.Bind(cfg, instance, new FieldPropertyInfo(speedProp), memberInfo.GetValue(memberTarget));
}
public override IEnumerable<ConfigEntryBase> GetConfigs() {
yield return enabled.config;
if (health.defaultValue > 0) yield return health.config;
yield return speed.config;
}
}
public class ConfigEntryBundleSpawnableEnemy : ConfigEntryBundleEnemy {
public ConfigEntryBundle<int> spawnWeightBase;
public ConfigEntryBundle<float> spawnWeightStealPercentage;
public ConfigEntryBundleSpawnableEnemy(string section, string enemyName, string replacementEnemyName, int baseHealth, float baseSpeed, ConfigEntryBundleExtraParameters extra = null) : base(section, enemyName, baseHealth, baseSpeed, extra) {
var weightBaseDesc = $"The base spawn weight of the {enemyName}. This is added onto the spawn weight stolen from the {replacementEnemyName}, or the base weight of 10 if the moon doesn't spawn the {replacementEnemyName}.";
var weightStealPercentageDesc = $"The percentage of spawn weight that the {enemyName} steals from the {replacementEnemyName} for that moon.\nSetting this 0 means that the {replacementEnemyName}'s weight is unaffected and the {enemyName}'s weight is based entirely by Spawn Weight Base.\nSetting this 1 means the {enemyName} effectively replaces the {replacementEnemyName}.";
spawnWeightBase = new ConfigEntryBundle<int>(section, "Spawn Weight Base", 0, weightBaseDesc, extra, new AcceptableValueRange<int>(0, 999));
spawnWeightStealPercentage = new ConfigEntryBundle<float>(section, "Spawn Weight Steal Percentage", 0.75f, weightStealPercentageDesc, extra, new AcceptableValueRange<float>(0f, 1f));
}
public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) {
base.Bind(cfg, instance, memberInfo, memberTarget);
var type = memberInfo.GetFieldPropertyType();
var weightBaseProp = type.GetField("spawnWeightBase", BindingFlags.Public | BindingFlags.Instance);
var weightStealPercentageProp = type.GetField("spawnWeightStealPercentage", BindingFlags.Public | BindingFlags.Instance);
spawnWeightBase.Bind(cfg, instance, new FieldPropertyInfo(weightBaseProp), memberInfo.GetValue(memberTarget));
spawnWeightStealPercentage.Bind(cfg, instance, new FieldPropertyInfo(weightStealPercentageProp), memberInfo.GetValue(memberTarget));
}
public override IEnumerable<ConfigEntryBase> GetConfigs() {
foreach(var a in base.GetConfigs()) {
yield return a;
}
yield return spawnWeightBase.config;
yield return spawnWeightStealPercentage.config;
}
}
public class ConfigEntryBundleItem : ConfigEntryBundleBase {
public ConfigEntryBundle<bool> enabled;
public ConfigEntryBundleMinMax<int> valueRange;
private static string _valueMessage(string itemName, string minmax) => $"The {minmax} scrap value of {itemName}. Lethal Company multiplies all scrap values by 0.4.";
public ConfigEntryBundleItem(string section, string keyPrefix, string itemName, int baseValueMin, int baseValueMax, ConfigEntryBundleExtraParameters extra = null){
var enabledDesc = $"If disabled, {itemName} is disabled from spawning.";
enabled = new ConfigEntryBundle<bool>(section, $"{keyPrefix}Enabled", true, enabledDesc, extra);
valueRange = new ConfigEntryBundleMinMax<int>(
section,
$"Value Min",
$"Value Max",
baseValueMin,
baseValueMax,
_valueMessage(itemName, "minimum"),
_valueMessage(itemName, "maximum"),
extra,
new AcceptableValueRange<int>(1, 400)
);
}
public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) {
var type = memberInfo.GetFieldPropertyType();
var enabledProp = type.GetField("enabled", BindingFlags.Public | BindingFlags.Instance);
var valueRangeProp = type.GetField("valueRange", BindingFlags.Public | BindingFlags.Instance);
enabled.Bind(cfg, instance, new FieldPropertyInfo(enabledProp), memberInfo.GetValue(memberTarget));
if (valueRange.min.defaultValue > 0) valueRange.Bind(cfg, instance, new FieldPropertyInfo(valueRangeProp), memberInfo.GetValue(memberTarget));
}
public override IEnumerable<ConfigEntryBase> GetConfigs() {
yield return enabled.config;
if (valueRange.min.defaultValue > 0) {
foreach(var a in valueRange.GetConfigs()) {
yield return a;
}
}
}
}
public class ConfigEntryBundleScrapItem : ConfigEntryBundleItem {
public ConfigEntryBundle<int> spawnWeight;
public ConfigEntryBundle<bool> spawnOnAllMoons;
public ConfigEntryBundleScrapItem(string section, string itemName, int baseValueMin, int baseValueMax, int baseSpawnWeight, ConfigEntryBundleExtraParameters extra = null) : base(section, string.Empty, itemName, baseValueMin, baseValueMax, extra) {
var weightDesc = $"The {itemName}'s spawn weight. Calculating spawn chance (%) is difficult as the total scrap weight for each moon varies from ~600 to ~850.";
var spawnDesc = $"If enabled, the {itemName} scrap item will spawn on all moons regardless if SDM loaded.";
spawnWeight = new ConfigEntryBundle<int>(section, "Spawn Weight Base", baseSpawnWeight, weightDesc, extra, new AcceptableValueRange<int>(0, 999));
spawnOnAllMoons = new ConfigEntryBundle<bool>(section, "Spawn On All Moons", false, spawnDesc, extra);
}
public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) {
base.Bind(cfg, instance, memberInfo, memberTarget);
var type = memberInfo.GetFieldPropertyType();
var weightDescProp = type.GetField("spawnWeight", BindingFlags.Public | BindingFlags.Instance);
var spawnDescProp = type.GetField("spawnOnAllMoons", BindingFlags.Public | BindingFlags.Instance);
spawnWeight.Bind(cfg, instance, new FieldPropertyInfo(weightDescProp), memberInfo.GetValue(memberTarget));
spawnOnAllMoons.Bind(cfg, instance, new FieldPropertyInfo(spawnDescProp), memberInfo.GetValue(memberTarget));
}
public override IEnumerable<ConfigEntryBase> GetConfigs() {
foreach(var a in base.GetConfigs()) {
yield return a;
}
yield return spawnWeight.config;
yield return spawnOnAllMoons.config;
} }
} }

View File

@ -110,11 +110,11 @@ namespace ScarletMansion {
new ChangeFloat ( PluginConfig.dunGenLengthMultiFactor, 4f ), new ChangeFloat ( PluginConfig.dunGenLengthMultiFactor, 4f ),
new ChangeInt ( PluginConfig.mainPathCount, 1 ), new ChangeInt ( PluginConfig.mainPathCount, 1 ),
new ChangeMinMaxInt ( PluginConfig.mainPathLength, 6, 9 ), new ChangeMinMaxInt ( PluginConfig.mainPathLength, 6, 8 ),
new ChangeBranchingPath( PluginConfig.branchPathSectionOne, 4, 6, 3, 4 ), new ChangeBranchingPath( PluginConfig.branchPathSectionOne, 3, 5, 2, 3 ),
new ChangeBranchingPath( PluginConfig.branchPathSectionTwo, 2, 3, 2, 3 ), new ChangeBranchingPath( PluginConfig.branchPathSectionTwo, 2, 3, 1, 2 ),
new ChangeBranchingPath( PluginConfig.branchPathSectionThree, 1, 2, 1, 2 ), new ChangeBranchingPath( PluginConfig.branchPathSectionThree, 1, 2, 0, 1 ),
new ChangeFloat ( PluginConfig.lootMultiplier, 1f ), new ChangeFloat ( PluginConfig.lootMultiplier, 1f ),

View File

@ -251,7 +251,7 @@ namespace ScarletMansion {
} }
public static List<SpawnableItemWithRarity> GetDungeonItems(){ public static List<SpawnableItemWithRarity> GetDungeonItems(){
return LoadAssetsIntoLevelPatch.GetItems(RoundManager.Instance.currentLevel.spawnableScrap); return RoundManager.Instance.currentLevel.spawnableScrap;
} }
public static int GetDungeonItemId(Item item, bool debug = false){ public static int GetDungeonItemId(Item item, bool debug = false){