From cdadd75ee93dfd2c1e613c05726d27202c9eac2a Mon Sep 17 00:00:00 2001 From: LadyAliceMargatroid Date: Sun, 28 Apr 2024 14:41:33 -0700 Subject: [PATCH] Initial --- .gitignore | 7 + ScarletMansion/ScarletMansion.sln | 25 + ScarletMansion/ScarletMansion/Assets.cs | 248 +++++++++ .../ScarletMansion/DunGenAnalyis.cs | 235 ++++++++ .../DoorwayConnectionSisterChain.cs | 60 ++ .../DoorwayConnectionSisterRuleInfo.cs | 47 ++ .../Components/MainRoomDoorwayGroups.cs | 24 + .../RemoveConnectorIfConnectedDoorwayBasic.cs | 20 + .../RemoveGameObjectsBasedOnCBSelected.cs | 37 ++ ...emoveGameObjectsBasedOnConnectedDoorway.cs | 63 +++ ...SwitchConnectorBlockerBasedOnCBSelected.cs | 42 ++ .../DunGenPatch/DoorwayConnectionPatch.cs | 62 +++ .../DoorwayConnectionSisterRule.cs | 91 ++++ .../DunGenPatch/Doorways/DCleanBase.cs | 20 + .../DCleanConnectorBlockerBasedOnSelected.cs | 30 + .../Doorways/DCleanDoorwayCompare.cs | 45 ++ ...DCleanRemoveDoorwayBasedOnConnectedDoor.cs | 26 + .../DCleanRemoveDoorwayBasedOnSelected.cs | 28 + ...anRemoveGameObjectsBasedOnConnectedDoor.cs | 26 + .../DunGenPatch/Doorways/DoorwayCleanup.cs | 69 +++ .../DunGenPatch/GeneratePath.cs | 269 +++++++++ .../DunGenPatch/GeneratePathPatch.cs | 58 ++ .../DunGenPatch/OptimizePatch.cs | 59 ++ .../ScarletMansion/DunGenPatch/Patch.cs | 122 +++++ .../DunGenPatch/PostProcessPatch.cs | 24 + .../Components/FloorCleanUpParent.cs | 27 + .../GamePatch/Components/FloorCleanup.cs | 71 +++ .../GamePatch/Components/KnightSpawnPoint.cs | 50 ++ .../Components/Lights/ScarletLight.cs | 64 +++ .../Components/Lights/ScarletLightCleanup.cs | 58 ++ .../GamePatch/Components/ScarletBedroom.cs | 247 +++++++++ .../GamePatch/Components/ScarletClock.cs | 122 +++++ .../GamePatch/Components/ScarletDoor.cs | 70 +++ .../GamePatch/Components/ScarletDoorLock.cs | 207 +++++++ .../GamePatch/Components/ScarletFireExit.cs | 71 +++ .../GamePatch/Components/ScarletFrame.cs | 50 ++ .../GamePatch/Components/ScarletHDRISky.cs | 85 +++ .../GamePatch/Components/ScarletLighting.cs | 19 + .../Components/ScarletPlayerControllerB.cs | 46 ++ .../GamePatch/Components/ScarletProp.cs | 52 ++ .../GamePatch/Components/ScarletVent.cs | 67 +++ .../ScarletMansion/GamePatch/DoorLockPatch.cs | 36 ++ .../GamePatch/Enemies/KnightVariant.cs | 274 ++++++++++ .../GamePatch/EnemyVentPatch.cs | 48 ++ .../GamePatch/ExtendedDungeonMapLoad.cs | 214 ++++++++ .../GamePatch/FixValues/FixBaseClass.cs | 20 + .../FixValues/FixCeilingLightValue.cs | 20 + .../GamePatch/FixValues/FixFireExit.cs | 84 +++ .../FixValues/FixHallwayLightValue.cs | 20 + .../GamePatch/FixValues/FixHoverIcon.cs | 18 + .../GamePatch/FixValues/FixRandomMapObject.cs | 38 ++ .../GamePatch/FixValues/FixSpawnItemGroup.cs | 34 ++ .../ScarletMansion/GamePatch/InitPatch.cs | 414 ++++++++++++++ .../GamePatch/Items/FlandreCrystal.cs | 144 +++++ .../GamePatch/Items/IScarletItem.cs | 13 + .../GamePatch/Items/ScarletFlashlight.cs | 104 ++++ .../GamePatch/Items/ScarletPainting.cs | 104 ++++ .../ScarletMansion/GamePatch/JesterAIPatch.cs | 26 + .../GamePatch/LoadAssetsIntoLevelPatch.cs | 167 ++++++ .../GamePatch/Managers/AngerManager.cs | 257 +++++++++ .../GamePatch/Managers/DoorwayManager.cs | 37 ++ .../GamePatch/Managers/KnightSpawnManager.cs | 71 +++ .../Managers/ScarletLightingManager.cs | 55 ++ .../Managers/ScarletNetworkManager.cs | 409 ++++++++++++++ .../GamePatch/MenuManagerPatch.cs | 37 ++ .../GamePatch/PlayerControllerBPatch.cs | 27 + .../Props/FireExitEmptySpaceCheck.cs | 34 ++ .../GamePatch/Props/FloorPropBasedOnFloor.cs | 47 ++ .../GamePatch/Props/GlobalPropWithChildren.cs | 11 + .../GamePatch/Props/LocalPropBasic.cs | 32 ++ .../GamePatch/Props/LocalPropSingle.cs | 38 ++ .../GamePatch/Props/RandomPrefabBasic.cs | 23 + .../GamePatch/Props/RandomPrefabBasicBase.cs | 48 ++ .../GamePatch/Props/RandomPrefabCycle.cs | 31 ++ .../GamePatch/Props/RandomPrefabWithScale.cs | 32 ++ .../GamePatch/Props/SpawnSyncedObjectCycle.cs | 49 ++ .../GamePatch/RoundManagerPatch.cs | 60 ++ .../GamePatch/ScarletLightPatch.cs | 60 ++ .../GamePatch/ShotgunItemPatch.cs | 22 + .../ScarletMansion/GamePatch/ShovelPatch.cs | 28 + .../NetworkObjectListScriptableObject.cs | 33 ++ .../ScarletMansion/MainMenuUpdate.cs | 87 +++ .../ModPatch/AdvancedCompanyPatch.cs | 37 ++ .../ModPatch/FacilityMeltdownPatch.cs | 27 + .../ModPatch/LethalConfigPatch.cs | 157 ++++++ .../ScarletMansion/ModPatch/MimicsPatch.cs | 37 ++ .../ScarletMansion/ModPatch/ModCompability.cs | 41 ++ .../ScarletMansion/ModPatch/ModPatch.cs | 56 ++ .../ModPatch/ReservedItemSlotPatch.cs | 29 + ScarletMansion/ScarletMansion/Plugin.cs | 160 ++++++ ScarletMansion/ScarletMansion/PluginConfig.cs | 512 ++++++++++++++++++ .../ScarletMansion/PluginConfigClasses.cs | 271 +++++++++ .../ScarletMansion/PluginConfigNetwork.cs | 83 +++ ScarletMansion/ScarletMansion/PresetConfig.cs | 373 +++++++++++++ .../ScarletMansion/PrintProperties.cs | 121 +++++ .../ScarletMansion/Properties/AssemblyInfo.cs | 36 ++ .../ScarletMansion/ScarletMansion.csproj | 261 +++++++++ .../ScarletMansion/SyncedInstance.cs | 83 +++ .../ScarletMansion/TranspilerUtilities.cs | 166 ++++++ ScarletMansion/ScarletMansion/Utility.cs | 372 +++++++++++++ .../ScarletMansionMimicsPatch.sln | 25 + .../ScarletMansionMimicsPatch/Patch.cs | 115 ++++ .../Properties/AssemblyInfo.cs | 36 ++ .../ScarletMansionMimicsPatch.csproj | 69 +++ 104 files changed, 9416 insertions(+) create mode 100644 .gitignore create mode 100644 ScarletMansion/ScarletMansion.sln create mode 100644 ScarletMansion/ScarletMansion/Assets.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenAnalyis.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterChain.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterRuleInfo.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/MainRoomDoorwayGroups.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveConnectorIfConnectedDoorwayBasic.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnCBSelected.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnConnectedDoorway.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Components/SwitchConnectorBlockerBasedOnCBSelected.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionPatch.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionSisterRule.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanBase.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanConnectorBlockerBasedOnSelected.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanDoorwayCompare.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnConnectedDoor.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnSelected.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveGameObjectsBasedOnConnectedDoor.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DoorwayCleanup.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/GeneratePath.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/GeneratePathPatch.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/OptimizePatch.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/Patch.cs create mode 100644 ScarletMansion/ScarletMansion/DunGenPatch/PostProcessPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanUpParent.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanup.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/KnightSpawnPoint.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLight.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLightCleanup.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletBedroom.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletClock.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoor.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoorLock.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFireExit.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFrame.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletHDRISky.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletLighting.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletPlayerControllerB.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletProp.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Components/ScarletVent.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/DoorLockPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Enemies/KnightVariant.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/EnemyVentPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/ExtendedDungeonMapLoad.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixBaseClass.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixCeilingLightValue.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixFireExit.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHallwayLightValue.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHoverIcon.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixRandomMapObject.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/FixValues/FixSpawnItemGroup.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/InitPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Items/FlandreCrystal.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Items/IScarletItem.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Items/ScarletFlashlight.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Items/ScarletPainting.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/JesterAIPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/LoadAssetsIntoLevelPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Managers/AngerManager.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Managers/DoorwayManager.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Managers/KnightSpawnManager.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletLightingManager.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletNetworkManager.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/MenuManagerPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/PlayerControllerBPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/FireExitEmptySpaceCheck.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/FloorPropBasedOnFloor.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/GlobalPropWithChildren.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropBasic.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropSingle.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasic.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasicBase.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabCycle.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabWithScale.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/Props/SpawnSyncedObjectCycle.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/RoundManagerPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/ScarletLightPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/ShotgunItemPatch.cs create mode 100644 ScarletMansion/ScarletMansion/GamePatch/ShovelPatch.cs create mode 100644 ScarletMansion/ScarletMansion/LoadingPatch/NetworkObjectListScriptableObject.cs create mode 100644 ScarletMansion/ScarletMansion/MainMenuUpdate.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/AdvancedCompanyPatch.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/FacilityMeltdownPatch.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/LethalConfigPatch.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/MimicsPatch.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/ModCompability.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/ModPatch.cs create mode 100644 ScarletMansion/ScarletMansion/ModPatch/ReservedItemSlotPatch.cs create mode 100644 ScarletMansion/ScarletMansion/Plugin.cs create mode 100644 ScarletMansion/ScarletMansion/PluginConfig.cs create mode 100644 ScarletMansion/ScarletMansion/PluginConfigClasses.cs create mode 100644 ScarletMansion/ScarletMansion/PluginConfigNetwork.cs create mode 100644 ScarletMansion/ScarletMansion/PresetConfig.cs create mode 100644 ScarletMansion/ScarletMansion/PrintProperties.cs create mode 100644 ScarletMansion/ScarletMansion/Properties/AssemblyInfo.cs create mode 100644 ScarletMansion/ScarletMansion/ScarletMansion.csproj create mode 100644 ScarletMansion/ScarletMansion/SyncedInstance.cs create mode 100644 ScarletMansion/ScarletMansion/TranspilerUtilities.cs create mode 100644 ScarletMansion/ScarletMansion/Utility.cs create mode 100644 ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.sln create mode 100644 ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Patch.cs create mode 100644 ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Properties/AssemblyInfo.cs create mode 100644 ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..473fee7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +Updates/ +Pictures/ +bin/ +.vs/ +obj/ + +/ScarletMansion/ScarletMansion/scarletmansion \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion.sln b/ScarletMansion/ScarletMansion.sln new file mode 100644 index 0000000..81cc16b --- /dev/null +++ b/ScarletMansion/ScarletMansion.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScarletMansion", "ScarletMansion\ScarletMansion.csproj", "{D7E169DF-3F43-44B0-A300-C23B9AA44D48}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D7E169DF-3F43-44B0-A300-C23B9AA44D48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7E169DF-3F43-44B0-A300-C23B9AA44D48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7E169DF-3F43-44B0-A300-C23B9AA44D48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7E169DF-3F43-44B0-A300-C23B9AA44D48}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EEF82335-ECC5-4F41-BD08-92DE05A89DD8} + EndGlobalSection +EndGlobal diff --git a/ScarletMansion/ScarletMansion/Assets.cs b/ScarletMansion/ScarletMansion/Assets.cs new file mode 100644 index 0000000..6f9d843 --- /dev/null +++ b/ScarletMansion/ScarletMansion/Assets.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using System.Reflection; +using System.IO; +using DunGen.Graph; +using UnityEngine.Experimental.Rendering; +using LethalLib.Modules; +using LethalLevelLoader; +using ScarletMansion.GamePatch.Items; +using static ScarletMansion.Assets; + +namespace ScarletMansion { + public static class Assets { + + static BepInEx.Logging.ManualLogSource logger => Plugin.logger; + + public static ActionList onAssetsLoadEvent = new ActionList("onAssetsLoad"); + + const string mainAssetBundleName = "scarletmansion"; + + // main assets + public static AssetBundle MainAssetBundle = null; + public static DungeonFlow dungeon; + public static NetworkObjectListScriptableObject networkObjectList; + public static AudioClip entranceAudioClip; + + // extended dungeon values + public static ExtendedMod extendedMod; + public static ExtendedDungeonFlow dungeonExtended; + + //public static ExtendedDungeonMapLoad.CustomMoonEntry rendEntry; + //public static ExtendedDungeonMapLoad.CustomMoonEntry dineEntry; + //public static ExtendedDungeonMapLoad.CustomMoonEntry titanEntry; + //public static List customMoonEntryList; + + // enemy values + + public class Enemy { + public GameObject enemy; + public EnemyType enemyType; + public Func rarityFunc; + + public TerminalNode terminalNode; + public TerminalKeyword terminalKeyword; + + public Enemy(GameObject enemy, TerminalNode node, TerminalKeyword keyword) { + this.enemy = enemy; + this.terminalNode = node; + this.terminalKeyword = keyword; + } + + public SpawnableEnemyWithRarity GetItemEntry(int rarity){ + var entry = new SpawnableEnemyWithRarity(); + entry.enemyType = enemyType; + entry.rarity = rarity; + return entry; + } + } + + public static Enemy knight; + + // item values + + public class GlobalItem { + public Item item; + private int _itemId; + + public GlobalItem(Item item) { + this.item = item; + _itemId = -1; + } + + + public int itemId { + get { + // cache time + if (_itemId == -1 && StartOfRound.Instance) { + var itemString = item ? item.itemName : "NULL"; + _itemId = Utility.GetGlobalItemId(item); + + if (_itemId != -1) { + Plugin.logger.LogWarning($"Cached {_itemId} itemId for item {itemString}"); + return _itemId; + } + Plugin.logger.LogWarning($"Tried to get itemId for item {itemString} but failed"); + } + + return _itemId; + } set { + _itemId = value; + } + } + } + + public class ScrapItem : GlobalItem { + public Func rarityFunc; + + public ScrapItem(Item item, Func func) : base (item){ + rarityFunc = func; + } + + public SpawnableItemWithRarity GetItemRarity(){ + var item = new SpawnableItemWithRarity(); + item.spawnableItem = this.item; + item.rarity = rarityFunc(); + return item; + } + + } + + public class Flashlight : GlobalItem { + public string assetName; + public string displayName; + + public Item lethalVanillaItem; + public int lethalHelmetIndex = -1; + public int scarletHelmetIndex = -1; + + public Flashlight(string baseName, int lethalHelmetIndex) : base(null) { + assetName = $"Scarlet{baseName}"; + displayName = $"D. {baseName}"; + this.lethalHelmetIndex = lethalHelmetIndex; + } + + public bool ContainsItem(Item compareItem) { + return compareItem == item || compareItem == lethalVanillaItem; + } + } + + public static List globalItems; + public static List scrapItems; + + public static Dictionary> itemRarityTable = new Dictionary>(){ + { "Deco. crystal", () => PluginConfig.Instance.crystalWeightValue }, + { "Shattered deco. crystal", () => PluginConfig.Instance.crystalBrokenWeightValue }, + { "Demonic painting", () => 0 } + }; + + public static Flashlight flashlight; + public static Flashlight flashlightBB; + + public static GlobalItem GetGlobalItem(Item item){ + return globalItems.FirstOrDefault(x => x.item == item); + } + + public static Flashlight GetFlashlight(Item item){ + if (flashlight.ContainsItem(item)) return flashlight; + if (flashlightBB.ContainsItem(item)) return flashlightBB; + return null; + } + + // game references + + public static ItemGroup genericItemGroup; + public static ItemGroup tabletopItemGroup; + public static ItemGroup smallItemGroup; + + public static bool dungeonMapHazardFound; + public static GameObject dungeonTurretMapHazard; + public static GameObject dungeonMinesMapHazard; + public static GameObject dungeonSpikeTrapMapHazard; + + public static Sprite hoverIcon; + + private static string GetAssemblyName() => Assembly.GetExecutingAssembly().FullName.Split(',')[0]; + public static void LoadAssetBundle() { + if (MainAssetBundle == null) { + using (var assetStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetAssemblyName() + "." + mainAssetBundleName)) { + MainAssetBundle = AssetBundle.LoadFromStream(assetStream); + } + } + + dungeon = Load("SDMLevel"); + networkObjectList = Load("SDMList"); + entranceAudioClip = Load("entrance"); + + knight = new Enemy( + Load("NET_KnightEnemy"), + Load("KnightNode"), + Load("KnightKeyword") + ); + + RegisterNetworkPrefab(networkObjectList.networkDungeon); + RegisterNetworkPrefab(networkObjectList.networkDoors); + RegisterNetworkPrefab(networkObjectList.networkItems); + RegisterNetworkPrefab(networkObjectList.networkFrames); + RegisterNetworkPrefab(networkObjectList.networkOther); + + globalItems = new List(); + scrapItems = new List(); + foreach(var i in networkObjectList.items){ + var entry = new ScrapItem(i, GetItemRarityFunction(i)); + scrapItems.Add(entry); + globalItems.Add(entry); + } + + flashlight = new Flashlight("Pro Flashlight", 0); + flashlightBB = new Flashlight("Flashlight", 1); + globalItems.Add(flashlight); + globalItems.Add(flashlightBB); + + onAssetsLoadEvent.Call(); + } + + public static Func GetItemRarityFunction(Item item){ + var name = item.itemName; + if (itemRarityTable.ContainsKey(name)){ + return itemRarityTable[name]; + } + + Plugin.logger.LogError($"Could not find rarity function for {name}. Setting to default value 10"); + return () => 10; + } + + + private static void RegisterNetworkPrefab(List list){ + foreach(var p in list){ + NetworkPrefabs.RegisterNetworkPrefab(p); + } + } + + public static T Load(string name, bool onlyReportErrors = true) where T: UnityEngine.Object { + if (MainAssetBundle == null){ + logger.LogError($"Trying to load in asset but asset bundle is missing"); + return null; + } + + var asset = MainAssetBundle.LoadAsset(name); + var missingasset = asset == null; + + if (missingasset || onlyReportErrors == true) { + logger.LogInfo($"Loading asset {name}"); + } + + if (missingasset) { + logger.LogError($"...but it was not found"); + } + + return asset; + + } + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenAnalyis.cs b/ScarletMansion/ScarletMansion/DunGenAnalyis.cs new file mode 100644 index 0000000..d19ddac --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenAnalyis.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using DunGen.Graph; +using UnityEngine; + +namespace ScarletMansion { + + public static class AnalysisUtilities { + + public class Average { + public List values; + public float total; + + public Average() { + values = new List(); + } + + public void Add(float value) { + values.Add(value); + total += value; + } + + public float GetAverage(){ + if (values.Count == 0) return 0f; + return total / values.Count; + } + + public float GetStdDev(){ + if (values.Count == 0) return 0f; + var avg = GetAverage(); + var x = values.Sum(i => { + var y = i - avg; + return y * y; + }); + return Mathf.Sqrt(x / values.Count); + } + + public override string ToString() { + //var avg = GetAverage(); + //var dev = GetStdDev(); + + var strList = new List(); + values.Sort(); + + var sectionCount = Mathf.Min(10, values.Count); + var sectionDistance = values.Count / sectionCount; + for(var i = 0; i * sectionDistance < values.Count; ++i){ + var avg = GetAverage(values.Skip(i * sectionDistance).Take(sectionDistance)); + strList.Add($"[{i}]{avg}"); + } + + /* + var leftIndex = values.Count / 4; + var rightIndex = values.Count - leftIndex; + var left = values[leftIndex]; + var right = values[rightIndex]; + return $"Avg[{avg:0.00}]({left:0.00} - {right:0.00}) Dev[{dev:0.00}]"; + */ + + var strListFormatted = string.Join(", ", strList); + return $"({strListFormatted})"; + } + + public float GetAverage(IEnumerable items){ + return items.Sum() / items.Count(); + } + } + } + + public static class DunGenAnalyis { + + public class Average { + public float totalVolume; + public float totalWeight; + public float average => totalWeight > 0f ? (totalVolume / totalWeight) : 0f; + + public Dictionary baseDictionary = new Dictionary(){ + { "SM_MayorEntrance_FINAL_32x24 Tile", false } + }; + + public void AddToAverage(float volume, float weight, string name){ + var isBaseUsed = false; + var isBaseTile = baseDictionary.TryGetValue(name, out isBaseUsed); + + if (!isBaseTile) { + totalVolume += volume; + totalWeight += weight; + } + } + + public void AddToBase(float volume, string name){ + var isBaseUsed = false; + var isBaseTile = baseDictionary.TryGetValue(name, out isBaseUsed); + totalWeight = 1f; + + if (isBaseTile && !isBaseUsed) { + totalVolume += volume; + baseDictionary[name] = true; + } + } + + public override string ToString() { + return $"{totalVolume / totalWeight}"; + } + } + + public static void Analysis(DungeonFlow flow, StartOfRound startofround, RoundManager roundmanager){ + + foreach(var l in startofround.levels){ + + var mult = l.factorySizeMultiplier * roundmanager.mapSizeMultiplier; + Plugin.logger.LogInfo($"{l.PlanetName}: {mult}"); + + var length = flow.Length; + var minLength = Mathf.RoundToInt(length.Min * mult); + var maxLength = Mathf.RoundToInt(length.Max * mult); + + var minTotal = 0f; + for(var i = 0; i <= minLength; ++i){ + minTotal += GetAverage(flow, minLength, i, 0); + minTotal += GetAverage(flow, minLength, i, 1); + minTotal += GetAverage(flow, minLength, i, 2); + //Plugin.logger.LogInfo($"new: {minTotal}"); + } + + var maxTotal = 0f; + for(var i = 0; i <= maxLength; ++i){ + maxTotal += GetAverage(flow, maxLength, i, 0); + maxTotal += GetAverage(flow, maxLength, i, 1); + maxTotal += GetAverage(flow, maxLength, i, 2); + //Plugin.logger.LogInfo($"new: {maxTotal}"); + } + + var maxSizeBounds = DunGenPatch.Patch.GetDungeonBounds(mult); + var maxSizeTotal = GetVolume(maxSizeBounds) * (3f / 5f); + + var minPer = (minTotal / maxSizeTotal).ToString("0.00"); + var maxPer = (maxTotal / maxSizeTotal).ToString("0.00"); + + Plugin.logger.LogInfo($"Min size required: {minTotal} - {maxTotal}"); + Plugin.logger.LogInfo($"All space: {maxSizeTotal}"); + Plugin.logger.LogInfo($"Taken space: {minPer} - {maxPer}"); + Plugin.logger.LogInfo(""); + } + } + + public static float GetAverage(DungeonFlow flow, int length, int index, int lineIndex){ + var average = new Average(); + var depth = (float)index / length; + + if (depth <= 1f){ + // start + if (depth == 0f){ + // count the start once + if (lineIndex == 0){ + foreach(var t in flow.Nodes[0].TileSets){ + ModifyAverage(t, average, 0f); + } + } + // skip + else { + + } + + } + // end + else if (depth == 1f) { + foreach(var t in flow.Nodes[1].TileSets){ + ModifyAverage(t, average, depth); + } + } + // entrance tile + else if (index == 1){ + // count the mayor once + if (lineIndex == 0) { + foreach(var a in flow.Lines[0].DungeonArchetypes){ + foreach(var t in a.TileSets){ + AddBase(t, average); + } + } + + + } + // skip + else { + + } + } + // in between + else { + var line = flow.GetLineAtDepth(depth); + foreach(var a in line.DungeonArchetypes){ + foreach(var t in a.TileSets){ + ModifyAverage(t, average, depth); + } + } + } + } + + //Plugin.logger.LogInfo($"b{lineIndex}i{index}l{length}: {average.average}"); + + return average.average; + } + + public static void AddBase(TileSet tileset, Average baseVol) { + foreach(var t in tileset.TileWeights.Weights){ + var gobj = t.Value; + var tile = gobj.GetComponent(); + var volume = GetVolume(tile.TileBoundsOverride); + baseVol.AddToBase(volume, t.Value.name); + } + } + + public static void ModifyAverage(TileSet tileset, Average averageVol, float depth){ + foreach(var t in tileset.TileWeights.Weights){ + var gobj = t.Value; + var tile = gobj.GetComponent(); + var volume = GetVolume(tile.TileBoundsOverride); + var weight = t.MainPathWeight * t.DepthWeightScale.Evaluate(depth); + averageVol.AddToAverage(volume * weight, weight, t.Value.name); + } + } + + public static float GetVolume(Bounds bounds){ + var size = bounds.size; + return size.x * size.y * size.z; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterChain.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterChain.cs new file mode 100644 index 0000000..2380e45 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterChain.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + public class DoorwayConnectionSisterChain : MonoBehaviour { + + public Doorway[] chain; + public bool loop = true; + + [ContextMenu("Create Chain")] + public void CreateChain(){ + if (chain == null) return; + if (chain.Length <= 1) return; + + for(var i = 0; i < chain.Length; ++i){ + var current = chain[i]; + var list = new List(); + + // add prev + if (i > 0 || loop) { + var prev = chain[((i - 1) + chain.Length) % chain.Length]; + list.Add(prev); + } + + if (i < chain.Length - 1 || loop){ + var next = chain[((i + 1) + chain.Length) % chain.Length]; + list.Add(next); + } + + var script = current.GetComponent(); + if (script == null) { + script = current.gameObject.AddComponent(); + } + script.sisters = list.ToArray(); + } + + } + + public void OnDrawGizmosSelected(){ + if (chain == null) return; + if (chain.Length <= 1) return; + + for(var i = 0; i < chain.Length; ++i){ + if (!loop && i == chain.Length - 1) continue; + + var current = chain[i]; + var next = chain[(i + 1) % chain.Length]; + var color = new Color((float)i / chain.Length, 1f, 1f); + Gizmos.color = color; + Gizmos.DrawLine(current.transform.position, next.transform.position); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterRuleInfo.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterRuleInfo.cs new file mode 100644 index 0000000..669f9b0 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/DoorwayConnectionSisterRuleInfo.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + public class DoorwayConnectionSisterRuleInfo : MonoBehaviour { + + private Doorway _self; + public Doorway self { + get { + if (_self == null) { + _self = GetComponent(); + } + return _self; + } + } + + public Doorway[] sisters; + + public void OnDrawGizmosSelected(){ + var center = transform.position; + if (sisters == null) return; + + foreach(var sis in sisters){ + var target = sis.transform.position; + var comp = sis.GetComponent(); + + if (self == null) { + Gizmos.color = Color.magenta; + } else if (comp == null || comp.sisters == null){ + Gizmos.color = Color.yellow; + } else if (!comp.sisters.Contains(self)) { + Gizmos.color = Color.red; + } else { + Gizmos.color = Color.green; + } + + Gizmos.DrawLine(center, target); + } + } + } + +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/MainRoomDoorwayGroups.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/MainRoomDoorwayGroups.cs new file mode 100644 index 0000000..d01a440 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/MainRoomDoorwayGroups.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + public class MainRoomDoorwayGroups : MonoBehaviour { + + public List groupA; + public List groupB; + public List groupC; + + public List GrabDoorwayGroup(Doorway target){ + if (groupA.Contains(target)) return groupA; + else if (groupB.Contains(target)) return groupB; + else if (groupC.Contains(target)) return groupC; + return null; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveConnectorIfConnectedDoorwayBasic.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveConnectorIfConnectedDoorwayBasic.cs new file mode 100644 index 0000000..2d144ea --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveConnectorIfConnectedDoorwayBasic.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; + +namespace ScarletMansion { + public class RemoveConnectorIfConnectedDoorwayBasic : MonoBehaviour, IDungeonCompleteReceiver { + public void OnDungeonComplete(Dungeon dungeon){ + var d = GetComponent(); + if (d == null || d.ConnectedDoorway == null) return; + if (d.ConnectedDoorway.DoorPrefabPriority == 0) { + d.ConnectorSceneObjects[0].SetActive(false); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnCBSelected.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnCBSelected.cs new file mode 100644 index 0000000..6c07fc9 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnCBSelected.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch.Components { + public class RemoveGameObjectsBasedOnCBSelected : MonoBehaviour, IDungeonCompleteReceiver { + + public Doorway doorway; + public List targets; + public GameObject cb; + + void Reset(){ + doorway = GetComponent(); + } + + public void OnDungeonComplete(Dungeon dungeon) { + var result = false; + foreach(Transform t in transform){ + if (t.name.Contains(cb.name)) { + result = true; + break; + } + } + + if (result) { + foreach(var t in targets) t.SetActive(false); + } + + } + + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnConnectedDoorway.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnConnectedDoorway.cs new file mode 100644 index 0000000..aa4b984 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/RemoveGameObjectsBasedOnConnectedDoorway.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch.Components { + public class RemoveGameObjectsBasedOnConnectedDoorway : MonoBehaviour, IDungeonCompleteReceiver { + + public enum Operation { Equal, NotEqual, LessThan, GreaterThan } + + public Doorway doorway; + public List targets; + public int doorwayPriority; + public Operation operation = Operation.Equal; + + void Reset(){ + doorway = GetComponent(); + } + + public void OnDungeonComplete(Dungeon dungeon) { + if (doorway.connectedDoorway == null) return; + var result = GetOperation().Invoke(doorway.connectedDoorway); + if (result) { + + foreach(var t in targets) t.SetActive(false); + } + } + + public Func GetOperation(){ + switch(operation){ + case Operation.Equal: + return EqualOperation; + case Operation.NotEqual: + return NotEqualOperation; + case Operation.LessThan: + return LessThanOperation; + case Operation.GreaterThan: + return GreaterThanOperation; + } + return null; + } + + public bool EqualOperation(Doorway other){ + return other.DoorPrefabPriority == doorwayPriority; + } + + public bool NotEqualOperation(Doorway other){ + return other.DoorPrefabPriority != doorwayPriority; + } + + public bool LessThanOperation(Doorway other){ + return other.DoorPrefabPriority < doorwayPriority; + } + + public bool GreaterThanOperation(Doorway other){ + return other.DoorPrefabPriority > doorwayPriority; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Components/SwitchConnectorBlockerBasedOnCBSelected.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Components/SwitchConnectorBlockerBasedOnCBSelected.cs new file mode 100644 index 0000000..dec019d --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Components/SwitchConnectorBlockerBasedOnCBSelected.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch.Components { + public class SwitchConnectorBlockerBasedOnCBSelected : MonoBehaviour, IDungeonCompleteReceiver{ + + public enum Action { SwitchToConnector, SwitchToBlocker }; + + public Doorway doorway; + public GameObject cb; + public Action switchAction; + + void Reset(){ + doorway = GetComponent(); + } + + public void OnDungeonComplete(Dungeon dungeon) { + var result = false; + foreach(Transform t in transform){ + if (t.name.Contains(cb.name)) { + result = true; + break; + } + } + + if (result) { + var connectorStatus = switchAction == Action.SwitchToConnector; + var blockerStatus = switchAction == Action.SwitchToBlocker; + + foreach(var c in doorway.ConnectorSceneObjects) c.SetActive(connectorStatus); + foreach(var b in doorway.BlockerSceneObjects) b.SetActive(blockerStatus); + } + + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionPatch.cs b/ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionPatch.cs new file mode 100644 index 0000000..7136593 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionPatch.cs @@ -0,0 +1,62 @@ +using System; +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 DunGen; + +namespace ScarletMansion.DunGenPatch { + public class DoorwayConnectionPatch { + + [HarmonyTranspiler] + [HarmonyPatch(typeof(DungeonProxy), "ConnectOverlappingDoorways")] + public static IEnumerable ConnectOverlappingDoorwaysPatch(IEnumerable instructions){ + var callFunction = typeof(DunGen.Graph.DungeonFlow).GetMethod("CanDoorwaysConnect", BindingFlags.Instance | BindingFlags.Public); + + var sequence = new InstructionSequence("doorway connect", false); + sequence.AddBasic(OpCodes.Callvirt, callFunction); + sequence.AddBasic(OpCodes.Brfalse); + + foreach(var instruction in instructions){ + + if (sequence.VerifyStage(instruction)){ + + var method = typeof(DoorwayConnectionSisterRule).GetMethod("CanDoorwaysConnect", BindingFlags.Static | BindingFlags.Public); + var getTileProxy = typeof(DoorwayProxy).GetMethod("get_TileProxy", BindingFlags.Instance | BindingFlags.Public); + + yield return new CodeInstruction(OpCodes.Ldloc_2); + yield return new CodeInstruction(OpCodes.Callvirt, getTileProxy); + yield return new CodeInstruction(OpCodes.Ldloc_S, 4); + yield return new CodeInstruction(OpCodes.Callvirt, getTileProxy); + + yield return new CodeInstruction(OpCodes.Ldloc_2); + yield return new CodeInstruction(OpCodes.Ldloc_S, 4); + + yield return new CodeInstruction(OpCodes.Call, method); + + yield return instruction; + + continue; + } + + yield return instruction; + } + + sequence.ReportComplete(); + } + + [HarmonyPatch(typeof(DungeonProxy), "ConnectOverlappingDoorways")] + [HarmonyPrefix] + public static void ConnectOverlappingDoorwaysPrePatch(ref DungeonProxy __instance){ + var enumerable = __instance.AllTiles.SelectMany(t => t.Doorways); + DoorwayConnectionSisterRule.UpdateCache(enumerable); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionSisterRule.cs b/ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionSisterRule.cs new file mode 100644 index 0000000..73b0479 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/DoorwayConnectionSisterRule.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; +using DunGen.Tags; + +namespace ScarletMansion.DunGenPatch { + public static class DoorwayConnectionSisterRule { + + public static bool active => Patch.active; + + public class Data { + public DoorwayConnectionSisterRuleInfo info; + public List proxies; + } + + public static Dictionary doorwayDictionary; + public static Dictionary doorwayProxyDictionary; + + public static void UpdateCache(IEnumerable list){ + if (!active) return; + + Plugin.logger.LogInfo("Updating cache from DungeonProxy"); + doorwayDictionary = new Dictionary(); + doorwayProxyDictionary = new Dictionary(); + + foreach(var a in list){ + + var doorway = a.DoorwayComponent; + if (doorwayDictionary.TryGetValue(doorway, out var data)){ + + data.proxies.Add(a); + doorwayProxyDictionary.Add(a, data); + + } else { + + var proxies = new List(); + proxies.Add(a); + var item = new Data { + info = doorway.GetComponent(), + proxies = proxies + }; + + doorwayProxyDictionary.Add(a, item); + doorwayDictionary.Add(a.DoorwayComponent, item); + + } + } + } + + public static bool CanDoorwaysConnect(bool result, TileProxy tileA, TileProxy tileB, DoorwayProxy doorwayA, DoorwayProxy doorwayB){ + + if (!result) return false; + if (!active) return true; + + var infoA = doorwayProxyDictionary[doorwayA].info; + var infoB = doorwayProxyDictionary[doorwayB].info; + + // deny if any sister doorway is already in use + // cause it feels like dumb otherwise + if (CheckIfSisterActive(infoA, tileB)){ + return false; + } + + if (CheckIfSisterActive(infoB, tileA)){ + return false; + } + + // allow like normal + return true; + } + + public static bool CheckIfSisterActive(DoorwayConnectionSisterRuleInfo info, TileProxy targetTile){ + if (info == null || info.sisters == null) return false; + + foreach(var sis in info.sisters){ + var proxies = doorwayDictionary[sis].proxies; + foreach(var proxy in proxies){ + var result = proxy.ConnectedDoorway != null && proxy.ConnectedDoorway.TileProxy == targetTile; + if (result) return true; + } + } + + return false; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanBase.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanBase.cs new file mode 100644 index 0000000..9b253a8 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanBase.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch.Doorways { + public abstract class DCleanBase : MonoBehaviour { + + public DoorwayCleanup parent; + + void Reset(){ + parent = GetComponent(); + } + + public abstract void Cleanup(); + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanConnectorBlockerBasedOnSelected.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanConnectorBlockerBasedOnSelected.cs new file mode 100644 index 0000000..5e067e2 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanConnectorBlockerBasedOnSelected.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch.Doorways { + public class DCleanConnectorBlockerBasedOnSelected : DCleanBase { + + public enum Action { SwitchToConnector, SwitchToBlocker }; + public Action switchAction; + public GameObject target; + + public override void Cleanup() { + var result = false; + foreach(Transform t in parent.doorway.transform){ + if (t.gameObject.activeSelf && t.name.Contains(target.name)) { + result = true; + break; + } + } + + if (result) { + parent.SwitchConnectorBlocker(switchAction == Action.SwitchToConnector); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanDoorwayCompare.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanDoorwayCompare.cs new file mode 100644 index 0000000..8686ae7 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanDoorwayCompare.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.DunGenPatch.Doorways { + public abstract class DCleanDoorwayCompare : DCleanBase { + + public enum Operation { Equal, NotEqual, LessThan, GreaterThan } + + public Func GetOperation(Operation operation){ + switch(operation){ + case Operation.Equal: + return EqualOperation; + case Operation.NotEqual: + return NotEqualOperation; + case Operation.LessThan: + return LessThanOperation; + case Operation.GreaterThan: + return GreaterThanOperation; + } + return null; + } + + public bool EqualOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority == doorwayPriority; + } + + public bool NotEqualOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority != doorwayPriority; + } + + public bool LessThanOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority < doorwayPriority; + } + + public bool GreaterThanOperation(Doorway other, int doorwayPriority){ + return other.DoorPrefabPriority > doorwayPriority; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnConnectedDoor.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnConnectedDoor.cs new file mode 100644 index 0000000..c5687ca --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnConnectedDoor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.DunGenPatch.Doorways { + public class DCleanRemoveDoorwayBasedOnConnectedDoor : DCleanDoorwayCompare { + + public int doorwayPriority; + public Operation operation = Operation.Equal; + + public override void Cleanup() { + var doorway = parent.doorway; + if (doorway.connectedDoorway == null) return; + var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority); + + if (result) { + parent.SwitchDoorwayGameObject(false); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnSelected.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnSelected.cs new file mode 100644 index 0000000..d75c666 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveDoorwayBasedOnSelected.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch.Doorways { + public class DCleanRemoveDoorwayBasedOnSelected : DCleanBase { + + public GameObject target; + + public override void Cleanup() { + var result = false; + foreach(Transform t in parent.doorway.transform){ + if (t.gameObject.activeSelf && t.name.Contains(target.name)) { + result = true; + break; + } + } + + if (result) { + parent.SwitchDoorwayGameObject(false); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveGameObjectsBasedOnConnectedDoor.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveGameObjectsBasedOnConnectedDoor.cs new file mode 100644 index 0000000..b3a13c8 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DCleanRemoveGameObjectsBasedOnConnectedDoor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.DunGenPatch.Doorways { + public class DCleanRemoveGameObjectsBasedOnConnectedDoor : DCleanDoorwayCompare { + + public List targets; + public int doorwayPriority; + public Operation operation = Operation.Equal; + + public override void Cleanup() { + var doorway = parent.doorway; + if (doorway.connectedDoorway == null) return; + var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority); + if (result) { + foreach(var t in targets) t.SetActive(false); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DoorwayCleanup.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DoorwayCleanup.cs new file mode 100644 index 0000000..de92e04 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Doorways/DoorwayCleanup.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; +using ScarletMansion.GamePatch.Managers; + +namespace ScarletMansion.DunGenPatch.Doorways { + public class DoorwayCleanup : MonoBehaviour, IDungeonCompleteReceiver { + + [Header("Doorway References")] + public Doorway doorway; + public List connectors; + public List blockers; + public GameObject doorwayGameObject; + + [Header("Cleanup References")] + public DCleanBase[] cleanupList; + + [Header("Overrides")] + public bool overrideConnector; + public bool overrideNoDoorway; + + [ContextMenu("Populate")] + public void Populate(){ + cleanupList = GetComponents(); + } + + void Reset(){ + doorway = GetComponent(); + } + + public void OnDungeonComplete(Dungeon dungeon) { + DoorwayManager.Instance.AddDoorwayCleanup(this); + } + + public void Cleanup(){ + // start up like in original + SwitchConnectorBlocker(doorway.ConnectedDoorway != null); + + foreach(var c in cleanupList) + c.Cleanup(); + + if (overrideNoDoorway) SwitchDoorwayGameObject(false); + + // clean up like in original + foreach(var c in connectors){ + if (!c.activeSelf) UnityEngine.Object.DestroyImmediate(c, false); + } + + foreach(var b in blockers){ + if (!b.activeSelf) UnityEngine.Object.DestroyImmediate(b, false); + } + } + + public void SwitchConnectorBlocker(bool isConnector){ + if (overrideConnector) isConnector = true; + foreach(var c in connectors) c.SetActive(isConnector); + foreach(var b in blockers) b.SetActive(!isConnector); + } + + public void SwitchDoorwayGameObject(bool isActive){ + doorwayGameObject.SetActive(isActive); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/GeneratePath.cs b/ScarletMansion/ScarletMansion/DunGenPatch/GeneratePath.cs new file mode 100644 index 0000000..671a297 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/GeneratePath.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; +using System.Security; +using System.Security.Permissions; + +[assembly: SecurityPermission( SecurityAction.RequestMinimum, SkipVerification = true )] +namespace ScarletMansion.DunGenPatch { + public static class GeneratePath { + + public static bool active => Patch.active; + + //public static List> allMainPathTiles; + + public static int analTestCount = 0; + public static int analTestCountMax = 100; + public static int analAltFailCount = 0; + + public static void RandomizeLineArchetypes(DungeonGenerator gen, bool randomizeMainPath){ + var mainPathString = randomizeMainPath ? "main path" : "branching path"; + //Plugin.logger.LogInfo($"Randomizing archetypes of {mainPathString}"); + + var arch = Assets.networkObjectList.archetypes; + var i = 0; + var j = 0; + + var tilesetsUsed = new Dictionary(); + foreach(var t in Assets.networkObjectList.tilesets){ + tilesetsUsed.Add(t, 0); + } + + while(j < 3 && i < arch.Count) { + var a = arch[i]; + PluginConfig.Instance.branchPathSectionOneValue.UpdateValues(a); + var tiles = randomizeMainPath ? a.TileSets : a.BranchCapTileSets; + RandomizeArchetype(gen, tiles, tilesetsUsed); + ++j; + ++i; + } + + j = 0; + while(j < 4 && i < arch.Count) { + var a = arch[i]; + PluginConfig.Instance.branchPathSectionTwoValue.UpdateValues(a); + var tiles = randomizeMainPath ? a.TileSets : a.BranchCapTileSets; + RandomizeArchetype(gen, tiles, tilesetsUsed); + ++j; + ++i; + } + + j = 0; + while(j < 3 && i < arch.Count) { + var a = arch[i]; + PluginConfig.Instance.branchPathSectionThreeValue.UpdateValues(a); + var tiles = randomizeMainPath ? a.TileSets : a.BranchCapTileSets; + RandomizeArchetype(gen, tiles, tilesetsUsed); + ++j; + ++i; + } + + /* + for(var k = 0; k < arch.Count; ++k){ + Plugin.logger.LogInfo($"a{k}"); + var a = arch[k]; + var tiles = randomizeMainPath ? a.TileSets : a.BranchCapTileSets; + foreach(var t in tiles){ + Plugin.logger.LogInfo($" {t.name}"); + } + } + */ + } + + public static void RandomizeArchetype(DungeonGenerator gen, List targetTileSet, Dictionary tilesetsUsed){ + // get 3 random + var newTiles = Assets.networkObjectList.tilesets.OrderBy(t => tilesetsUsed[t] + gen.RandomStream.NextDouble()).Take(3); + + var i = targetTileSet.Count - 1; + foreach(var n in newTiles){ + targetTileSet[i] = n; + --i; + + tilesetsUsed[n] += 1; + } + } + + public static IEnumerator GenerateAlternativeMainPaths(DungeonGenerator gen) { + + // the amount of extra alt. paths + var altCount = PluginConfig.Instance.mainPathCountValue - 1; + + if (!active || altCount == 0){ + Patch.callAlternative = false; + yield return gen.Wait(gen.GenerateBranchPaths()); + Patch.callAlternative = true; + yield break; + } + + gen.ChangeStatus(GenerationStatus.Branching); + + var allMainPathTiles = new List>(); + allMainPathTiles.Add(gen.proxyDungeon.MainPathTiles); + + // main tile is the true main room and not the fake room + // this MUST have multiple doorways as you can imainge + var mainTile = gen.proxyDungeon.MainPathTiles[1]; + var mainTileDoorwayGroups = mainTile.Prefab.GetComponentInChildren(); + var fakeTileProxy = new DoorwayProxy(mainTile, 0, mainTile.doorways[0].DoorwayComponent, Vector3.zero, Quaternion.identity); + + // what this is for needs some explaining + // the alternate main paths are really just branches that can't end early + // since they are just branches, they are affected by the branch prune setting + // as such, the final node of an alternate main path CANNOT be a node that can be pruned + // luckily, the last node is my Nodes section has tiles that won't be pruned + // so i'm just using that so the final node cannot be a target for pruning + var finalNode = gen.DungeonFlow.Nodes.OrderBy(x => x.Position).LastOrDefault(); + + for (var b = 0; b < altCount; ++b) { + RandomizeLineArchetypes(gen, true); + var previousTile = mainTile; + var targetLength = Mathf.RoundToInt(gen.DungeonFlow.Length.GetRandom(gen.RandomStream) * gen.LengthMultiplier); + var archetypes = new List(targetLength); + + var newMainPathTiles = new List(); + newMainPathTiles.Add(mainTile); + + // most of this code is a mix of the GenerateMainPath() + // and GenerateBranch() code + for(var t = 1; t < targetLength; ++t){ + var lineDepthRatio = Mathf.Clamp01((float)t / (targetLength - 1)); + var lineAtDepth = gen.DungeonFlow.GetLineAtDepth(lineDepthRatio); + if (lineAtDepth == null){ + yield return gen.Wait(gen.InnerGenerate(true)); + yield break; + } + + if (lineAtDepth != gen.previousLineSegment){ + gen.currentArchetype = lineAtDepth.GetRandomArchetype(gen.RandomStream, archetypes); + gen.previousLineSegment = lineAtDepth; + } + + // terrible solution but FUCK it + // and yet it worked + // this is how my last node cannot be a target of pruning + List useableTileSets; + if (lineDepthRatio >= 1f){ + useableTileSets = finalNode.TileSets; + } else { + useableTileSets = gen.currentArchetype.TileSets; + } + + if (t == 1){ + + // go to each doorway + foreach(var doorway in mainTile.doorways){ + // if null or another fake, ignore + // we want the real ones + var con = doorway.ConnectedDoorway; + if (con == fakeTileProxy || con == null) continue; + + // grab its corresponding group + var groups = mainTileDoorwayGroups.GrabDoorwayGroup(doorway.DoorwayComponent); + if (groups == null) continue; + + // go through the list again, but this time we adding fakes to trick the AddTile() + foreach(var again in mainTile.doorways){ + // if null AND its part of the group + // add the fake + if (again.ConnectedDoorway == null && groups.Contains(again.DoorwayComponent)){ + again.ConnectedDoorway = fakeTileProxy; + } + } + } + + } + + var tileProxy = gen.AddTile(previousTile, useableTileSets, lineDepthRatio, gen.currentArchetype, TilePlacementResult.None); + + if (tileProxy == null) { + if (!Patch.startAnalysis) Plugin.logger.LogInfo($"Alt. main branch gen failed at {b}:{lineDepthRatio}"); + analAltFailCount++; + yield return gen.Wait(gen.InnerGenerate(true)); + yield break; + } + + // this is debug code from when a mysterious bug arised + // the culprit? the final node of the alternate main path being pruned of course + if (lineDepthRatio >= 1f){ + if (!Patch.startAnalysis) Plugin.logger.LogInfo($"Alt. main branch at {b} ended with {tileProxy.PrefabTile.name}"); + } + + tileProxy.Placement.BranchDepth = t; + tileProxy.Placement.NormalizedBranchDepth = lineDepthRatio; + tileProxy.Placement.GraphNode = previousTile.Placement.GraphNode; + tileProxy.Placement.GraphLine = previousTile.Placement.GraphLine; + + previousTile = tileProxy; + newMainPathTiles.Add(tileProxy); + + if (gen.ShouldSkipFrame(true)) yield return gen.GetRoomPause(); + } + + allMainPathTiles.Add(newMainPathTiles); + + } + + // okay lets fix the fakes + foreach(var doorway in mainTile.doorways){ + if (doorway.ConnectedDoorway == fakeTileProxy) { + doorway.ConnectedDoorway = null; + } + } + + Patch.callAlternative = false; + if (!Patch.startAnalysis) Plugin.logger.LogInfo($"Created {altCount} alt. paths, creating branches now"); + + // this is major trickery and it works still + for(var b = 0; b < altCount + 1; ++b){ + if (!Patch.startAnalysis) Plugin.logger.LogInfo($"Branch {b}"); + RandomizeLineArchetypes(gen, false); + gen.proxyDungeon.MainPathTiles = allMainPathTiles[b]; + yield return gen.Wait(gen.GenerateBranchPaths()); + } + + Patch.callAlternative = true; + + gen.proxyDungeon.MainPathTiles = allMainPathTiles[0]; + + if (AnalysisUpdate(gen)) yield return gen.Wait(gen.InnerGenerate(true)); + } + + public static bool AnalysisUpdate(DungeonGenerator gen){ + if (Patch.startAnalysis && analTestCount <= analTestCountMax) { + var t = Patch.stopwatch.ElapsedMilliseconds; + //Patch.tileCalculations.Add(t); + //Plugin.logger.LogInfo($"C{TestCount}, T({Patch.tileCalculations.ToString()}), S{AltFailCount}, R{gen.retryCount}, D({Patch.doorwayPairs.ToString()})"); + + var allTiles = gen.proxyDungeon.AllTiles; + var firstTilePos = allTiles[0].Placement.position; + for(var i = 1; i < allTiles.Count; ++i){ + var tile = allTiles[i]; + var tilePrefab = tile.Prefab; + var prefabBasePos = tilePrefab.transform.position; + var scrap = tilePrefab.GetComponentsInChildren(); + foreach(var s in scrap){ + var offPos = s.transform.position - prefabBasePos; + var realPos = tile.Placement.Rotation * offPos + tile.Placement.Position; + //var floor = Mathf.FloorToInt((realPos.y + 16f) / 8f); + Patch.scrapDistance.Add(Vector3.Distance(realPos, firstTilePos)); + //Patch.scrapFloors.Add(floor); + } + } + + Plugin.logger.LogInfo($"C{analTestCount}, S{analAltFailCount}, R{gen.retryCount - analAltFailCount - analTestCount}\nLoot {Patch.scrapDistance.ToString()}\nFloor {Patch.scrapFloors.ToString()}"); + + analTestCount++; + Patch.stopwatch.Restart(); + return true; + } + return false; + } + + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/GeneratePathPatch.cs b/ScarletMansion/ScarletMansion/DunGenPatch/GeneratePathPatch.cs new file mode 100644 index 0000000..4a56a25 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/GeneratePathPatch.cs @@ -0,0 +1,58 @@ +using System; +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; + +namespace ScarletMansion.DunGenPatch { + + public class GeneratePathPatch { + + [HarmonyPatch(typeof(RoundManager), "GenerateNewFloor")] + [HarmonyPrefix] + public static void DungeonGeneratorGenerate_PrefixPrefix(){ + // safety check + Plugin.logger.LogInfo("Disabling SDM logic"); + Patch.Deactivate(); + //Patch.ActivateAnalysis(); + } + + public static void GeneratePatch(RoundManager roundManager){ + Plugin.logger.LogInfo("Loading Scarlet Mansion map so we are activating the alt. dungen scripts"); + Patch.Activate(roundManager.dungeonGenerator.Generator); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath")] + public static void GenerateMainPathPatch(ref DungeonGenerator __instance, ref IEnumerator __result){ + if (Patch.active && Patch.callAlternative) { + GeneratePath.RandomizeLineArchetypes(__instance, true); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(DungeonGenerator), "GenerateBranchPaths")] + public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance, ref IEnumerator __result){ + if (Patch.active && Patch.callAlternative) { + __result = GeneratePath.GenerateAlternativeMainPaths(__instance); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(RoundManager), "FinishGeneratingLevel")] + public static void GenerateBranchPathsPatch(){ + if (Patch.active) { + Plugin.logger.LogInfo("Alt. InnerGenerate() function complete"); + } + } + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/OptimizePatch.cs b/ScarletMansion/ScarletMansion/DunGenPatch/OptimizePatch.cs new file mode 100644 index 0000000..3b2e2c0 --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/OptimizePatch.cs @@ -0,0 +1,59 @@ +/* +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; +using HarmonyLib; + +namespace ScarletMansion.DunGenPatch { + public class OptimizePatch { + + [HarmonyPatch(typeof(DoorwayPairFinder), "GetDoorwayPairs")] + [HarmonyPrefix] + public static bool GetDoorwayPairsPatch(ref DoorwayPairFinder __instance, int? maxCount, ref Queue __result){ + + __instance.tileOrder = __instance.CalculateOrderedListOfTiles(); + var doorwayPairs = __instance.PreviousTile == null ? + __instance.GetPotentialDoorwayPairsForFirstTile() : + __instance.GetPotentialDoorwayPairsForNonFirstTile(); + + var num = doorwayPairs.Count(); + if (maxCount != null) { + num = Mathf.Min(num, maxCount.Value); + } + __result = new Queue(num); + + var newList = OrderDoorwayPairs(doorwayPairs, num); + foreach(var item in newList){ + __result.Enqueue(item); + } + + return false; + } + + private class DoorwayPairComparer : IComparer { + public int Compare(DoorwayPair x, DoorwayPair y) { + var tileWeight = y.TileWeight.CompareTo(x.TileWeight); + if (tileWeight == 0) return y.DoorwayWeight.CompareTo(x.DoorwayWeight); + return tileWeight; + } + } + + private static IEnumerable OrderDoorwayPairs(IEnumerable list, int num){ + + var c = list.Count(); + var d = Mathf.Min(c, num); + if (d > 1) { + Patch.doorwayPairs.Add(d); + } + + return list.OrderBy(x => x, new DoorwayPairComparer()).Take(num); + } + + + } +} +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/Patch.cs b/ScarletMansion/ScarletMansion/DunGenPatch/Patch.cs new file mode 100644 index 0000000..32684dd --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/Patch.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using Unity.AI.Navigation; +using UnityEngine; +using UnityEngine.AI; +using System.Diagnostics; +using UnityEngine.Rendering; +using UnityEngine.Rendering.HighDefinition; + +namespace ScarletMansion.DunGenPatch { + public static class Patch { + + public static bool active; + public static bool callAlternative; + public static DungeonGenerator generatorInstance; + + public static bool startAnalysis; + public static Stopwatch stopwatch; + public static AnalysisUtilities.Average scrapDistance; + public static AnalysisUtilities.Average scrapFloors; + //public static AnalysisUtilities.Average tileCalculations; + //public static AnalysisUtilities.Average doorwayPairs; + + public static HDRenderPipelineAsset previousHDRPAsset; + public static HDRenderPipelineAsset sdmHDRPAsset; + + public static void Activate(DungeonGenerator generator){ + active = true; + callAlternative = true; + generatorInstance = generator; + //startAnalysis = true; + + var scale = generator.LengthMultiplier; + var bounds = GetDungeonBounds(scale); + + generator.DungeonFlow.TileInjectionRules = new List(); + + generator.RestrictDungeonToBounds = true; + generator.TilePlacementBounds = bounds; + Plugin.logger.LogInfo($"Set new dungeon bounds with, {bounds.center} and {bounds.size}"); + + var mainPathLength = generator.DungeonFlow.Length; + Plugin.logger.LogInfo($"Length of main path be: {GetLength(mainPathLength, scale)}"); + + GamePatch.LoadAssetsIntoLevelPatch.ModifyLevel(StartOfRound.Instance.currentLevel); + + Plugin.logger.LogInfo("Updating HDRP asset: doubling max shadows request"); + try { + previousHDRPAsset = QualitySettings.renderPipeline as HDRenderPipelineAsset; + sdmHDRPAsset = ScriptableObject.Instantiate(previousHDRPAsset); + + var settings = sdmHDRPAsset.currentPlatformRenderPipelineSettings; + Plugin.logger.LogInfo($"maxScreenSpaceShadowSlots: {settings.hdShadowInitParams.maxScreenSpaceShadowSlots} -> {settings.hdShadowInitParams.maxScreenSpaceShadowSlots * 2}"); + //Plugin.logger.LogInfo($"maxShadowRequests: {settings.hdShadowInitParams.maxShadowRequests} -> {settings.hdShadowInitParams.maxShadowRequests * 2}"); + + settings.hdShadowInitParams.maxScreenSpaceShadowSlots *= 2; + //settings.hdShadowInitParams.maxShadowRequests *= 2; + sdmHDRPAsset.currentPlatformRenderPipelineSettings = settings; + + QualitySettings.renderPipeline = sdmHDRPAsset; + } catch (Exception e) { + Plugin.logger.LogError("Failed to update HDRP asset"); + Plugin.logger.LogError(e.ToString()); + } + + + if (startAnalysis) ActivateAnalysis(); + } + + + public static void ActivateAnalysis(){ + stopwatch = new Stopwatch(); + stopwatch.Start(); + + scrapDistance = new AnalysisUtilities.Average(); + scrapFloors = new AnalysisUtilities.Average(); + //tileCalculations = new AnalysisUtilities.Average(); + //doorwayPairs = new AnalysisUtilities.Average(); + } + + public static Bounds GetDungeonBounds(float scale){ + var width = PluginConfig.Instance.dunGenWidthBaseValue; + var length = PluginConfig.Instance.dunGenLengthBaseValue; + var widthFac = PluginConfig.Instance.dunGenWidthMultiFactorValue; + var lengthFac = PluginConfig.Instance.dunGenLengthMultiFactorValue; + + var totalwidth = width + (width * (scale - 1) * widthFac); + var totallength = length + (length * (scale - 1) * lengthFac); + + //var + + var offset = new Vector3(0f, 4f, totallength * 0.5f - 8f); + var size = new Vector3(totalwidth, 40f, totallength); + return new Bounds(offset, size); + } + + public static string GetLength(IntRange range, float multi){ + return $"({range.Min * multi} - {range.Max * multi})"; + } + + public static void Deactivate(){ + active = false; + callAlternative = false; + generatorInstance = null; + + GamePatch.JesterAIPatch.active = false; + + if (previousHDRPAsset && QualitySettings.renderPipeline == sdmHDRPAsset) { + Plugin.logger.LogInfo("Restoring original HDRP asset"); + + QualitySettings.renderPipeline = previousHDRPAsset; + previousHDRPAsset = null; + sdmHDRPAsset = null; + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/DunGenPatch/PostProcessPatch.cs b/ScarletMansion/ScarletMansion/DunGenPatch/PostProcessPatch.cs new file mode 100644 index 0000000..c92d77f --- /dev/null +++ b/ScarletMansion/ScarletMansion/DunGenPatch/PostProcessPatch.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using HarmonyLib; +using UnityEngine; + +namespace ScarletMansion.DunGenPatch { + public class PostProcessPatch { + + [HarmonyPostfix] + [HarmonyPatch(typeof(DungeonGenerator), "PostProcess")] + public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance){ + if (Patch.active) { + var value = __instance.RandomStream.Next(999); + GamePatch.Props.SpawnSyncedObjectCycle.UpdateCycle(value); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanUpParent.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanUpParent.cs new file mode 100644 index 0000000..927dae2 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanUpParent.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + public class FloorCleanUpParent : MonoBehaviour, IDungeonCompleteReceiver { + + public FloorCleanup[] children; + + void Reset(){ + children = GetComponentsInChildren(); + } + + public void OnDungeonComplete(Dungeon dungeon) { + var anyChanges = true; + while(anyChanges) { + anyChanges = false; + foreach(var c in children) anyChanges = anyChanges | c.UpdateRender(); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanup.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanup.cs new file mode 100644 index 0000000..2afe0e4 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/FloorCleanup.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion { + public class FloorCleanup : MonoBehaviour { + + [Header("References")] + public MeshRenderer targetRenderer; + public GameObject targerGameObject; + public int bitValueCheck; + + [Header("Logic")] + public GameObject[] neighbors; + + public int[] stateValues; + public Material[] stateMaterials; + public float[] stateRotations; + + public State[] states; + + [System.Serializable] + public class State { + public int value; + public Material material; + public float rotation; + } + + void Reset(){ + targetRenderer = GetComponent(); + targerGameObject = gameObject; + } + + public bool UpdateRender(){ + + var value = 0; + var maxValue = (1 << neighbors.Length) - 1; + for(var i = 0; i < neighbors.Length; ++i){ + var n = neighbors[i]; + if (n != null && neighbors[i].activeSelf) value += 1 << i; + } + + if (value == maxValue) return false; + + for(var i = 0; i < stateValues.Length; ++i){ + if (stateValues[i] == value) { + targetRenderer.material = stateMaterials[i]; + targetRenderer.transform.localEulerAngles = new Vector3(0f, stateRotations[i], 0f); + return false; + } + } + + var wasActive = targerGameObject.activeSelf; + targerGameObject.SetActive(false); + return wasActive; + } + + void OnDrawGizmosSelected(){ + Gizmos.color = Color.green; + for(var i = 0; i < neighbors.Length; ++i){ + if ((bitValueCheck & (1 << i)) > 0) { + Gizmos.DrawCube(neighbors[i].transform.position, Vector3.one); + } + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/KnightSpawnPoint.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/KnightSpawnPoint.cs new file mode 100644 index 0000000..a002919 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/KnightSpawnPoint.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion { + + public class KnightSpawnPoint : MonoBehaviour { + + public int index; + + public void Start(){ + if (GameNetworkManager.Instance.isHostingGame) + StartCoroutine(GetNearbyPlayers()); + } + + public const float minTime = 4f; + public const float maxTime = 8f; + public const float minSqrDistance = 6f * 6f; + + public IEnumerator GetNearbyPlayers(){ + while(true){ + var randomWait = UnityEngine.Random.Range(minTime, maxTime); + yield return new WaitForSeconds(randomWait); + + var sround = StartOfRound.Instance; + var players = sround.allPlayerScripts; + var playerCount = sround.connectedPlayersAmount + 1; + for(var i = 0; i < playerCount; ++i){ + var player = players[i]; + if (!player.isPlayerControlled && player.isPlayerDead) continue; + + // could i do a raycast? + // i mean i could but most knights are in visable locations in every room + // plus its a big pain on GOD + var dist = Vector3.SqrMagnitude(transform.position - player.transform.position); + if (dist <= minSqrDistance) { + KnightSpawnManager.Instance.lastKnightSeenPlayer = index; + Plugin.logger.LogInfo($"Knight {index} has noticed player {player.playerUsername}"); + } + } + } + } + + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLight.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLight.cs new file mode 100644 index 0000000..a4e7e4f --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLight.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.Lights { + + public class ScarletLight : MonoBehaviour, IComparable, IDungeonCompleteReceiver { + + public Light light; + public int priority = 0; + + private Coroutine anger; + private bool permenantAnger; + + public int CompareTo(ScarletLight obj) { + return obj.priority.CompareTo(priority); + } + + void Reset(){ + light = GetComponent(); + } + + public void BeginAngry(float duration, bool forever){ + if (permenantAnger) return; + if (anger != null) StopCoroutine(anger); + + if (forever) permenantAnger = true; + anger = StartCoroutine(AngerCoroutine(duration)); + } + + private IEnumerator AngerCoroutine(float duration){ + var t = 0f; + var c = light.color; + while(t < 0.375f) { + yield return null; + t += Time.deltaTime; + light.color = Color.Lerp(c, Color.red, t / 0.375f); + } + + if (permenantAnger) yield break; + yield return new WaitForSeconds(duration + UnityEngine.Random.value); + + c = light.color; + t = 0f; + while(t < 1.25f){ + yield return null; + t += Time.deltaTime; + light.color = Color.Lerp(c, Color.white, t / 1.25f); + } + + } + + public void OnDungeonComplete(Dungeon dungeon) { + if (!gameObject.activeInHierarchy) return; + GamePatch.Managers.AngerManager.Instance.AddLight(this); + } + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLightCleanup.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLightCleanup.cs new file mode 100644 index 0000000..99986c7 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/Lights/ScarletLightCleanup.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.Lights { + public class ScarletLightCleanup : MonoBehaviour, IDungeonCompleteReceiver { + + public static float[] weights = new [] { 2f, 4f, 3f, 1f }; + public int maxLights = 2; + + public void OnDungeonComplete(Dungeon dungeon) { + + var lights = GetComponentsInChildren(); + + if (lights.Length == 0) return; + + // the smallest amount of optimization + var random = DunGenPatch.Patch.generatorInstance.RandomStream; + if (lights.Length > 1){ + Utility.Shuffle(random, lights); + System.Array.Sort(lights); + } + + //Plugin.logger.LogInfo(string.Join(", ", lights.Select(l => l.priority))); + + var count = GetRandom(random, weights); + count = Mathf.Min(count, maxLights); + for(var i = count; i < lights.Length; ++i){ + lights[i].gameObject.SetActive(false); + } + + } + + public static int GetRandom(RandomStream random, float[] weights) { + var total = 0f; + foreach(var w in weights){ + total += w; + } + + var value = (float)random.NextDouble() * total; + for(var i = 0; i < weights.Length; ++i){ + var w = weights[i]; + if (w == 0f) continue; + + if (value < w) { + return i; + } + value -= w; + } + + Plugin.logger.LogError("GetRandom for float[] failed. Defaulting to 0"); + return 0; + } + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletBedroom.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletBedroom.cs new file mode 100644 index 0000000..f0732f0 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletBedroom.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using DunGen; +using ScarletMansion.Lights; +using ScarletMansion.GamePatch.Managers; +using GameNetcodeStuff; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletBedroom : MonoBehaviour, IDungeonCompleteReceiver { + + public static ActionList onBedroomEndEvent = new ActionList("onBedroomEnd"); + + [Header("References")] + public ScarletVent vent; + public Transform paintingSpawnTransform; + public AudioSource screamAudioSource; + public Transform[] itemSpawns; + + private Light[] lights; + private Doorway[] doorways; + private ItemReference[] bonusItems; + private EnemyReferenceSpawnLogic bonusEnemy; + + const float spawnTime = 10f; + const float cloudTime = 4f; + + public void Anger(Transform target){ + AngerManager.Instance.Anger(); + StartCoroutine(PlayAudio()); + StartCoroutine(IncreaseVentAudio()); + StartCoroutine(LockDoorAndSpawnEnemy(target)); + + foreach(var l in lights){ + StartCoroutine(FlickerLight(l)); + } + } + + public IEnumerator PlayAudio(){ + yield return new WaitForSeconds(UnityEngine.Random.Range(0.75f, 1f)); + screamAudioSource.Play(); + AngerManager.Instance.TriggerAngerLightBrief(6f); + + var local = StartOfRound.Instance.localPlayerController; + var dist = Vector3.SqrMagnitude(local.transform.position - transform.position); + if (dist < 12f * 12f) + local.JumpToFearLevel(0.75f); + else if (dist < 24f * 24f) + local.JumpToFearLevel(0.5f); + else if (dist < 32f * 32f) + local.JumpToFearLevel(0.25f); + } + + public IEnumerator LockDoorAndSpawnEnemy(Transform target){ + var roundmanager = RoundManager.Instance; + var doors = new List(); + + // lock doors + if (roundmanager.IsServer){ + foreach(var d in doorways){ + var result = AngerManager.Instance.GetScarletDoor(d.transform.position); + if (result) doors.Add(result); + } + + foreach(var d in doors){ + d.LockDoorClientRpc(); + } + } + + yield return new WaitForSeconds(spawnTime); + + SpawnRandomEnemy(target); + SpawnLoot(); + + onBedroomEndEvent.Call(); + + // stop volume stuff + vent.OpenVentClientRpc(); + vent.shakeAudioSource.Stop(); + vent.shakeAudioSource.volume = 0f; + + // unlock doors + if (roundmanager.IsServer){ + foreach(var d in doors){ + d.UnlockDoorClientRpc(); + } + } + } + + public IEnumerator IncreaseVentAudio(){ + yield return new WaitForSeconds(1f); + + vent.shakeAudioSource.Play(); + var t = 0f; + while (t < cloudTime){ + vent.shakeAudioSource.volume = t / cloudTime; + t += Time.deltaTime; + yield return null; + } + } + + public IEnumerator FlickerLight(Light light){ + // gives time for player to react to their environment + yield return new WaitForSeconds(UnityEngine.Random.Range(1f, 1.5f)); + + // scarlet lights flicker + var isScarletLight = light.GetComponent() != null; + if (isScarletLight){ + var count = UnityEngine.Random.Range(6, 8); + for (var i = 0; i < count; ++i) { + light.enabled = false; + yield return new WaitForSeconds(UnityEngine.Random.Range(0.15f, 0.25f) * (1f - count * 0.05f)); + light.enabled = true; + yield return new WaitForSeconds(UnityEngine.Random.Range(0.5f, 0.6f) * (1f - count * 0.05f)); + } + + light.enabled = false; + } + // normal lights are candles, so just turn them off + else { + yield return new WaitForSeconds(UnityEngine.Random.Range(3f, 5f)); + light.enabled = false; + } + } + + private static List spawnableEnemiesTrueList; + + public static void CreateRandomEnemyList(List enemies){ + var lower = PluginConfig.Instance.paintingEnemyListValue.ToLowerInvariant(); + if (lower == "default"){ + lower = "knight@s1,crawler,nutcracker,springman,maskedplayerenemy,jester@s1"; + } + + spawnableEnemiesTrueList = new List(); + var entries = Utility.ParseString(lower, new string[] {","}, new string[] {"@"} ); + for(var i = 0; i < enemies.Count; ++i){ + var enemyType = enemies[i].enemyType; + var enemyName = enemyType.name.ToLowerInvariant(); + var enemyDisplayName = enemyType.enemyName.ToLowerInvariant(); + + var link = entries.FirstOrDefault(e => e.main == enemyName || e.main == enemyDisplayName); + if (link != null){ + EnemyReferenceSpawnLogic.SpawnLogic spawnValue; + var spawnTag = link.GetParameter("s"); + if (spawnTag != null) spawnValue = (EnemyReferenceSpawnLogic.SpawnLogic)Utility.TryParseInt(spawnTag.Substring(1), 0); + else spawnValue = 0; + + var reference = new EnemyReferenceSpawnLogic(enemyType, i, spawnValue); + spawnableEnemiesTrueList.Add(reference); + Plugin.logger.LogInfo($"Added {reference.ToString()} to bedroom event"); + } + } + } + + public EnemyReferenceSpawnLogic GetRandomEnemy(System.Random sysRandom){ + var roundManager = RoundManager.Instance; + if (!roundManager.IsServer) return null; + if (!PluginConfig.Instance.paintingSpawnEnemyValue) return null; + + if (spawnableEnemiesTrueList == null || spawnableEnemiesTrueList.Count == 0){ + Plugin.logger.LogError($"Could not select enemy to spawn in bedroom. Empty enemy list?"); + return null; + } + + var index = sysRandom.Next(spawnableEnemiesTrueList.Count); + var enemy = spawnableEnemiesTrueList[index]; + Plugin.logger.LogInfo($"Selected enemy to spawn {enemy.ToString()}"); + return enemy; + } + + public void SpawnRandomEnemy(Transform target, int overrideIndex = -1){ + var roundmanager = RoundManager.Instance; + if (!roundmanager.IsHost) return; + if (bonusEnemy == null) return; + + try { + + var enemy = bonusEnemy.enemy; + var enemyIndex = bonusEnemy.index; + + if (enemyIndex > -1){ + var pos = vent.transform.position; + var dir = target.transform.position - pos; + dir.y = 0f; + var rot = Quaternion.LookRotation(dir); + var y = rot.eulerAngles.y; + + bonusEnemy.ApplySpawnLogic(); + + roundmanager.currentEnemyPower += enemy.PowerLevel; + roundmanager.SpawnEnemyServerRpc(vent.transform.position, y, enemyIndex); + } + } catch (Exception e) { + var enemyString = bonusEnemy != null ? bonusEnemy.ToString() : "NULL"; + Plugin.logger.LogError($"Failed to spawn enemy for bedroom event, the smelly culprit is {enemyString}"); + Plugin.logger.LogError(e.ToString()); + } + + } + + public void SpawnLoot(){ + var roundmanager = RoundManager.Instance; + if (!roundmanager.IsServer) return; + + try { + AngerManager.Instance.SpawnAngerLoot(bonusItems, itemSpawns); + } catch (Exception e) { + var itemStrings = string.Join("\n", bonusItems.Select(i => i != null ? i.ToString() : "NULL")); + Plugin.logger.LogError($"Failed to spawn items for bedroom event, the smelly culprits are {itemStrings}"); + Plugin.logger.LogError(e.ToString()); + } + } + + public void OnDungeonComplete(Dungeon dungeon) { + AngerManager.Instance.AddBedroom(this); + + var parent = GetComponentInParent(); + lights = parent.GetComponentsInChildren(); + doorways = parent.UsedDoorways.ToArray(); + + var dunRandom = DunGenPatch.Patch.generatorInstance.RandomStream; + var randomValue = (int)(dunRandom.NextDouble() * int.MaxValue); + var sysRandom = new System.Random(randomValue); + + Utility.Shuffle(sysRandom, itemSpawns); + var count = sysRandom.Next(PluginConfig.Instance.paintingExtraLootValue.min, PluginConfig.Instance.paintingExtraLootValue.max + 1); + + bonusItems = AngerManager.Instance.CreateAngerLoot(count, sysRandom); + bonusEnemy = GetRandomEnemy(sysRandom); + } + + public void OnTriggerEnter(Collider other){ + Plugin.logger.LogInfo(other.gameObject.tag); + if (other.gameObject.tag == "Player"){ + var player = other.gameObject.GetComponent(); + if (player && player.IsLocalPlayer){ + HUDManager.Instance.DisplayTip("SDM Dungeon Events", "Paintings fetch a good price, if you are daring...", false, true, "SDM_Bedroom"); + } + } + } + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletClock.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletClock.cs new file mode 100644 index 0000000..e70d410 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletClock.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletClock : NetworkBehaviour { + + [Header("Animations")] + public Vector3 eulerAngleOffset = new Vector3(-90f, -90f, -90f); + public Transform hourHand; + public Transform minuteHand; + private Coroutine animationCoroutine; + private int lastHour = -1; + private int lastTotalMinutes = -1; + + [Header("Sounds")] + public AudioSource audioSource; + public AudioClip[] audioClips; + + [Header("Destruction Values")] + public bool stop; + public float chanceEveryHourToDestroy = 1f / 52f; + private float timerTillDestroyed = -1f; + + void Update(){ + + if (stop) return; + + if (IsServer && timerTillDestroyed > 0f){ + timerTillDestroyed -= Time.deltaTime; + if (timerTillDestroyed <= 0f) { + StopClockClientRpc(); + return; + } + } + + var timeOfDay = TimeOfDay.Instance; + var totalMinutes = (int)(timeOfDay.normalizedTimeOfDay * (60f * timeOfDay.numberOfHours)) + 360; + var hours = Mathf.FloorToInt(totalMinutes / 60f); + var minutes = totalMinutes % 60; + + var timeChanged = lastTotalMinutes != totalMinutes; + var hourChanged = lastHour != hours; + + if (timeChanged) { + if (animationCoroutine != null) StopCoroutine(animationCoroutine); + animationCoroutine = StartCoroutine(MoveHands(hours, minutes)); + } + + if (IsServer && hourChanged){ + HourlySelfDestroyCheck(); + } + + lastHour = hours; + lastTotalMinutes = totalMinutes; + } + + // 60 = -90 + // 07 = -45 + // 15 = 0 + // 22 = 45 + + + // 12 = -90 + // 01 = -45 + // 03 = 0 + // 04 = 45 + + public IEnumerator MoveHands(int hour, int minutes){ + var currentHourHandRotation = hourHand.localRotation; + var currentMinuteHandRotation = minuteHand.localRotation; + + var nextHourHandAngles = eulerAngleOffset + new Vector3(hour / 12f * 360f, 0f, 0f); + var nextHourHandRotation = Quaternion.Euler(nextHourHandAngles); + + var nextMinuteHandAngles = eulerAngleOffset + new Vector3(minutes / 60f * 360f, 0f, 0f); + var nextMinuteHandRotation = Quaternion.Euler(nextMinuteHandAngles); + + //Plugin.logger.LogInfo($"({hour}: {minutes}): ({nextHourHandAngles.x}, {nextMinuteHandRotation.x})"); + + PlayAudio(); + + var t = 0f; + var atimer = 0.3f; + while(t < atimer) { + yield return null; + t += Time.deltaTime; + + hourHand.localRotation = Quaternion.Slerp(currentHourHandRotation, nextHourHandRotation, t / atimer); + minuteHand.localRotation = Quaternion.Slerp(currentMinuteHandRotation, nextMinuteHandRotation, t / atimer); + } + } + + public void PlayAudio(){ + var clip = audioClips[UnityEngine.Random.Range(0, audioClips.Length)]; + audioSource.pitch = UnityEngine.Random.value * 0.3f + 0.85f; + audioSource.PlayOneShot(clip); + WalkieTalkie.TransmitOneShotAudio(audioSource, clip); + } + + public void HourlySelfDestroyCheck(){ + if (stop || timerTillDestroyed > 0) return; + + Plugin.logger.LogInfo("Hourly clock self-destroy check"); + if (UnityEngine.Random.value < chanceEveryHourToDestroy) { + timerTillDestroyed = UnityEngine.Random.value * 60f + 15f; + Plugin.logger.LogInfo($"Clock offing itself in {timerTillDestroyed} sec"); + } + } + + [ClientRpc] + public void StopClockClientRpc(){ + stop = true; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoor.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoor.cs new file mode 100644 index 0000000..a273599 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoor.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using UnityEngine.AI; +using ScarletMansion.GamePatch.Managers; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletDoor : NetworkBehaviour { + + [Header("Door Reference")] + public DoorLock door; + + [Header("Personal Refs")] + public NavMeshObstacle obstacle; + public BoxCollider boxCollider; + public ParticleSystem ps; + + private bool previousDoorLockValue; + + void Awake(){ + AngerManager.Instance.AddDoor(this); + } + + [ServerRpc] + public void LockDoorServerRpc(){ + LockDoorClientRpc(); + } + + [ServerRpc] + public void UnlockDoorServerRpc(){ + UnlockDoorClientRpc(); + } + + [ClientRpc] + public void LockDoorClientRpc(){ + if (door){ + door.isDoorOpened = false; + door.navMeshObstacle.enabled = true; + + if (RoundManager.Instance.IsServer) + door.GetComponent().TriggerAnimationNonPlayer(false, true, true); + + door.doorTrigger.interactable = false; + previousDoorLockValue = door.isLocked; + door.isLocked = true; + } + + obstacle.enabled = true; + boxCollider.enabled = true; + ps.Play(); + } + + [ClientRpc] + public void UnlockDoorClientRpc(){ + if (door){ + door.doorTrigger.interactable = true; + door.isLocked = previousDoorLockValue; + } + + obstacle.enabled = false; + boxCollider.enabled = false; + ps.Stop(); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoorLock.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoorLock.cs new file mode 100644 index 0000000..3333f82 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletDoorLock.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletDoorLock : DoorLock { + + public static float internalTimerCooldown = 1f; + + [Header("Scarlet")] + public List healthTilePieces; + public List frameTilePieces; + private int healthTilePiecesCount; + public GameObject propPrefab; + + [Header("Audio")] + public AudioSource audioSource; + public AudioClip[] destroyAudioClips; + public AudioClip fullyDestroyedAudioClip; + + [Header("Proper Disable Logic")] + public Collider[] disableColliders; + public Animator disableAnimator; + public bool callNormalScript; + + private float nextInternalTimer = 0f; + private float destroyMeter = 0f; + private int currentDamage = 0; + + public void AwakeScarlet(){ + healthTilePiecesCount = healthTilePieces.Count + 1; + } + + void Start(){ + if (IsOwner){ + var randomCheck = UnityEngine.Random.value < 0.1f; + if (randomCheck){ + ApplyDamageServerRpc(Vector3.forward, 50f); + } + } + } + + public void OnTriggerStayScarlet(Collider other){ + if (NetworkManager.Singleton == null || !base.IsServer) return; + if (isDoorOpened || !doorTrigger.interactable) return; + + if (other.tag == "Enemy"){ + var comp = other.GetComponent(); + if (comp == null) return; + + // do normal door opening script + var enemyScript = comp.mainScript; + if (IsInOpenDoorNormallyState(enemyScript)) { + callNormalScript = true; + OnTriggerStay(other); + return; + } + + var direction = transform.position - comp.transform.position; + direction.y = 0f; + direction = direction.normalized; + + var timeDelta = Time.deltaTime * GetDoorDamagePerSecond(enemyScript); + if (isLocked) timeDelta *= PluginConfig.Instance.lockedDoorEnemyDamageMultiplierValue; + destroyMeter += timeDelta; + + ApplyDoorPieceDamage(direction); + } + } + + static readonly Dictionary> EnemyToDoorOpen = new Dictionary>(){ + { typeof(CentipedeAI), (e) => e.currentBehaviourStateIndex <= 1 }, + { typeof(CrawlerAI), (e) => e.currentBehaviourStateIndex == 0 }, + { typeof(FlowermanAI), (e) => e.currentBehaviourStateIndex <= 1 }, + { typeof(HoarderBugAI), (e) => e.currentBehaviourStateIndex <= 1 }, + { typeof(JesterAI), (e) => e.currentBehaviourStateIndex <= 1 }, + { typeof(MaskedPlayerEnemy), (e) => e.currentBehaviourStateIndex == 0 }, + { typeof(NutcrackerEnemyAI), (e) => e.currentBehaviourStateIndex <= 1 }, + { typeof(PufferAI), (e) => e.currentBehaviourStateIndex == 0 }, + { typeof(SandSpiderAI), (e) => e.currentBehaviourStateIndex <= 1 }, + { typeof(SpringManAI), (e) => e.currentBehaviourStateIndex == 0 }, + { typeof(KnightVariant), (e) => e.currentBehaviourStateIndex == 0 }, + }; + + static readonly Dictionary EnemyDoorDamagePerSecond = new Dictionary(){ + { typeof(BlobAI), 6.25f }, + { typeof(CentipedeAI), 25f }, + { typeof(CrawlerAI), 50f }, + { typeof(DressGirlAI), 50f }, + { typeof(FlowermanAI), 50f }, + { typeof(HoarderBugAI), 50f }, + { typeof(JesterAI), 50f }, + { typeof(MaskedPlayerEnemy), 50f }, + { typeof(NutcrackerEnemyAI), 50f }, + { typeof(PufferAI), 25f }, + { typeof(SandSpiderAI), 25f }, + { typeof(SpringManAI), 12.5f }, + { typeof(KnightVariant), 12.5f }, + }; + + public bool IsInOpenDoorNormallyState(EnemyAI enemy){ + var type = enemy.GetType(); + if (EnemyToDoorOpen.ContainsKey(type)) return EnemyToDoorOpen[type](enemy); + return false; + } + + public float GetDoorDamagePerSecond(EnemyAI enemy){ + var type = enemy.GetType(); + if (EnemyDoorDamagePerSecond.ContainsKey(type)) return EnemyDoorDamagePerSecond[type]; + return enemy.openDoorSpeedMultiplier * 100f; + } + + public void ApplyDoorPieceDamage(Vector3 direction, bool ignoreInternalTimer = false){ + // full destruction + if (destroyMeter >= 100f) DestroyAndOpenDoorServerRpc(direction); + + // health destruction + var passesTimeVibeCheck = ignoreInternalTimer || Time.time >= nextInternalTimer; + if (passesTimeVibeCheck && healthTilePieces.Count > 0) { + var newDamage = Mathf.FloorToInt(destroyMeter * healthTilePiecesCount) / 100; + var damageDiff = newDamage - currentDamage; + if (damageDiff > 0) { + DestroyDoorHealthPiecesClientRpc(newDamage - currentDamage, direction); + nextInternalTimer = Time.time + internalTimerCooldown; + currentDamage = newDamage; + } + } + } + + [ServerRpc(RequireOwnership = false)] + public void ApplyDamageServerRpc(Vector3 direction, float damage){ + destroyMeter += damage; + ApplyDoorPieceDamage(direction, true); + } + + [ServerRpc(RequireOwnership = false)] + public void DestroyAndOpenDoorServerRpc(Vector3 direction){ + Plugin.logger.LogInfo("Destro time"); + OpenDoorAsEnemyClientRpc(); + DestroyDoorClientRpc(direction); + } + + [ClientRpc] + public void DestroyDoorClientRpc(Vector3 direction){ + DestroyDoorPieces(healthTilePieces, healthTilePieces.Count, direction); + DestroyDoorPieces(frameTilePieces, frameTilePieces.Count, direction); + PlayAudio(fullyDestroyedAudioClip); + + disableAnimator.enabled = false; + foreach(var c in disableColliders){ + c.enabled = false; + } + + //doorTrigger.interactable = false; + //doorTrigger.disabledHoverTip = string.Empty; + + isLocked = false; + isDoorOpened = true; + navMeshObstacle.enabled = false; + } + + [ClientRpc] + public void DestroyDoorHealthPiecesClientRpc(int count, Vector3 direction){ + DestroyDoorPieces(healthTilePieces, count, direction); + PlayAudio(destroyAudioClips[UnityEngine.Random.Range(0, destroyAudioClips.Length)]); + } + + public void DestroyDoorPieces(List tilePieces, int count, Vector3 direction){ + while(count > 0 && tilePieces.Count > 0){ + var lastIndex = tilePieces.Count - 1; + var target = tilePieces[lastIndex]; + + var prop = Instantiate(propPrefab); + prop.GetComponent().CreateProp(target, direction); + target.SetActive(false); + + tilePieces.RemoveAt(lastIndex); + count--; + } + } + + public void PlayAudio(AudioClip clip){ + audioSource.PlayOneShot(clip); + WalkieTalkie.TransmitOneShotAudio(audioSource, clip); + } + + public static bool ScarletDoorRaycast(GrabbableObject __instance, Vector3 position, Vector3 forward, float distance, out ScarletDoorLock door){ + door = null; + var isPlayerHolding = __instance.isHeld && __instance.playerHeldBy == GameNetworkManager.Instance.localPlayerController; + var isServerCheck = __instance.IsServer && !isPlayerHolding; + if (!isPlayerHolding && !isServerCheck) return false; + + var ray = new Ray(position, forward); + var layer = 1 << LayerMask.NameToLayer("InteractableObject"); + if (Physics.Raycast(ray, out var hit, distance, layer, QueryTriggerInteraction.Ignore)) { + door = hit.transform.GetComponent(); + return door != null; + } + return false; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFireExit.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFireExit.cs new file mode 100644 index 0000000..915548e --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFireExit.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using GameNetcodeStuff; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletFireExit : MonoBehaviour { + + public bool enablePortalAnimation; + + [Header("Animations")] + public MeshRenderer portalRenderer; + public MeshRenderer blackRenderer; + + private Material portalMaterial; + private Material blackMaterial; + private Coroutine timerCoroutine; + private Coroutine currentCoroutine; + + public const float animationTime = 0.4f; + + void Start(){ + portalMaterial = portalRenderer.material; + blackMaterial = blackRenderer.material; + } + + public void DisableEnablePortal(){ + if (!enablePortalAnimation) return; + + if (timerCoroutine != null) StopCoroutine(timerCoroutine); + timerCoroutine = StartCoroutine(DisableEnablePortalCoroutine()); + } + + private IEnumerator DisableEnablePortalCoroutine(){ + DisablePortal(); + yield return new WaitForSeconds(3f); + EnablePortal(); + } + + public void DisablePortal(){ + if (currentCoroutine != null) StopCoroutine(currentCoroutine); + currentCoroutine = StartCoroutine(SetPortalCoroutine(-0.5f, 0f)); + } + + public void EnablePortal(){ + if (currentCoroutine != null) StopCoroutine(currentCoroutine); + currentCoroutine = StartCoroutine(SetPortalCoroutine(1.5f, 1f)); + } + + private IEnumerator SetPortalCoroutine(float dissolveTarget, float blackTarget){ + float dv; + Color bv; + do { + dv = portalMaterial.GetFloat("_DissolveValue"); + dv = Mathf.MoveTowards(dv, dissolveTarget, 2f / animationTime * Time.deltaTime); + portalMaterial.SetFloat("_DissolveValue", dv); + + bv = blackMaterial.color; + bv.a = Mathf.MoveTowards(bv.a, blackTarget, 1f / animationTime * Time.deltaTime); + blackMaterial.color = bv; + + yield return null; + } while (dv != dissolveTarget && bv.a != blackTarget); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFrame.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFrame.cs new file mode 100644 index 0000000..f8bbb5e --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletFrame.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using GameNetcodeStuff; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletFrame : NetworkBehaviour { + + //public MeshRenderer renderer; + //public Material[] materials; + + public void OnInteract(PlayerControllerB player){ + var direction = player.transform.position - transform.position; + direction.y = 0f; + + ChangeDirection(direction); + ChangeDirectionServerRpc(direction); + } + + /* + [ClientRpc] + public void UpdateMaterialClientRpc(int value){ + var mats = new List(2); + renderer.GetMaterials(mats); + + mats[1] = materials[value % materials.Length]; + renderer.SetMaterials(mats); + } + */ + + [ServerRpc(RequireOwnership = false)] + public void ChangeDirectionServerRpc(Vector3 direction){ + ChangeDirectionClientRpc(direction); + } + + [ClientRpc] + public void ChangeDirectionClientRpc(Vector3 direction){ + ChangeDirection(direction); + } + + public void ChangeDirection(Vector3 direction){ + transform.rotation = Quaternion.LookRotation(direction); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletHDRISky.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletHDRISky.cs new file mode 100644 index 0000000..766f848 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletHDRISky.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.HighDefinition; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; + +/* + +namespace ScarletMansion { + public class ScarletHDRISky : ScarletLighting { + + public Volume volume; + + public HDRISky GetSky(){ + volume.sharedProfile.TryGet(out var sky); + return sky; + } + + public VisualEnvironment GetEnv(){ + volume.sharedProfile.TryGet(out var env); + return env; + } + + public override void DisableLights() { + volume.enabled = false; + } + + public override void UpdateLight(float ratio) { + GetSky().multiplier.value = valueRatio; + GetEnv().skyAmbientMode.value = skyMode; + volume.enabled = trueEnabled; + } + + public int value = 100; + public float valueRatio = 1f; + + public bool trueEnabled = true; + public SkyAmbientMode skyMode = SkyAmbientMode.Dynamic; + + void Update(){ + int direction = 0; + if (IfKeyPress(Keyboard.current.minusKey, Keyboard.current.numpadMinusKey)){ + direction = -1; + } else if (IfKeyPress(Keyboard.current.equalsKey, Keyboard.current.numpadPlusKey)){ + direction = 1; + } + + if (IfKeyPress(Keyboard.current.backspaceKey)){ + trueEnabled = !trueEnabled; + Plugin.logger.LogInfo(trueEnabled); + } + + if (IfKeyPress(Keyboard.current.digit0Key, Keyboard.current.numpad0Key)){ + if (skyMode == SkyAmbientMode.Dynamic) skyMode = SkyAmbientMode.Static; + else skyMode = SkyAmbientMode.Dynamic; + Plugin.logger.LogInfo(skyMode); + } + + if (direction != 0){ + value = Mathf.Clamp(value + direction * 5, -100, 200); + valueRatio = value * 0.01f; + Plugin.logger.LogInfo(value); + } + + } + + bool IfKeyPress(params KeyControl[] keys){ + foreach(var k in keys){ + if (k.wasPressedThisFrame) return true; + } + return false; + } + + } + + + +} + +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletLighting.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletLighting.cs new file mode 100644 index 0000000..5c320f6 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletLighting.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +/* + +namespace ScarletMansion { + public abstract class ScarletLighting : MonoBehaviour { + + public abstract void UpdateLight(float ratio); + public abstract void DisableLights() ; + + } +} + +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletPlayerControllerB.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletPlayerControllerB.cs new file mode 100644 index 0000000..5edd744 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletPlayerControllerB.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using GameNetcodeStuff; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletPlayerControllerB : MonoBehaviour { + + public PlayerControllerB player; + + public void Initialize(PlayerControllerB player){ + this.player = player; + CreateHelmetForFlashlight(player, Assets.flashlight, 0); + CreateHelmetForFlashlight(player, Assets.flashlightBB, 1); + } + + public static void CreateHelmetForFlashlight(PlayerControllerB player, Assets.Flashlight flashlight, int index){ + try { + var helmetLights = player.allHelmetLights.ToList(); + var light = helmetLights[index]; + var parent = light.transform.parent; + var gameObj = GameObject.Instantiate(light.gameObject, parent); + gameObj.name = $"scarletlight{index}"; + + var newFlashlightHelmetIndex = helmetLights.Count; + + if (flashlight.scarletHelmetIndex != newFlashlightHelmetIndex) + Plugin.logger.LogInfo($"Created helmet light for scarlet flashlight {index}. Updated index from {flashlight.scarletHelmetIndex} to {newFlashlightHelmetIndex}"); + + flashlight.scarletHelmetIndex = newFlashlightHelmetIndex; + helmetLights.Add(gameObj.GetComponent()); + player.allHelmetLights = helmetLights.ToArray(); + + } catch (Exception e) { + Plugin.logger.LogError("Failed to create helmet light for scarlet flashlight"); + Plugin.logger.LogError(e.ToString()); + + flashlight.scarletHelmetIndex = index; + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletProp.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletProp.cs new file mode 100644 index 0000000..33a909c --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletProp.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace ScarletMansion.GamePatch.Components { + public class ScarletProp : MonoBehaviour { + + [Header("References")] + public MeshFilter meshFilter; + public MeshRenderer meshRenderer; + public MeshCollider meshCollider; + public Rigidbody rigidbody; + + public static float minForce = 4f; + public static float maxForce = 6f; + public static float minAngle = -15f; + public static float maxAngle = 15f; + + public static float despawnTime = 10f; + + public void CreateProp(GameObject target, Vector3 direction){ + gameObject.layer = 6; + + transform.position = target.transform.position; + transform.rotation = target.transform.rotation; + transform.localScale = target.transform.lossyScale * 0.9f; // so they don't get stuck in any frame + + var targetMesh = target.GetComponent().sharedMesh; + meshFilter.sharedMesh = targetMesh; + meshCollider.sharedMesh = targetMesh; + meshRenderer.materials = target.GetComponent().materials; + + var randomDirection = Quaternion.Euler(Random.Range(-minAngle, maxAngle), Random.Range(-minAngle, maxAngle), Random.Range(-minAngle, maxAngle)) * direction; + var force = randomDirection * Random.Range(minForce, maxForce); + rigidbody.AddForce(force, ForceMode.Impulse); + + StartCoroutine(DespawnTimer()); + } + + IEnumerator DespawnTimer(){ + yield return new WaitForSeconds(despawnTime); + gameObject.SetActive(false); + } + + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletVent.cs b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletVent.cs new file mode 100644 index 0000000..0c3d7fb --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Components/ScarletVent.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion { + public class ScarletVent : MonoBehaviour { + + public AudioSource shakeAudioSource; + public AudioSource summonAudioSource; + public PlayAudioAnimationEvent audioEvent; + + [Header("Shake")] + public ParticleSystem spawningPartciles; + public ParticleSystem lightingParticles; + public ParticleSystem emitParticles; + public float volumeToRate = 1f; + + public static bool prewarm; + + void Start(){ + if (!prewarm){ + OpenVentClientRpc(); + prewarm = true; + Plugin.logger.LogInfo("Prewarming particles by forcing it emit now lmao"); + } + } + + private void PlayParticleSystem(ParticleSystem ps){ + if (ps.isPlaying) return; + ps.Play(); + } + + private void StopParticleSystem(ParticleSystem ps){ + if (!ps.isPlaying) return; + ps.Stop(); + } + + public void OpenVentClientRpc(){ + emitParticles.Play(); + audioEvent.PlayAudio1Oneshot(); + } + + void Update(){ + var isPlaying = shakeAudioSource.isPlaying; + if (isPlaying){ + PlayParticleSystem(spawningPartciles); + SetEmissionRate(spawningPartciles, shakeAudioSource.volume * volumeToRate); + + if (shakeAudioSource.volume >= 0.8f) PlayParticleSystem(lightingParticles); + else StopParticleSystem(lightingParticles); + } else { + StopParticleSystem(spawningPartciles); + StopParticleSystem(lightingParticles); + } + } + + private void SetEmissionRate(ParticleSystem ps, float mult){ + var emis = ps.emission; + emis.rateOverTimeMultiplier = mult; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/DoorLockPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/DoorLockPatch.cs new file mode 100644 index 0000000..df78ff0 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/DoorLockPatch.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; + +namespace ScarletMansion.GamePatch { + public class DoorLockPatch { + + [HarmonyPatch(typeof(DoorLock), "Awake")] + [HarmonyPostfix] + public static void AwakePatch(ref DoorLock __instance){ + var scarlet = __instance as Components.ScarletDoorLock; + scarlet?.AwakeScarlet(); + } + + [HarmonyPatch(typeof(DoorLock), "OnTriggerStay")] + [HarmonyPrefix] + public static bool OnTriggerStayPatch(ref DoorLock __instance, Collider other){ + var scarlet = __instance as Components.ScarletDoorLock; + if (scarlet){ + if (!scarlet.callNormalScript) { + scarlet.OnTriggerStayScarlet(other); + return false; + } + scarlet.callNormalScript = false; + } + + return true; + } + + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Enemies/KnightVariant.cs b/ScarletMansion/ScarletMansion/GamePatch/Enemies/KnightVariant.cs new file mode 100644 index 0000000..249d5f1 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Enemies/KnightVariant.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using UnityEngine.AI; +using GameNetcodeStuff; + +namespace ScarletMansion { + + public class KnightVariant : EnemyAI { + + public AISearchRoutine searchForPlayers; + // private float checkLineOfSightInterval; + // private bool hasEnteredChaseMode; + private bool stoppingMovement; + private bool hasStopped; + public AnimationStopPoints animStopPoints; + private float currentChaseSpeed = 14.5f * 0.75f; + private float currentAnimSpeed = 1f; + private PlayerControllerB previousTarget; + private bool wasOwnerLastFrame; + private float stopAndGoMinimumInterval; + private float timeSinceHittingPlayer; + public AudioClip[] springNoises; + public Collider mainCollider; + + public override void Start(){ + base.Start(); + + if (IsOwner && KnightSpawnManager.Instance) { + var index = KnightSpawnManager.Instance.GetSpawnPointIndex(); + if (index == -1) return; + + SyncKnightReplacementClientRpc(index); + } + } + + public override void DoAIInterval() { + base.DoAIInterval(); + + var startofround = StartOfRound.Instance; + + if (startofround.allPlayersDead) return; + if (isEnemyDead) return; + + var currentBehaviourStateIndex = this.currentBehaviourStateIndex; + + // state == 1, anger + if (currentBehaviourStateIndex != 0) { + if (currentBehaviourStateIndex != 1) return; + if (searchForPlayers.inProgress) StopSearch(searchForPlayers, true); + + if (TargetClosestPlayer(1.5f, false, 70f)) { + if (previousTarget != targetPlayer) { + previousTarget = targetPlayer; + ChangeOwnershipOfEnemy(targetPlayer.actualClientId); + } + movingTowardsTargetPlayer = true; + return; + } + SwitchToBehaviourState(0); + ChangeOwnershipOfEnemy(startofround.allPlayerScripts[0].actualClientId); + } + // state == 0, searching + else { + if (!IsServer) { + ChangeOwnershipOfEnemy(startofround.allPlayerScripts[0].actualClientId); + return; + } + + for (int i = 0; i < ModPatch.ModCompability.GetStartOfRoundScriptLength(); i++) { + var player = startofround.allPlayerScripts[i]; + var canTarget = PlayerIsTargetable(player, false, false); + if (!canTarget) continue; + + var hasLOS = !Physics.Linecast(transform.position + Vector3.up * 0.5f, player.gameplayCamera.transform.position, startofround.collidersAndRoomMaskAndDefault); + var withinVision = Vector3.SqrMagnitude(transform.position - player.transform.position) < 30f * 30f; + if (hasLOS && withinVision) { + SwitchToBehaviourState(1); + return; + } + } + + agent.speed = 6f; + if (!searchForPlayers.inProgress) { + movingTowardsTargetPlayer = false; + StartSearch(transform.position, searchForPlayers); + return; + } + } + } + + public override void Update() { + base.Update(); + if (isEnemyDead) return; + if (timeSinceHittingPlayer >= 0f) timeSinceHittingPlayer -= Time.deltaTime; + + var currentBehaviourStateIndex = this.currentBehaviourStateIndex; + var startofround = StartOfRound.Instance; + + // state == 1, anger + if (currentBehaviourStateIndex != 0 && currentBehaviourStateIndex == 1) { + if (IsOwner) { + if (stopAndGoMinimumInterval > 0f) stopAndGoMinimumInterval -= Time.deltaTime; + if (!wasOwnerLastFrame) { + wasOwnerLastFrame = true; + if (!stoppingMovement && timeSinceHittingPlayer < 0.12f) agent.speed = currentChaseSpeed; + else agent.speed = 0f; + } + + var flag = false; + for (int i = 0; i < ModPatch.ModCompability.GetStartOfRoundScriptLength(); i++) { + var player = startofround.allPlayerScripts[i]; + var canTarget = PlayerIsTargetable(player, false, false); + if (!canTarget) continue; + + var hasLOS = player.HasLineOfSightToPosition(transform.position + Vector3.up * 1.6f, 68f, 60, -1f); + var notTooClose = Vector3.SqrMagnitude(player.gameplayCamera.transform.position - eye.position) > 0.3f * 0.3f; + if (hasLOS && notTooClose) { + flag = true; + } + } + if (stunNormalizedTimer > 0f) { + flag = true; + } + if (flag != stoppingMovement && stopAndGoMinimumInterval <= 0f) { + stopAndGoMinimumInterval = 0.15f; + if (flag) SetAnimationStopServerRpc(); + else SetAnimationGoServerRpc(); + stoppingMovement = flag; + } + } + + // stopping movement + if (stoppingMovement) { + if (animStopPoints.canAnimationStop) { + var localPlayer = GameNetworkManager.Instance.localPlayerController; + if (!hasStopped) { + hasStopped = true; + if (localPlayer.HasLineOfSightToPosition(transform.position, 70f, 25, -1f)) { + var num = Vector3.SqrMagnitude(transform.position - localPlayer.transform.position); + if (num < 4f * 4f) localPlayer.JumpToFearLevel(0.9f, true); + else if (num < 9f * 9f) localPlayer.JumpToFearLevel(0.4f, true); + } + + if (currentAnimSpeed > 2f) { + RoundManager.PlayRandomClip(creatureVoice, springNoises, false, 1f, 0); + if (animStopPoints.animationPosition == 1) creatureAnimator.SetTrigger("springBoing"); + else creatureAnimator.SetTrigger("springBoingPosition2"); + } + } + + var notTooClose = Vector3.SqrMagnitude(localPlayer.transform.position - transform.position) > 0.25f * 0.25f; + if (mainCollider.isTrigger && notTooClose) mainCollider.isTrigger = false; + + creatureAnimator.SetFloat("walkSpeed", 0f); + currentAnimSpeed = 0f; + if (IsOwner) { + agent.speed = 0f; + return; + } + } + } + + // we so back to killing + else { + if (hasStopped) { + hasStopped = false; + mainCollider.isTrigger = true; + } + + currentAnimSpeed = Mathf.Lerp(currentAnimSpeed, 5f, 3f * Time.deltaTime); + creatureAnimator.SetFloat("walkSpeed", currentAnimSpeed); + if (IsOwner) agent.speed = Mathf.Lerp(agent.speed, currentChaseSpeed, 4.5f * Time.deltaTime); + + } + } + } + + [ServerRpc] + public void SetAnimationStopServerRpc() { + SetAnimationStopClientRpc(); + } + + [ClientRpc] + public void SetAnimationStopClientRpc() { + stoppingMovement = true; + } + + [ServerRpc] + public void SetAnimationGoServerRpc() { + SetAnimationGoClientRpc(); + } + + [ClientRpc] + public void SetAnimationGoClientRpc() { + stoppingMovement = false; + } + + public override void OnCollideWithPlayer(Collider other) { + base.OnCollideWithPlayer(other); + + if (stoppingMovement) return; + if (currentBehaviourStateIndex != 1) return; + if (timeSinceHittingPlayer >= 0f) return; + + var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false); + if (playerControllerB != null) { + timeSinceHittingPlayer = 0.2f; + playerControllerB.DamagePlayer(90, true, true, CauseOfDeath.Mauling, 1, false, default(Vector3)); + playerControllerB.JumpToFearLevel(1f, true); + } + } + + [ClientRpc] + public void SyncKnightReplacementClientRpc(int index){ + SyncKnightReplacement(index); + } + + public void SyncKnightReplacement(int index){ + Plugin.logger.LogInfo($"Spawning knight at {index}"); + + try { + var target = KnightSpawnManager.Instance.GetSpawnPointTransform(index); + target.gameObject.SetActive(false); + + transform.position = target.position; + transform.rotation = target.rotation; + serverPosition = target.position; + + if (agent == null) agent = GetComponentInChildren(); + agent.Warp(target.position); + + if (IsOwner) { + SyncPositionToClients(); + } + } catch (Exception e){ + Plugin.logger.LogError($"Tried to knight spawn at {index}, but completely failed"); + Plugin.logger.LogError(e); + } + } + + + + /* + public override void OnCollideWithPlayer(Collider other) { + //base.OnCollideWithPlayer(other); + if (stoppingMovement) return; + if (currentBehaviourStateIndex != 1) return; + if (timeSinceHittingPlayer >= 0f) return; + + var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false); + if (playerControllerB != null) { + timeSinceHittingPlayer = 1f; + playerControllerB.DamagePlayer(90, true, true, CauseOfDeath.Bludgeoning, 0, false, default(Vector3)); + playerControllerB.JumpToFearLevel(1f, true); + } + + KnightAttackServerRpc(); + } + + [ServerRpc] + public void KnightAttackServerRpc(){ + creatureAnimator.SetTrigger("swinging"); + stoppingMovement = true; + hasStopped = true; + } + */ + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/EnemyVentPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/EnemyVentPatch.cs new file mode 100644 index 0000000..eaf226e --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/EnemyVentPatch.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; + +namespace ScarletMansion.GamePatch { + [HarmonyPatch(typeof(EnemyVent))] + public class EnemyVentPatch { + + /* + [HarmonyPostfix] + [HarmonyPatch("BeginVentSFX")] + public static void BeginVentSFXPatch(ref EnemyVent __instance){ + var comp = __instance.GetComponent(); + if (comp) { + comp.BeginVentSFX(); + } + } + */ + + public static bool setActive = false; + + [HarmonyPrefix] + [HarmonyPatch("OpenVentClientRpc")] + public static void OpenVentClientRpcPatchPre(ref EnemyVent __instance){ + setActive = true; + } + + + [HarmonyPostfix] + [HarmonyPatch("OpenVentClientRpc")] + public static void OpenVentClientRpcPatchPost(ref EnemyVent __instance){ + if (setActive == false) return; + + var comp = __instance.GetComponent(); + if (comp) { + Plugin.logger.LogInfo("Doing scalet open vent"); + comp.OpenVentClientRpc(); + } + + setActive = false; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/ExtendedDungeonMapLoad.cs b/ScarletMansion/ScarletMansion/GamePatch/ExtendedDungeonMapLoad.cs new file mode 100644 index 0000000..2caa796 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/ExtendedDungeonMapLoad.cs @@ -0,0 +1,214 @@ +/* +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LethalLevelLoader; +using UnityEngine; + +namespace ScarletMansion { + public class ExtendedDungeonMapLoad { + + public static List GetCustomMoons(string value, int rarity){ + var list = new List(); + var config = value.ToLowerInvariant(); + + if (config == "all") { + list.Add(new DynamicLevelTags("Vanilla", rarity)); + list.Add(new DynamicLevelTags("Custom", rarity)); + Plugin.logger.LogInfo("Loading SDM for all moons"); + } else if (config == "vanilla") { + list.Add(new DynamicLevelTags("Vanilla", rarity)); + Plugin.logger.LogInfo("Loading SDM for all vanilla moons"); + } else if (config == "modded") { + list.Add(new DynamicLevelTags("Custom", rarity)); + Plugin.logger.LogInfo("Loading SDM for all modded moons"); + } else if (config == "paid") { + list.Add(new DynamicRoutePrices(new Vector2(1, 9999), rarity)); + Plugin.logger.LogInfo("Loading SDM for all paid moons"); + } else if (config == "free") { + list.Add(new DynamicRoutePrices(new Vector2(0, 9999), rarity)); + Plugin.logger.LogInfo("Loading SDM for all free moons"); + } else { + Plugin.logger.LogInfo("Loading SDM with predefined moon list"); + var moonEntries = config.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach(var entry in moonEntries){ + var moonValues = entry.Split(new[] { '@' }, StringSplitOptions.RemoveEmptyEntries); + var len = moonValues.Length; + if (len > 2){ + Plugin.logger.LogError($"Invalid setup for moon rarity config: {entry}. Skipping"); + continue; + } + if (len == 1){ + Plugin.logger.LogInfo($"Loading SDM for moon {entry} at default rarity of {rarity}"); + list.Add(new ManualPlanetNameReference(entry, rarity)); + } else { + var moon = moonValues[0]; + var moonRarity = moonValues[1]; + if (int.TryParse(moonRarity, out var moonRarityValue)){ + Plugin.logger.LogInfo($"Loading SDM for moon {moon} at rarity of {moonRarityValue}"); + list.Add(new ManualPlanetNameReference(moon, moonRarityValue)); + continue; + } + + Plugin.logger.LogError($"Failed to parse rarity value for moon {moon} rarity {moonRarity}. Skipping"); + } + } + } + + return list; + } + + public static void ClearLastCustomMoonEntryList(){ + + Plugin.logger.LogInfo("Clearing previous custom dungeon load"); + + var dun = Assets.dungeonExtended; + var lastList = Assets.customMoonEntryList; + if (lastList != null) { + foreach(var item in lastList){ + item.Remove(dun); + } + } + + Assets.rendEntry.Remove(dun); + Assets.dineEntry.Remove(dun); + Assets.titanEntry.Remove(dun); + + Assets.customMoonEntryList = null; + } + + public static void AddToExtendedDungeonFlow(List list){ + var dun = Assets.dungeonExtended; + foreach(var item in list){ + item.Add(dun); + } + + Assets.customMoonEntryList = list; + } + + public abstract class CustomMoonEntry { + + public virtual void Add(ExtendedDungeonFlow flow) { + Plugin.logger.LogInfo($"Added {ToString()}"); + } + + public virtual void Remove(ExtendedDungeonFlow flow) { + Plugin.logger.LogInfo($"Removed {ToString()}"); + } + + public abstract void SetRarity(int rarity); + } + + public class ManualContentSourceNameReference : CustomMoonEntry { + + public StringWithRarity value; + + public ManualContentSourceNameReference(string title, int rarity){ + value = new StringWithRarity(title, rarity); + } + + public override void Add(ExtendedDungeonFlow flow){ + base.Add(flow); + flow.manualContentSourceNameReferenceList.Add(value); + } + + public override void Remove(ExtendedDungeonFlow flow){ + if (flow.manualContentSourceNameReferenceList.Remove(value)) base.Remove(flow); + else Plugin.logger.LogInfo($"Tried to remove {ToString()} but it's already gone"); + } + + public override void SetRarity(int rarity) { + value.Rarity = rarity; + } + + public override string ToString() { + return $"ManualContentSourceNameReference: ({value.Name}, {value.Rarity})"; + } + } + + public class DynamicLevelTags : CustomMoonEntry { + + public StringWithRarity value; + + public DynamicLevelTags(string title, int rarity){ + value = new StringWithRarity(title, rarity); + } + + public override void Add(ExtendedDungeonFlow flow){ + base.Add(flow); + flow.dynamicLevelTagsList.Add(value); + } + + public override void Remove(ExtendedDungeonFlow flow){ + if (flow.dynamicLevelTagsList.Remove(value)) base.Remove(flow); + else Plugin.logger.LogInfo($"Tried to remove {ToString()} but it's already gone"); + } + + public override void SetRarity(int rarity) { + value.Rarity = rarity; + } + + public override string ToString() { + return $"DynamicLevelTags: ({value.Name}, {value.Rarity})"; + } + } + + public class ManualPlanetNameReference : CustomMoonEntry { + + public StringWithRarity value; + + public ManualPlanetNameReference(string title, int rarity){ + value = new StringWithRarity(title, rarity); + } + + public override void Add(ExtendedDungeonFlow flow){ + base.Add(flow); + flow.manualPlanetNameReferenceList.Add(value); + } + + public override void Remove(ExtendedDungeonFlow flow){ + if (flow.manualPlanetNameReferenceList.Remove(value)) base.Remove(flow); + else Plugin.logger.LogInfo($"Tried to remove {ToString()} but it's already gone"); + } + + public override void SetRarity(int rarity) { + value.Rarity = rarity; + } + + public override string ToString() { + return $"ManualPlanetNameReference: ({value.Name}, {value.Rarity})"; + } + } + + public class DynamicRoutePrices : CustomMoonEntry { + + public Vector2WithRarity value; + + public DynamicRoutePrices(Vector2 price, int rarity){ + value = new Vector2WithRarity(price, rarity); + } + + public override void Add(ExtendedDungeonFlow flow){ + base.Add(flow); + flow.dynamicRoutePricesList.Add(value); + } + + public override void Remove(ExtendedDungeonFlow flow){ + if (flow.dynamicRoutePricesList.Remove(value)) base.Remove(flow); + else Plugin.logger.LogInfo($"Tried to remove {ToString()} but it's already gone"); + } + + public override void SetRarity(int rarity) { + value.Rarity = rarity; + } + + public override string ToString() { + return $"DynamicRoutePrices: ([{value.Min}, {value.Max}], {value.Rarity})"; + } + } + + } +} +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixBaseClass.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixBaseClass.cs new file mode 100644 index 0000000..13da071 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixBaseClass.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.GamePatch.FixValues { + + public abstract class FixBaseClass : MonoBehaviour where T: Component { + + public T target; + + void Reset(){ + target = GetComponent(); + } + + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixCeilingLightValue.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixCeilingLightValue.cs new file mode 100644 index 0000000..6e270c4 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixCeilingLightValue.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.FixValues { + public class FixCeilingLightValue : FixBaseClass { + + public void Awake(){ + var weight = target.Props.Weights[1]; + var value = PluginConfig.Instance.ceilingLightsWeightValue; + weight.MainPathWeight = value; + weight.BranchPathWeight = value; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixFireExit.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixFireExit.cs new file mode 100644 index 0000000..59292f5 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixFireExit.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using ScarletMansion.GamePatch.Components; + +namespace ScarletMansion.GamePatch.FixValues { + public class FixFireExit : FixBaseClass { + + public bool vanillaSetup; + + [Header("Targets")] + public GameObject sdmGameObject; + public GameObject vanillaGameObject; + public List vanillaGameObjectGlobalPropTargets; + + [Header("SDM Fire Exit")] + public GameObject sdmRenderGameObject; + public ScarletFireExit sdmFireExit; + + [Header("Vanilla References")] + public SpawnSyncedObject vanillaSpawnSyncedObject; + public GameObject vanillaLightGameObject; + public GameObject vanillaCube001GameObject; + public GameObject vanillaCube002GameObject; + + public bool EnableVanillaFireExit => !PluginConfig.Instance.useSDMFireExitsValue && vanillaSetup; + + public void Awake(){ + if (EnableVanillaFireExit){ + sdmGameObject.SetActive(false); + sdmRenderGameObject.SetActive(false); + vanillaGameObject.SetActive(true); + } + } + + public void OnDisable(){ + foreach(var t in vanillaGameObjectGlobalPropTargets) + t.SetActive(false); + } + + public void OnEnable(){ + foreach(var t in vanillaGameObjectGlobalPropTargets) + t.SetActive(true); + } + + public void Setup(GameObject blocker){ + try { + vanillaSpawnSyncedObject.spawnPrefab = blocker.GetComponentInChildren().spawnPrefab; + CopyMeshAndRenderers("Light", vanillaLightGameObject); + CopyMeshAndRenderers("Cube.001", vanillaCube001GameObject); + CopyMeshAndRenderers("Cube.002", vanillaCube002GameObject); + + void CopyMeshAndRenderers(string sourceName, GameObject target){ + var source = Utility.FindChildRecurvisely(blocker.transform, sourceName); + target.GetComponent().sharedMesh = source.GetComponent().sharedMesh; + target.GetComponent().sharedMaterials = source.GetComponent().sharedMaterials; + } + + vanillaSetup = true; + //Plugin.logger.LogInfo("Vanilla fire exit set up"); + } catch (Exception e){ + Plugin.logger.LogInfo("Failed to setup vanilla fire exit. The 'Use SDM Fire Exits' config will be ignored"); + Plugin.logger.LogError(e.ToString()); + + vanillaSetup = false; + } + } + + public void ForcefullyEnableDoorway(){ + gameObject.SetActive(true); + sdmGameObject.SetActive(false); + sdmRenderGameObject.SetActive(false); + vanillaGameObject.SetActive(false); + } + + public void ForcefullyEnableSDMRender(){ + sdmRenderGameObject.SetActive(true); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHallwayLightValue.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHallwayLightValue.cs new file mode 100644 index 0000000..5f931b1 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHallwayLightValue.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.FixValues { + public class FixHallwayLightValue : FixBaseClass { + + public void Awake(){ + var weight = target.Props.Weights[1]; + var value = PluginConfig.Instance.hallwayLightsWeightValue; + weight.MainPathWeight = value; + weight.BranchPathWeight = value; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHoverIcon.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHoverIcon.cs new file mode 100644 index 0000000..503d5be --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixHoverIcon.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.GamePatch.FixValues { + public class FixHoverIcon : MonoBehaviour { + + public InteractTrigger trigger; + + public void Awake(){ + trigger.hoverIcon = Assets.hoverIcon; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixRandomMapObject.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixRandomMapObject.cs new file mode 100644 index 0000000..d49c869 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixRandomMapObject.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.FixValues { + public class FixRandomMapObject : FixBaseClass { + + [Flags] + public enum MapObject { + None = 0, + Turret = 1, + Mine = 2, + SpikeTrap = 4 + } + + public MapObject mapObjectFlag; + + public void Awake(){ + target.spawnablePrefabs = new List(); + if (!Assets.dungeonMapHazardFound) return; + + if (mapObjectFlag.HasFlag(MapObject.Turret)) { + target.spawnablePrefabs.Add(Assets.dungeonTurretMapHazard); + } + if (mapObjectFlag.HasFlag(MapObject.Mine)) { + target.spawnablePrefabs.Add(Assets.dungeonMinesMapHazard); + } + if (mapObjectFlag.HasFlag(MapObject.SpikeTrap)) { + target.spawnablePrefabs.Add(Assets.dungeonSpikeTrapMapHazard); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixSpawnItemGroup.cs b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixSpawnItemGroup.cs new file mode 100644 index 0000000..2ba4cb2 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/FixValues/FixSpawnItemGroup.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.GamePatch.FixValues { + public class FixSpawnItemGroup : FixBaseClass { + + public enum ItemGroup { + Null, + Generic, + Tabletop, + Small + } + + public ItemGroup itemGroup; + + public void Awake(){ + switch(itemGroup){ + case ItemGroup.Generic: + target.spawnableItems = Assets.genericItemGroup; + break; + case ItemGroup.Tabletop: + target.spawnableItems = Assets.tabletopItemGroup; + break; + case ItemGroup.Small: + target.spawnableItems = Assets.smallItemGroup; + break; + } + } + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/InitPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/InitPatch.cs new file mode 100644 index 0000000..79aa74e --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/InitPatch.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using HarmonyLib; +using UnityEngine; +using DunGen.Graph; +using ScarletMansion.DunGenPatch; +using LethalLib.Modules; +using Unity.Netcode; +using LethalLevelLoader; +using static UnityEngine.GraphicsBuffer; + +namespace ScarletMansion.GamePatch { + + public class InitPatch { + + private static void CreateNetworkManager(StartOfRound __instance){ + Plugin.logger.LogInfo($"IsServer: {__instance.IsServer}"); + if (__instance.IsServer) { + if (ScarletNetworkManager.Instance == null) { + var prefab = Assets.networkObjectList.scarletNetworkManager; + var obj = GameObject.Instantiate(prefab); + var comp = obj.GetComponent(); + comp.Spawn(false); + Plugin.logger.LogInfo("Created Scarlet Network Manager. We in"); + } else { + Plugin.logger.LogWarning("Scarlet Network Manager already exists? Probably scary"); + } + } + } + + private static IEnumerator WaitForNetworkObject(StartOfRound __instance, Action action){ + while(__instance.NetworkObject.IsSpawned == false) { + yield return null; + } + action(__instance); + } + + [HarmonyPatch(typeof(StartOfRound), "Awake")] + [HarmonyPrefix] + public static void StartOfRound_Start(ref StartOfRound __instance) { + + __instance.StartCoroutine(WaitForNetworkObject(__instance, CreateNetworkManager)); + + /* + foreach(var p in __instance.levels){ + var totalScrap = p.spawnableScrap.Sum(s => s.rarity); + var totalEnemy = p.Enemies.Sum(s => s.rarity); + Plugin.logger.LogInfo($"{p.PlanetName}: S{totalScrap}, E{totalEnemy}"); + }*/ + + // safety cleaning + DunGenPatch.Patch.Deactivate(); + //ScarletLightingManager.Clean(); + + FixMapReferences(__instance); + FixItemPrefabValues(__instance); + + + + /* + foreach(var l in __instance.levels) { + foreach(var i in l.dungeonFlowTypes){ + if (i.id == 1) i.rarity = 0; + } + } + */ + + /* + // my heart can't handle it + foreach(var lev in __instance.levels){ + lev.maxDaytimeEnemyPowerCount = 0; + lev.maxEnemyPowerCount = 0; + lev.maxOutsideEnemyPowerCount = 0; + } + */ + + // DunGenAnalyis.Analysis(Assets.dungeon, __instance, RoundManager.Instance); + } + + [HarmonyPatch(typeof(RoundManager), "Awake")] + [HarmonyPrefix] + public static void RoundManagerAwakePatch(ref RoundManager __instance) { + FixDungeonPrefabValues(__instance); + + /* + foreach(var d in __instance.dungeonFlowTypes) { + Plugin.logger.LogInfo("--------"); + Plugin.logger.LogInfo($"{d.name}: {d.Length.ToString()}"); + Plugin.logger.LogInfo($"{d.BranchMode}: {d.BranchCount.ToString()}"); + foreach(var l in d.Lines){ + Plugin.logger.LogInfo($" {l.Position}, {l.Length}"); + foreach(var a in l.DungeonArchetypes){ + Plugin.logger.LogInfo($" {a.BranchCount.ToString()}, {a.BranchingDepth.ToString()}"); + } + } + Plugin.logger.LogInfo("--------\n"); + } + */ + } + + [HarmonyPatch(typeof(RoundManager), "Start")] + [HarmonyPostfix] + public static void RoundManagerStartPatch(ref RoundManager __instance) { + MyOwnCoroutine.AddCoroutine(UpdateNetworkConfig(__instance)); + } + + public static bool GameReadNullCheck(object target, string targetString, string error) { + if (target == null) { + Plugin.logger.LogError($"Error when find ({targetString}). {error}!"); + return false; + } + return true; + } + + public static void FixMapReferences(StartOfRound round){ + + try { + // use the names of assets instead of the itemName or enemyName + // to account for the korean patch and such + if (Assets.genericItemGroup == null || Assets.tabletopItemGroup == null){ + var bottleItem = round.allItemsList.itemsList + .FirstOrDefault(i => i.name.ToLowerInvariant() == "bottlebin"); + + if (GameReadNullCheck(bottleItem, "bottlebin", "Generic/Tabletop scrap will not spawn")){ + Assets.genericItemGroup = bottleItem.spawnPositionTypes[0]; + Assets.tabletopItemGroup = bottleItem.spawnPositionTypes[1]; + } + } + + if (Assets.smallItemGroup == null){ + var cupValue = round.allItemsList.itemsList + .FirstOrDefault(i => i.name.ToLowerInvariant() == "fancycup"); + + if (GameReadNullCheck(cupValue, "fancycup", "Small scrap will not spawn")){ + Assets.smallItemGroup = cupValue.spawnPositionTypes[1]; + } + } + + var knight = Assets.knight; + if (knight.enemyType == null){ + var springItem = round.levels + .SelectMany(lev => lev.Enemies) + .FirstOrDefault(e => e.enemyType.name.ToLowerInvariant() == "springman"); + + if (GameReadNullCheck(springItem, "springman", "Knight enemy will not spawn")) { + var type = ScriptableObject.Instantiate(springItem.enemyType); + type.name = "Knight"; + type.enemyPrefab = knight.enemy; + type.enemyName = "Knight"; + + knight.enemyType = type; + knight.enemy.GetComponentInChildren().enemyType = type; + Enemies.RegisterEnemy(type, 0, Levels.LevelTypes.None, knight.terminalNode, knight.terminalKeyword); + } + } + + + + } catch (Exception e) { + Plugin.logger.LogError("Error when reading base game's item list. Pretty big error I would say"); + Plugin.logger.LogError(e); + return; + } + } + + public static void FixItemPrefabValues(StartOfRound round){ + + try { + + var itemsList = round.allItemsList.itemsList; + + void QuickItemFix(Item itemToFix, Item itemReference){ + itemToFix.itemIcon = itemReference.itemIcon; + itemToFix.grabSFX = itemReference.grabSFX; + itemToFix.dropSFX = itemReference.dropSFX; + } + + var magClass = itemsList.FirstOrDefault(i => i.name.ToLowerInvariant() == "magnifyingglass"); + if (GameReadNullCheck(magClass, "magnifyingglass", "Item will have missing image and sound assets")) { + QuickItemFix(Assets.scrapItems[0].item, magClass); + QuickItemFix(Assets.scrapItems[1].item, magClass); + } + + var paintClass = itemsList.FirstOrDefault(i => i.name.ToLowerInvariant() == "fancypainting"); + + if (GameReadNullCheck(paintClass, "fancypainting", "Item will have missing image and sound assets")){ + QuickItemFix(Assets.scrapItems[2].item, magClass); + } + + void UpdateFlashlight(Assets.Flashlight flashlight, string search, int fixIndex){ + var flashItem = round.allItemsList.itemsList + .FirstOrDefault(i => i.name.ToLowerInvariant() == search); + + if (GameReadNullCheck(flashItem, search, "CRYSTAL FLASHLIGHT WILL FAIL")){ + var prefab = Assets.networkObjectList.toFixGameObjects[fixIndex]; + + var item = ScriptableObject.Instantiate(flashItem); + item.name = flashlight.assetName; + item.itemName = flashlight.displayName; + item.saveItemVariable = true; + item.batteryUsage *= 1.5f; + item.spawnPrefab = prefab; + item.weight += 0.02f; + item.isScrap = true; + + flashlight.item = item; + flashlight.lethalVanillaItem = flashItem; + + var scarletItemComp = prefab.GetComponentInChildren(); + var refItemComp = flashItem.spawnPrefab.GetComponentInChildren(); + scarletItemComp.bulbDark = refItemComp.bulbDark; + scarletItemComp.bulbLight = refItemComp.bulbLight; + scarletItemComp.flashlightClips = refItemComp.flashlightClips; + scarletItemComp.flashlightFlicker = refItemComp.flashlightFlicker; + scarletItemComp.outOfBatteriesClip = refItemComp.outOfBatteriesClip; + scarletItemComp.itemProperties = item; + + var scarletAudioComp = scarletItemComp.flashlightAudio; + var refAudioComp = refItemComp.flashlightAudio; + scarletAudioComp.SetCustomCurve(AudioSourceCurveType.CustomRolloff, refAudioComp.GetCustomCurve(AudioSourceCurveType.CustomRolloff)); + + var scarletLightComp = scarletItemComp.flashlightBulb; + var refLightComp = refItemComp.flashlightBulb; + scarletLightComp.cookie = refLightComp.cookie; + + void CopyAndPasteMeshFilterAndRenderer(MeshRenderer meshScarlet, MeshRenderer meshReference){ + meshScarlet.sharedMaterials = meshReference.sharedMaterials; + var scarletMeshComp = meshScarlet.GetComponent(); + var refMeshComp = meshReference.GetComponent(); + scarletMeshComp.sharedMesh = refMeshComp.sharedMesh; + } + + CopyAndPasteMeshFilterAndRenderer(scarletItemComp.flashlightMesh, refItemComp.flashlightMesh); + var scarletTie = scarletItemComp.flashlightMesh.transform.Find("Tie"); + var refTie = refItemComp.flashlightMesh.transform.Find("Tie"); + if (scarletTie && refTie) { + var scarletTieMesh = scarletTie.GetComponent(); + var refTieMesh = refTie.GetComponent(); + if (scarletTieMesh && refTieMesh) { + Plugin.logger.LogInfo("Fixed tie of bb flashlight"); + CopyAndPasteMeshFilterAndRenderer(scarletTieMesh, refTieMesh); + } + } + + LethalLib.Modules.Items.RegisterItem(item); + } + } + + if (Assets.flashlight.item == null) { + UpdateFlashlight(Assets.flashlight, "proflashlight", 2); + } + + if (Assets.flashlightBB.item == null) { + UpdateFlashlight(Assets.flashlightBB, "flashlight", 3); + } + + } catch (Exception e) { + Plugin.logger.LogError("Error when reading base game's item list. Pretty big error I would say"); + Plugin.logger.LogError(e); + return; + } + + } + + public static void FixDungeonPrefabValues(RoundManager manager){ + + var networkmanager = GameObject.FindObjectOfType(); + var prefabs = networkmanager.NetworkConfig.Prefabs.Prefabs; + var prefabFixList = Assets.networkObjectList.toFixGameObjects; + + //foreach(var p in prefabs){ + // var hasInteract = p.Prefab.GetComponentInChildren(); + // var interStr = hasInteract ? "YES" : ""; + // Plugin.logger.LogInfo($"{p.Prefab.name}: {interStr}"); + //} + + var steeldoor = prefabs.FirstOrDefault(p => p.Prefab.name.ToLowerInvariant() == "fancydoormapmodel"); + if (GameReadNullCheck(steeldoor, "FancyDoorMapModel", "SDM doors will have missing icons and sounds")){ + var interact = steeldoor.Prefab.GetComponentInChildren(); + var animateTrigger = steeldoor.Prefab.GetComponentInChildren(); + Assets.hoverIcon = interact.hoverIcon; + + var boolFalse = animateTrigger.boolFalseAudios; + var boolTrue = animateTrigger.boolTrueAudios; + var secondary = animateTrigger.secondaryAudios; + + FixDoorway(prefabFixList[0]); + FixDoorway(prefabFixList[1]); + + void FixDoorway(GameObject g){ + var animate = g.GetComponentInChildren(); + + animate.boolFalseAudios = boolFalse; + animate.boolTrueAudios = boolTrue; + animate.secondaryAudios = secondary; + } + } + + var mine = prefabs.FirstOrDefault(p => p.Prefab.name.ToLowerInvariant() == "landmine"); + var turret = prefabs.FirstOrDefault(p => p.Prefab.name.ToLowerInvariant() == "turretcontainer"); + var spiketrap = prefabs.FirstOrDefault(p => p.Prefab.name.ToLowerInvariant() == "spikerooftraphazard"); + + if (GameReadNullCheck(mine, "LandMine", "Map Object Hazards will not spawn") && GameReadNullCheck(turret, "TurretContainer", "Map Object Hazards will not spawn") && GameReadNullCheck(spiketrap, "SpikeRoofTrapHazard", "Map Object Hazards will not spawn")){ + Assets.dungeonMapHazardFound = true; + Assets.dungeonMinesMapHazard = mine.Prefab; + Assets.dungeonTurretMapHazard = turret.Prefab; + Assets.dungeonSpikeTrapMapHazard = spiketrap.Prefab; + + } + + try { + var flow = manager.dungeonFlowTypes.Select(d => d.dungeonFlow).FirstOrDefault(d => d.name == "Level1Flow"); + var tiles = flow.Nodes[1].TileSets[0].TileWeights.Weights; + var doorways = tiles.SelectMany(t => t.Value.GetComponentsInChildren()); + var blockers = doorways.SelectMany(d => d.BlockerPrefabWeights).Select(e => e.GameObject); + var globalprops = blockers.SelectMany(b => b.GetComponentsInChildren()); + var fireProp = globalprops.FirstOrDefault(f => f.PropGroupID == 1231); + prefabFixList[4].GetComponent().Setup(fireProp.gameObject); + + } catch (Exception e){ + Plugin.logger.LogInfo("Failed to setup vanilla fire exit. The 'Use SDM Fire Exits' config will be ignored"); + Plugin.logger.LogError(e.ToString()); + } + + + } + + + private static IEnumerator UpdateNetworkConfig(RoundManager roundManager){ + while(PluginConfig.Synced == false){ + yield return null; + } + + /* + var list = ExtendedDungeonMapLoad.GetCustomMoons(PluginConfig.Instance.customMapsValue, PluginConfig.Instance.customMapDefaultWeightValue); + ExtendedDungeonMapLoad.ClearLastCustomMoonEntryList(); + + // overriding with dis + if (list.Count > 0){ + Plugin.logger.LogInfo("Overring default snow moons. Loading with custom moon list"); + ExtendedDungeonMapLoad.AddToExtendedDungeonFlow(list); + } + + else { + Plugin.logger.LogInfo("Loading SDM to default snow moons"); + + Assets.dineEntry.SetRarity(PluginConfig.Instance.dungeonSnowWeightValue); + Assets.rendEntry.SetRarity(PluginConfig.Instance.dungeonSnowWeightValue); + Assets.titanEntry.SetRarity(PluginConfig.Instance.dungeonTitanWeightValue); + + var dun = Assets.dungeonExtended; + Assets.dineEntry.Add(dun); + Assets.rendEntry.Add(dun); + Assets.titanEntry.Add(dun); + } + */ + + Lights.ScarletLightCleanup.weights = new float[] { + PluginConfig.Instance.lightsSpawnZeroWeightValue, + PluginConfig.Instance.lightsSpawnOneWeightValue, + PluginConfig.Instance.lightsSpawnTwoWeightValue + //PluginConfig.Instance.lightsSpawnThreeWeightValue + }; + + /* + foreach(var item in Assets.items){ + item.UpdateStringWithRarity(); + } + // hack + foreach(var level in PatchedContent.ExtendedLevels) { + Plugin.logger.LogInfo($"{level.name}: {level.AuthorName}"); + var hackCall = typeof(ItemManager).GetMethod("InjectCustomItemsIntoLevelViaDynamicRarity", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + hackCall.Invoke(null, new object[] { level, true } ); + } + */ + + //Assets.dungeonExtended.DynamicDungeonSizeMinMax = new Vector2(PluginConfig.Instance.dunGenMultiplierValue.min, PluginConfig.Instance.dunGenMultiplierValue.max); + Assets.dungeon.Length = PluginConfig.Instance.mainPathLengthValue.GetDungenIntRange(); + + DungeonFlow.GlobalPropSettings GetGlobalPropSetting(int id) { + foreach(var p in Assets.dungeon.GlobalProps){ + if (p.ID == id ) return p; + } + return null; + } + + GetGlobalPropSetting(254).Count = new DunGen.IntRange( + PluginConfig.Instance.paintingCountValue, + PluginConfig.Instance.paintingCountValue + ); + + Plugin.logger.LogInfo("Set networked config values"); + } + + public static void UpdateTileWeightDebug(string tile, float multiplier){ + var tilesets = Assets.dungeon.GetUsedTileSets(); + foreach(var s in tilesets){ + foreach(var t in s.TileWeights.Weights){ + var n = t.Value.name; + if (n.Contains(tile)) { + Plugin.logger.LogInfo($"{t.Value.name} weight being multiplied by {multiplier}"); + t.MainPathWeight *= multiplier; + t.BranchPathWeight *= multiplier; + } + } + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Items/FlandreCrystal.cs b/ScarletMansion/ScarletMansion/GamePatch/Items/FlandreCrystal.cs new file mode 100644 index 0000000..c0d040e --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Items/FlandreCrystal.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using GameNetcodeStuff; +using ScarletMansion.GamePatch.Managers; + +namespace ScarletMansion.GamePatch.Items { + public class FlandreCrystal : GrabbableObject { + + public static Color[] colorVariants = new Color[] { + new Color(0f, 0f, 1f), + new Color(0f, 1f, 1f), + new Color(0f, 1f, 0f), + new Color(1f, 0f, 1f), + new Color(1f, 0.647f, 0f), + new Color(0.502f, 0f, 0.502f), + new Color(1f, 0f, 0f), + new Color(1f, 1f, 0f) + }; + + [Header("Target Transformation")] + public Item targetTransformation; + + [Header("Colors")] + public int colorIndex; + public Light light; + public bool hasUsedCrystal; + + private PlayerControllerB previousPlayerHeldBy; + private Coroutine flashCoroutine; + private float intensity; + private float range; + + public override void Start() { + base.Start(); + StartCoroutine(WaitForScrapValue()); + intensity = light.intensity; + range = light.range; + } + + public IEnumerator WaitForScrapValue(){ + while (scrapValue == 0) yield return null; + + colorIndex = scrapValue % colorVariants.Length; + + var color = colorVariants[colorIndex]; + mainObjectRenderer.material.color = color; + light.color = color; + } + + public override void EquipItem() { + base.EquipItem(); + previousPlayerHeldBy = playerHeldBy; + light.enabled = true; + } + + public override void ItemActivate(bool used, bool buttonDown = true) { + base.ItemActivate(used, buttonDown); + if (playerHeldBy == null) return; + if (hasUsedCrystal) return; + + var flashLightResult = FindFlashlightInInventory(); + if (flashLightResult.index != -1) { + var lastPlayer = playerHeldBy; + hasUsedCrystal = true; + lastPlayer.activatingItem = true; + + // fail safe, but it feels kinda shit + var result = ScarletNetworkManagerUtility.CreateFlashlight(playerHeldBy, flashLightResult.item, this); + if (result == false){ + hasUsedCrystal = false; + lastPlayer.activatingItem = false; + } + } else { + Flash(); + FlashServerRpc(); + } + } + + public override void PocketItem() { + base.PocketItem(); + playerHeldBy.activatingItem = false; + light.enabled = false; + } + + public override void DiscardItem() { + base.DiscardItem(); + light.enabled = true; + } + + private (FlashlightItem item, int index) FindFlashlightInInventory(){ + var items = playerHeldBy.ItemSlots; + for(var i = 0; i < items.Length; ++i){ + var item = items[i] as FlashlightItem; + if (item != null && Assets.GetFlashlight(item.itemProperties) != null) { + Plugin.logger.LogInfo($"Flashlight slot {i}"); + return (item, i); + } + } + return (null, -1); + } + + [ServerRpc(RequireOwnership = false)] + public void FlashServerRpc(){ + FlashClientRpc(); + } + + [ClientRpc] + public void FlashClientRpc(){ + if (playerHeldBy && playerHeldBy.IsOwner) return; + Flash(); + } + + private void Flash(){ + if (flashCoroutine != null) StopCoroutine(flashCoroutine); + flashCoroutine = StartCoroutine(FlashEnumerator()); + } + + public IEnumerator FlashEnumerator(){ + light.intensity = intensity; + light.range = range; + + var t = 0f; + while(t < 0.3f) { + yield return null; + t += Time.deltaTime; + + var factor = t / 0.3f * (float)Math.PI; + factor = Mathf.Sin(factor) * 1f + 1f; + light.intensity = intensity * factor; + light.range = range * factor; + } + + light.intensity = intensity; + light.range = range; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Items/IScarletItem.cs b/ScarletMansion/ScarletMansion/GamePatch/Items/IScarletItem.cs new file mode 100644 index 0000000..0790234 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Items/IScarletItem.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ScarletMansion.GamePatch.Items { + public interface IScarletItem { + + void UpdateSpecialProperties(int value); + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Items/ScarletFlashlight.cs b/ScarletMansion/ScarletMansion/GamePatch/Items/ScarletFlashlight.cs new file mode 100644 index 0000000..363f48a --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Items/ScarletFlashlight.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.GamePatch.Items { + + // While I would prefer to use my own script, + // It would cause too many problems I think + // Due to mods that reserve flashlights and such + public class ScarletFlashlight : FlashlightItem, IScarletItem { + + [Header("Colors")] + public MeshRenderer[] crystalRenderers; + public int colorIndex; + private Color lightColor; + private int fallbackFlashlightTypeID; + + public override void Start() { + base.Start(); + + fallbackFlashlightTypeID = flashlightTypeID; + var flashlight = Assets.GetFlashlight(itemProperties); + if (flashlight == null || flashlight.scarletHelmetIndex == -1){ + StartCoroutine(WaitForRealFlashlightID()); + return; + } + + flashlightTypeID = flashlight.scarletHelmetIndex; + } + + public override int GetItemDataToSave() { + base.GetItemDataToSave(); + return colorIndex; + } + + public override void LoadItemSaveData(int saveData) { + base.LoadItemSaveData(saveData); + UpdateSpecialProperties(saveData); + } + + public override void EquipItem() { + // test to prevent future BS + if (playerHeldBy && flashlightTypeID >= playerHeldBy.allHelmetLights.Length){ + Plugin.logger.LogWarning($"FlashlightTypeID {flashlightTypeID} is invalid somehow. Reverting to ID {fallbackFlashlightTypeID}"); + flashlightTypeID = fallbackFlashlightTypeID; + } + + base.EquipItem(); + + // once we fall back, we can no longer call this + if (flashlightTypeID != fallbackFlashlightTypeID) + UpdateHelmetColor(); + } + + public void UpdateHelmetColor(){ + var light = playerHeldBy.allHelmetLights[flashlightTypeID]; + light.color = lightColor; + } + + public void UpdateSpecialProperties(int colorIndex) { + this.colorIndex = colorIndex; + + bulbLight = new Material(bulbLight); + bulbDark = new Material(bulbDark); + + var color = FlandreCrystal.colorVariants[colorIndex]; + lightColor = flashlightBulb.color = Color.Lerp(flashlightBulb.color, color, 0.5f); + flashlightBulbGlow.color = Color.Lerp(flashlightBulbGlow.color, color, 0.5f); + bulbLight.color = Color.Lerp(bulbLight.color, color, 0.75f); + bulbDark.color = Color.Lerp(bulbDark.color, color, 0.75f); + + SwitchFlashlight(isBeingUsed); + + foreach(var r in crystalRenderers){ + r.material.color = color; + } + } + + public IEnumerator WaitForRealFlashlightID(){ + Plugin.logger.LogInfo("Waiting for real helmet index for scarlet flashlight"); + var t = Time.realtimeSinceStartup + 8f; + while(true) { + yield return null; + + var flashlight = Assets.GetFlashlight(itemProperties); + if (flashlight != null && flashlight.scarletHelmetIndex != -1) { + Plugin.logger.LogInfo("Got real helmet index for scarlet flashlight"); + flashlightTypeID = flashlight.scarletHelmetIndex; + yield break; + } + + if (Time.realtimeSinceStartup > t) { + Plugin.logger.LogWarning("Failed to get helmet index for scarlet flashlight"); + yield break; + } + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Items/ScarletPainting.cs b/ScarletMansion/ScarletMansion/GamePatch/Items/ScarletPainting.cs new file mode 100644 index 0000000..f2a76c3 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Items/ScarletPainting.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using ScarletMansion.GamePatch.Components; +using ScarletMansion.GamePatch.Managers; + +namespace ScarletMansion.GamePatch.Items { + public class ScarletPainting : GrabbableObject { + + public ScarletBedroom bedroom; + public bool isAttached = true; + + [Header("Variations")] + public int variation; + public Material[] variationMaterials; + + /* + + public static readonly string[] raycastLayers = new string[] { "Default", "Room", "InteractableObject", "Colliders", "MiscLevelGeometry", "Terrain", "PlacementBlocker", "Railing", "DecalStickableSurface" }; + public static readonly LayerMask raycastLayerMask = LayerMask.GetMask(raycastLayers); + public static readonly string[] placementWhiteList = new string[] { "ShipInside" }; + + [Header("Placement References")] + public GameObject ghostGameObject; + public BoxCollider ghostCollider; + private bool validPlacement; + */ + + public override int GetItemDataToSave() { + base.GetItemDataToSave(); + return variation; + } + + public override void LoadItemSaveData(int saveData) { + base.LoadItemSaveData(saveData); + variation = saveData; + + mainObjectRenderer.material = variationMaterials[variation]; + transform.rotation = Quaternion.Euler(itemProperties.restingRotation); // why zeekers + } + + public override void Start() { + base.Start(); + + if (isAttached){ + bedroom = AngerManager.Instance.GetBedroomWithPainting(transform.position); + scrapValue = PluginConfig.Instance.paintingValueValue; + } + } + + public override void EquipItem() { + Plugin.logger.LogInfo($"Scarlet painting was grabbed. Is owner: {IsOwner}"); + if (isAttached) { + if (bedroom) bedroom.Anger(playerHeldBy.transform); + isAttached = false; + } + + base.EquipItem(); + } + + /* + public override void ItemActivate(bool used, bool buttonDown = true) { + base.ItemActivate(used, buttonDown); + if (playerHeldBy == null && validPlacement){ + playerHeldBy.DiscardHeldObject(true, null, ghostCollider.transform.position); + } + } + + + public override void LateUpdate() { + base.LateUpdate(); + + ghostGameObject.SetActive(false); + + if (playerHeldBy != null && isHeld){ + var playerCamera = playerHeldBy.gameplayCamera.transform; + var ray = new Ray(playerCamera.position, playerCamera.forward); + + // check for valid wall + if (Physics.Raycast(ray, out var hit, 2f, raycastLayerMask, QueryTriggerInteraction.Ignore)){ + var gobj = hit.transform.gameObject; + if (!placementWhiteList.Contains(gobj.name)) return; + + ghostGameObject.transform.position = hit.transform.position; + ghostGameObject.transform.rotation = Quaternion.LookRotation(hit.normal); + + // check for collisions + if (Physics.CheckBox(ghostCollider.center, ghostCollider.size * 0.5f, ghostCollider.transform.rotation, raycastLayerMask, QueryTriggerInteraction.Ignore)){ + return; + } + + ghostGameObject.SetActive(true); + validPlacement = true; + } + } + + } + */ + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/JesterAIPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/JesterAIPatch.cs new file mode 100644 index 0000000..45e47f0 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/JesterAIPatch.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; + +namespace ScarletMansion.GamePatch { + + public class JesterAIPatch { + + public static bool active; + + [HarmonyPatch(typeof(JesterAI), "Start")] + [HarmonyPostfix] + public static void StartPatch(ref JesterAI __instance){ + if (DunGenPatch.Patch.active && active && __instance.IsOwner) { + Plugin.logger.LogInfo("Activing kill order for Jester"); + __instance.SwitchToBehaviourState(1); + active = false; + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/LoadAssetsIntoLevelPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/LoadAssetsIntoLevelPatch.cs new file mode 100644 index 0000000..c73cec8 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/LoadAssetsIntoLevelPatch.cs @@ -0,0 +1,167 @@ +using HarmonyLib; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using System.Reflection.Emit; +using System.Reflection; + +namespace ScarletMansion.GamePatch { + + public class LoadAssetsIntoLevelPatch { + + // alright new strat + // at the start, we create an alternate enemy (and item) list + // we highjack the enemy (and item) list variable where ever it is called (base game or advanced company) + // if the highjacked list matches the one we created, which it should (and we grab from advanced company too if needed) + // then we replace + + public static InjectionDictionary enemiesInjection = new InjectionDictionary( + "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 SyncVentSpawnTimeClientRpcPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "SyncVentSpawnTimeClientRpc", 1); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "AssignRandomEnemyToVent")] + public static IEnumerable AssignRandomEnemyToVentPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "AssignRandomEnemyToVent", 10); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "EnemyCannotBeSpawned")] + public static IEnumerable EnemyCannotBeSpawnedPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "EnemyCannotBeSpawned", 4); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "ResetEnemyTypesSpawnedCounts")] + public static IEnumerable ResetEnemyTypesSpawnedCountsPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "ResetEnemyTypesSpawnedCounts", 4); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "SetChallengeFileRandomModifiers")] + public static IEnumerable SetChallengeFileRandomModifiersPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "SetChallengeFileRandomModifiersEnemy", 3); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "SpawnEnemyGameObject")] + public static IEnumerable SpawnEnemyGameObjectPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, enemiesInjection, "SpawnEnemyGameObject", 3); + } + + + + [HarmonyTranspiler] + [HarmonyPatch(typeof(GiftBoxItem), "Start")] + public static IEnumerable GiftBoxItemStartPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "GiftBoxItem", 5); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "SetChallengeFileRandomModifiers")] + public static IEnumerable SetChallengeFileRandomModifiersItemPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "SetChallengeFileRandomModifiersItem", 1); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")] + public static IEnumerable SpawnScrapInLevelPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, itemsInjection, "SpawnScrapInLevel", 4); + } + + + + public static List lastEnemiesRarity; + public static List currentEnemiesRarity; + + public static List lastItemsRarity; + public static List currentItemsRarity; + + public static void ModifyLevel(SelectableLevel level){ + + lastEnemiesRarity = level.Enemies; + lastItemsRarity = level.spawnableScrap; + + currentEnemiesRarity = lastEnemiesRarity.ToList(); + currentItemsRarity = lastItemsRarity.ToList(); + + if (Assets.knight != null) { + var baseWeight = PluginConfig.Instance.knightWeightBaseValue; + var target = currentEnemiesRarity + .Where(c => c.enemyType.name.ToLowerInvariant() == "springman") + .FirstOrDefault(); + + if (target == null){ + const int noCoilheaBaseKnightRarity = 10; + Plugin.logger.LogInfo($"No spring enemy in level, using default rarity of {noCoilheaBaseKnightRarity} for knight"); + + var knight = Assets.knight.GetItemEntry(noCoilheaBaseKnightRarity + baseWeight); + + Plugin.logger.LogInfo($"Adding enemy Knight with weight {knight.rarity}"); + currentEnemiesRarity.Add(knight); + + } else { + currentEnemiesRarity.Remove(target); + var percentage = PluginConfig.Instance.knightWeightStealPercentageValue; + var knightRarity = Mathf.RoundToInt(target.rarity * percentage); + + var spring = new SpawnableEnemyWithRarity(); + spring.enemyType = target.enemyType; + spring.rarity = target.rarity - knightRarity; + + var knight = Assets.knight.GetItemEntry(knightRarity + baseWeight); + + Plugin.logger.LogInfo($"Adding enemy Knight with weight {knight.rarity}"); + Plugin.logger.LogInfo($"Setting enemy Coil-head with weight {spring.rarity}"); + currentEnemiesRarity.Add(spring); + currentEnemiesRarity.Add(knight); + } + } else { + Plugin.logger.LogError($"Failed to load custom enemy as their reference is missing"); + } + + + foreach(var i in Assets.scrapItems){ + var entry = i.GetItemRarity(); + + if (entry.rarity > 0) { + Plugin.logger.LogInfo($"Adding item {entry.spawnableItem.itemName} with weight {entry.rarity}"); + currentItemsRarity.Add(entry); + } + } + + + Plugin.logger.LogInfo($"Loaded custom enemies and items for {level.sceneName}"); + Components.ScarletBedroom.CreateRandomEnemyList(currentEnemiesRarity); + } + + public static List GetEnemies(List target){ + if (DunGenPatch.Patch.active && target == lastEnemiesRarity) return currentEnemiesRarity; + return target; + } + + + public static List GetItems(List target){ + if (DunGenPatch.Patch.active && target == lastItemsRarity) return currentItemsRarity; + return target; + } + + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Managers/AngerManager.cs b/ScarletMansion/ScarletMansion/GamePatch/Managers/AngerManager.cs new file mode 100644 index 0000000..49d84c2 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Managers/AngerManager.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; +using HarmonyLib; +using Unity.Netcode; +using ScarletMansion.GamePatch.Components; +using DunGen; +using Key = UnityEngine.InputSystem.Key; +using ScarletMansion.Lights; +using UnityEngine.Rendering; +using UnityEngine.Rendering.HighDefinition; + +namespace ScarletMansion.GamePatch.Managers { + public class AngerManager : MonoBehaviour, IDungeonCompleteReceiver { + + public static AngerManager Instance { get; private set; } + + //public Dictionary angeredEnemies; + public int level; + public List bedrooms; + public List doors; + public List lights; + + //public KeyboardFloatDebug metal = new KeyboardFloatDebug("metal", 0f, 0f, 1f, 0.1f, Key.Numpad4, Key.Numpad7); + //public KeyboardFloatDebug smooth = new KeyboardFloatDebug("smooth", 0f, 0f, 1f, 0.1f, Key.Numpad6, Key.Numpad9); + + void Awake(){ + Instance = this; + bedrooms = new List(); + doors = new List(); + lights = new List(); + } + /* + void Update(){ + var enabled = 0; + var active = 0; + var total = 0; + foreach(var l in lights){ + if (l.enabled) enabled++; + if (l.gameObject.activeInHierarchy) active++; + total++; + } + Plugin.logger.LogInfo($"{enabled}|{active}/{total}"); + } + */ + + /* + void Update(){ + var deltaIntensity = 0; + if (Utility.IfKeyPress(Key.Numpad7)) { + deltaIntensity = 1; + } else if (Utility.IfKeyPress(Key.Numpad4)) { + deltaIntensity = -1; + } + + var deltaRange = 0; + if (Utility.IfKeyPress(Key.Numpad9)) { + deltaRange = 1; + } else if (Utility.IfKeyPress(Key.Numpad6)) { + deltaRange = -1; + } + + if (deltaIntensity != 0 || deltaRange != 0){ + var tile = Utility.GetClosestTileToPlayer(); + var lights = tile.GetComponentsInChildren(); + var localPlayer = StartOfRound.Instance.localPlayerController.transform.position; + var closestLight = lights.OrderBy(t => Vector3.SqrMagnitude(t.transform.position - localPlayer)).FirstOrDefault(); + if (closestLight != null) { + closestLight.light.intensity += deltaIntensity * 0.5f; + closestLight.light.range += deltaRange * 0.25f; + + Plugin.logger.LogInfo($"{closestLight.light.intensity}, {closestLight.light.range}"); + } + } + } + */ + public void Anger(int angerAmount = 1){ + Plugin.logger.LogInfo($"Raising anger from {level} to {level + angerAmount}"); + var manager = RoundManager.Instance; + manager.minEnemiesToSpawn += angerAmount; + level += angerAmount; + } + + public void AddBedroom(ScarletBedroom b){ + bedrooms.Add(b); + } + + public void AddDoor(ScarletDoor d){ + // I want to get only doors that are revelant + foreach(var b in bedrooms){ + var dist = Vector3.SqrMagnitude(d.transform.position - b.transform.position); + if (dist < 16f * 16f){ + doors.Add(d); + return; + } + } + } + + public ScarletBedroom GetBedroomWithPainting(Vector3 basePosition){ + ScarletBedroom target = null; + var dist = 1f; + + foreach(var b in bedrooms){ + var newdist = Vector3.SqrMagnitude(basePosition - b.paintingSpawnTransform.position); + if (newdist < dist){ + target = b; + dist = newdist; + } + } + + if (target == null){ + Plugin.logger.LogError($"There is no close bedroom painting spawn at {basePosition}"); + return null; + } + + //Plugin.logger.LogInfo($"Closest bedroom painting spawn {dist} away"); + return target; + } + + public ScarletDoor GetScarletDoor(Vector3 basePosition){ + ScarletDoor target = null; + var dist = 1f; + + foreach(var b in doors){ + var newdist = Vector3.SqrMagnitude(basePosition - b.transform.position); + if (newdist < dist){ + target = b; + dist = newdist; + } + } + + if (target == null){ + Plugin.logger.LogError($"There is no close door spawn at {basePosition}"); + return null; + } + + //Plugin.logger.LogInfo($"Closest door spawn {dist} away"); + return target; + } + + public void AddLight(ScarletLight light){ + lights.Add(light); + } + + public ItemReference[] CreateAngerLoot(int count, System.Random sysRandom){ + var roundManager = RoundManager.Instance; + if (!roundManager.IsServer) return null; + + //Plugin.logger.LogInfo($"Creating {count} bonus items"); + if (count == 0) return new ItemReference[0]; + + var list = new List(); + var scrap = Utility.GetDungeonItems(); + foreach(var e in scrap){ + list.Add(e.rarity); + } + + var bonusItems = new ItemReference[count]; + for(var i = 0; i < bonusItems.Length; ++i){ + var index = roundManager.GetRandomWeightedIndexList(list, sysRandom); + var item = scrap[index].spawnableItem; + var cost = (int)(sysRandom.Next(item.minValue + 25, item.maxValue + 35) * roundManager.scrapValueMultiplier); + + var itemRef = new ItemReference(item, index, cost); + bonusItems[i] = itemRef; + + //Plugin.logger.LogInfo($"Created bonus item of {itemRef}"); + } + + return bonusItems; + } + + public void SpawnAngerLoot(ItemReference[] loot, Transform[] spawnTransforms){ + var roundManager = RoundManager.Instance; + + if (loot == null) { + Plugin.logger.LogError($"Anger loot is empty. SPOOOKY"); + return; + } + + for(var i = 0; i < loot.Length; ++i){ + var item = loot[i]; + var pos = spawnTransforms[i].position; + ScarletNetworkParams callParams = new ScarletNetworkParams() { scrapValue = item.value }; + ScarletNetworkManager.Instance.CreateScrapItemServerRpc(item.itemId, pos, callParams); + } + } + + public void OnDungeonComplete(Dungeon dungeon) { + Anger(PluginConfig.Instance.minIndoorEnemySpawnCountValue); + } + + public void TriggerAngerLightBrief(float duration){ + foreach(var s in lights){ + s.BeginAngry(duration, false); + } + } + + public void TriggerAngerLightForever(){ + foreach(var s in lights){ + s.BeginAngry(0f, true); + } + } + + /* + public void Anger(){ + level += 1; + var manager = RoundManager.Instance; + + var enemies = manager.SpawnedEnemies; + foreach(var e in enemies){ + // only inside + if (e.enemyType.isDaytimeEnemy || e.isOutside) continue; + + if (!angeredEnemies.ContainsKey(e)) + angeredEnemies.Add(e, 0); + } + + foreach(var e in angeredEnemies.Keys){ + AngerEnemy(e, level - angeredEnemies[e]); + angeredEnemies[e] = level; + } + } + + [ClientRpc] + public void AngerEnemyClientRpc(NetworkBehaviourReference enemyRef, int levelOffset){ + + if (enemyRef.TryGet(out var enemy)){ + if (levelOffset < 1) return; + + var type = enemy.GetType(); + if (type == typeof(BlobAI)){ + Plugin.logger.LogInfo("Can't anger blob yet"); + } + + else { + Plugin.logger.LogInfo($"Angering {type} is not yet supported"); + } + + } + + } + + public void Remove(EnemyAI enemy){ + angeredEnemies.Remove(enemy); + } + + */ + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Managers/DoorwayManager.cs b/ScarletMansion/ScarletMansion/GamePatch/Managers/DoorwayManager.cs new file mode 100644 index 0000000..e2b65c8 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Managers/DoorwayManager.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; +using ScarletMansion.DunGenPatch.Doorways; + +namespace ScarletMansion.GamePatch.Managers { + public class DoorwayManager : MonoBehaviour { + + public static DoorwayManager Instance { get; private set; } + public static ActionList onMainEntranceTeleportSpawnedEvent = new ActionList("onMainEntranceTeleportSpawned"); + + public List doorwayCleanup; + + public void Awake(){ + Instance = this; + doorwayCleanup = new List(); + } + + public void AddDoorwayCleanup(DoorwayCleanup dw){ + doorwayCleanup.Add(dw); + } + + public static void onMainEntranceTeleportSpawnedFunction(){ + if (Instance && DunGenPatch.Patch.active) { + var doorwayCleanups = Instance.doorwayCleanup; + foreach(var d in doorwayCleanups){ + d.Cleanup(); + } + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Managers/KnightSpawnManager.cs b/ScarletMansion/ScarletMansion/GamePatch/Managers/KnightSpawnManager.cs new file mode 100644 index 0000000..3097639 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Managers/KnightSpawnManager.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + + public class KnightSpawnManager : MonoBehaviour, IDungeonCompleteReceiver { + + public static KnightSpawnManager Instance { get; private set; } + + public List spawnPoints; + public List unusedSpawnPoints; + + public int lastKnightSeenPlayer = -1; + public bool disableNextKnightSpecialSpawn; + + void Awake(){ + Instance = this; + } + + public void OnDungeonComplete(Dungeon dungeon) { + // IDK KNOW IF I CAN TRUST THIS TO BE THE SAME FOR ALL CLIENTS + // but probably + var points = dungeon.GetComponentsInChildren(); + spawnPoints = points.ToList(); + for(var i = 0; i < spawnPoints.Count; ++i){ + spawnPoints[i].index = i; + } + + unusedSpawnPoints = points.ToList(); + Plugin.logger.LogInfo($"Found {spawnPoints.Count} spawn points for the knight"); + } + + public int GetSpawnPointIndex(){ + if (disableNextKnightSpecialSpawn) { + disableNextKnightSpecialSpawn = true; + return -1; + } + + if (unusedSpawnPoints.Count == 0) return -1; + + // cause it would be funny + if (lastKnightSeenPlayer >= 0){ + var tempitem = spawnPoints[lastKnightSeenPlayer]; + if (tempitem.gameObject.activeInHierarchy){ + Plugin.logger.LogInfo($"Using the last knight {tempitem.index} that saw a player"); + unusedSpawnPoints.Remove(tempitem); + lastKnightSeenPlayer = -1; + return tempitem.index; + } + + } + + var index = UnityEngine.Random.Range(0, unusedSpawnPoints.Count); + var item = unusedSpawnPoints[index]; + unusedSpawnPoints.RemoveAt(index); + + return item.index; + } + + public Transform GetSpawnPointTransform(int index){ + return spawnPoints[index].transform; + } + + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletLightingManager.cs b/ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletLightingManager.cs new file mode 100644 index 0000000..bebe096 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletLightingManager.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +/* + +namespace ScarletMansion { + public static class ScarletLightingManager { + + public static ScarletLighting[] lights; + + public static void Init(){ + lights = UnityEngine.Object.FindObjectsOfType(); + } + + public static void Clean(){ + lights = null; + } + + public static void UpdateLights(float modifier){ + if (lights == null) return; + + try { + foreach(var l in lights){ + l.UpdateLight(modifier); + } + } catch (Exception e){ + Plugin.logger.LogError("UpdateLights found a weird error, we just aborting"); + Plugin.logger.LogError(e.ToString()); + lights = null; + } + } + + public static void DisableLights(){ + if (lights == null) return; + + try { + foreach(var l in lights){ + l.DisableLights(); + } + } catch (Exception e){ + Plugin.logger.LogError("DisableLights found a weird error, we just aborting"); + Plugin.logger.LogError(e.ToString()); + lights = null; + } + } + + } +} + +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletNetworkManager.cs b/ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletNetworkManager.cs new file mode 100644 index 0000000..2a8ff3b --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Managers/ScarletNetworkManager.cs @@ -0,0 +1,409 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Unity.Netcode; +using GameNetcodeStuff; +using ScarletMansion.GamePatch.Items; +using ScarletMansion.GamePatch; + +namespace ScarletMansion { + + public struct ScarletNetworkParams : INetworkSerializeByMemcpy { + public int scrapValue; + public int specialValue; + } + + public class ItemReference { + public Item item; + public int itemId; + public int value; + + public ItemReference(Item item, int itemId, int value){ + this.item = item; + this.itemId = itemId; + this.value = value; + } + + public override string ToString() { + var itemString = item ? item.name : "NULL"; + return $"{itemString}:{itemId} ({value})"; + } + } + + public class EnemyReference { + public EnemyType enemy; + public int index; + + public EnemyReference(EnemyType enemy, int index){ + this.enemy = enemy; + this.index = index; + } + + public override string ToString() { + var enemyString = enemy ? enemy.name : "NULL"; + return $"{enemyString}:{index}"; + } + } + + public class EnemyReferenceSpawnLogic : EnemyReference { + public enum SpawnLogic { None, Special }; + public SpawnLogic logic; + + public EnemyReferenceSpawnLogic(EnemyType enemy, int index, SpawnLogic logic) : base(enemy, index) { + this.logic = logic; + } + + public void ApplySpawnLogic(){ + if (logic == SpawnLogic.None) return; + + var enemyName = enemy.name.ToLowerInvariant(); + if (enemyName == "knight") + KnightSpawnManager.Instance.disableNextKnightSpecialSpawn = true; + else if (enemyName == "jester") + JesterAIPatch.active = true; + } + + public override string ToString() { + var b = base.ToString(); + return $"{b} [{logic.ToString()}]"; + } + } + + public class ScarletNetworkManager : NetworkBehaviour { + + public static ScarletNetworkManager Instance { get; private set; } + + void Awake(){ + Instance = this; + } + + [ServerRpc(RequireOwnership = false)] + public void DestroyPlayerItemInSlotServerRpc(NetworkBehaviourReference playerRef, int itemSlot, ServerRpcParams callParams = default(ServerRpcParams)){ + if (playerRef.TryGet(out var player)){ + Plugin.logger.LogInfo($"P{player.OwnerClientId}, S{callParams.Receive.SenderClientId}"); + + DestroyPlayerItemInSlotClientRpc(playerRef, itemSlot); + return; + } + + Plugin.logger.LogError($"Error trying to get player script (SERVERRPC)"); + } + + [ClientRpc] + public void DestroyPlayerItemInSlotClientRpc(NetworkBehaviourReference playerRef, int itemSlot){ + if (playerRef.TryGet(out var player) && !player.IsOwner){ + player.DestroyPlayerItemInSlot_SDM(itemSlot); + return; + } + + if (player == null) + Plugin.logger.LogError($"Error trying to get player script (CLIENTRPC)"); + } + + [ServerRpc(RequireOwnership = false)] + public void CreateItemServerRpc(int itemId, Vector3 position, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + CreateItem(itemId, false, position, null, callParams); + } + + [ServerRpc(RequireOwnership = false)] + public void CreateItemServerRpc(int itemId, Vector3 position, NetworkBehaviourReference playerRef, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + playerRef.TryGet(out var player); + CreateItem(itemId, false, position, player, callParams); + } + + [ServerRpc(RequireOwnership = false)] + public void CreateScrapItemServerRpc(int itemId, Vector3 position, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + CreateItem(itemId, true, position, null, callParams); + } + + [ServerRpc(RequireOwnership = false)] + public void CreateScrapItemServerRpc(int itemId, Vector3 position, NetworkBehaviourReference playerRef, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + playerRef.TryGet(out var player); + CreateItem(itemId, true, position, player, callParams); + } + + private void CreateItem(int itemId, bool fromScrapArray, Vector3 position, PlayerControllerB player, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + GameObject prefab; + if (fromScrapArray) { + prefab = Utility.GetDungeonItems()[itemId].spawnableItem.spawnPrefab; + } else { + prefab = StartOfRound.Instance.allItemsList.itemsList[itemId].spawnPrefab; + } + var comp = CreateGrabbableObject(prefab, player, position); + + StartCoroutine(WaitForEndOfFrameToUpdateItemInitialProperities(comp)); + UpdateItemFallingProperites(comp, position); + UpdateItemElevator(comp, player); + + var scarletItem = comp as IScarletItem; + if (scarletItem != null) scarletItem.UpdateSpecialProperties(callParams.specialValue); + + var scrapValue = callParams.scrapValue; + if (scrapValue > 0) { + UpdateItemValueProperties(comp, scrapValue); + } + + comp.NetworkObject.Spawn(false); + + if (player == null) CreateItemClientRpc(comp, position, callParams); + else CreateItemClientRpc(comp, position, player, callParams); + } + + [ClientRpc] + public void CreateItemClientRpc(NetworkBehaviourReference itemRef, Vector3 position, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + if (IsServer) return; + StartCoroutine(WaitForItem(itemRef, (c) => CreateItem(c, position, null, callParams))); + } + + [ClientRpc] + public void CreateItemClientRpc(NetworkBehaviourReference itemRef, Vector3 position, NetworkBehaviourReference playerRef, ScarletNetworkParams callParams = default(ScarletNetworkParams)){ + if (IsServer) return; + StartCoroutine(WaitForItemAndPlayer(itemRef, playerRef, (c, p) => CreateItem(c, position, p, callParams))); + } + + private IEnumerator WaitForItem(NetworkBehaviourReference itemRef, Action action){ + var t = Time.realtimeSinceStartup + 8f; + + GrabbableObject comp; + while(!itemRef.TryGet(out comp)){ + yield return null; + + if (Time.realtimeSinceStartup > t) { + Plugin.logger.LogError("Failed to find network object (ITEM)"); + yield break; + } + } + yield return new WaitForEndOfFrame(); + action.Invoke(comp); + } + + private IEnumerator WaitForItemAndPlayer(NetworkBehaviourReference itemRef, NetworkBehaviourReference playerRef, Action action){ + var t = Time.realtimeSinceStartup + 8f; + + GrabbableObject comp; + while(!itemRef.TryGet(out comp)){ + yield return null; + + if (Time.realtimeSinceStartup > t) { + Plugin.logger.LogError("Failed to find network object (ITEM)"); + yield break; + } + } + + PlayerControllerB player; + while(!playerRef.TryGet(out player)){ + yield return null; + + if (Time.realtimeSinceStartup > t) { + Plugin.logger.LogError("Failed to find network object (PLAYER)"); + yield break; + } + } + + yield return new WaitForEndOfFrame(); + action.Invoke(comp, player); + } + + private void CreateItem(GrabbableObject item, Vector3 position, PlayerControllerB player, ScarletNetworkParams callParams = default(ScarletNetworkParams)) { + UpdateItemFallingProperites(item, position); + UpdateItemElevator(item, player); + + var scarletItem = item as IScarletItem; + if (scarletItem != null) scarletItem.UpdateSpecialProperties(callParams.specialValue); + + var scrapValue = callParams.scrapValue; + if (scrapValue > 0) { + UpdateItemValueProperties(item, scrapValue); + } + } + + private GrabbableObject CreateGrabbableObject(GameObject prefab, PlayerControllerB player, Vector3 position) { + var parent = GetItemSpawnTransform(player); + var gameObject = GameObject.Instantiate(prefab, position, Quaternion.identity, parent); + return gameObject.GetComponent(); + } + + private Transform GetItemSpawnTransform(PlayerControllerB player) { + if (((player != null && player.isInElevator) || StartOfRound.Instance.inShipPhase) && RoundManager.Instance.spawnedScrapContainer != null) { + return RoundManager.Instance.spawnedScrapContainer; + } + return StartOfRound.Instance.elevatorTransform; + } + + private IEnumerator WaitForEndOfFrameToUpdateItemInitialProperities(GrabbableObject comp) { + yield return new WaitForEndOfFrame(); + UpdateItemInitialProperites(comp); + } + + private void UpdateItemInitialProperites(GrabbableObject comp) { + comp.reachedFloorTarget = false; + comp.hasHitGround = false; + comp.fallTime = 0f; + } + + private void UpdateItemFallingProperites(GrabbableObject comp, Vector3 position) { + comp.startFallingPosition = position; + comp.targetFloorPosition = comp.GetItemFloorPosition(position); + } + + private void UpdateItemValueProperties(GrabbableObject comp, int value) { + comp.SetScrapValue(value); + RoundManager.Instance.totalScrapValueInLevel += value; + } + + private void UpdateItemElevator(GrabbableObject comp, PlayerControllerB player){ + if (player != null && player.isInHangarShipRoom) { + player.SetItemInElevator(true, true, comp); + } + } + + } + + public static class ScarletNetworkManagerUtility { + + public static int GetFlashlightId(FlashlightItem flashlightItem){ + var flashlight = Assets.GetFlashlight(flashlightItem.itemProperties); + if (flashlight != null) return flashlight.itemId; + return -1; + } + + public static bool CreateFlashlight(PlayerControllerB player, FlashlightItem flashLight, FlandreCrystal crystal){ + var color = crystal.colorIndex; + var position = crystal.transform.position + Vector3.up * 0.25f; + var id = GetFlashlightId(flashLight); + var scrapValue = crystal.scrapValue; + var flashlightValue = scrapValue; + var nextCrystal = crystal.targetTransformation; + + if (nextCrystal) { + flashlightValue = Mathf.RoundToInt(flashlightValue * 0.5f); + } + + if (id == -1) { + var flashString = flashLight ? flashLight.itemProperties.itemName : "NULL"; + Plugin.logger.LogError($"Failed to find a corresponding flashlight for {flashString}"); + return false; + } + + player.DestroyPlayerItemAndSync_SDM(flashLight); + player.DestroyPlayerItemAndSync_SDM(crystal); + + ScarletNetworkParams callParams = new ScarletNetworkParams() { scrapValue = flashlightValue, specialValue = color}; + ScarletNetworkManager.Instance.CreateItemServerRpc(id, position, player, callParams); + + if (nextCrystal) { + var brokenCrystal = Assets.GetGlobalItem(nextCrystal); + if (brokenCrystal == null) { + Plugin.logger.LogError($"Failed to find a corresponding global item for {nextCrystal.itemName}"); + return true; + } + + var brokenCrystalId= brokenCrystal.itemId; + if (brokenCrystalId == -1) { + Plugin.logger.LogError($"Failed to find a corresponding id for {nextCrystal.itemName}"); + return true; + } + + var colorLength = FlandreCrystal.colorVariants.Length; + var nextScrapColorValue = scrapValue % colorLength; + var nextScrapValue = flashlightValue - (flashlightValue % colorLength) + nextScrapColorValue; + ScarletNetworkParams nextCallParams = new ScarletNetworkParams() { scrapValue = nextScrapValue}; + ScarletNetworkManager.Instance.CreateItemServerRpc(brokenCrystalId, position, player, nextCallParams); + } + + return true; + } + + public static void DestroyPlayerItemAndSync_SDM(this PlayerControllerB player, GrabbableObject obj){ + if (!player.IsOwner) return; + + var itemSlots = player.ItemSlots; + for(var i = 0; i < itemSlots.Length; ++i){ + if (itemSlots[i] == obj) { + DestroyPlayerItemInSlotAndSync_SDM(player, i); + return; + } + } + + var grabObjString = obj ? obj.itemProperties.itemName : "NULL"; + Plugin.logger.LogError($"Player {player.playerUsername}:{player.actualClientId} tried to destroy item {grabObjString} which is empty or not owned by player"); + } + + public static void DestroyPlayerItemInSlotAndSync_SDM(this PlayerControllerB player, int itemSlot){ + if (!player.IsOwner) return; + + var itemSlots = player.ItemSlots; + if (itemSlot >= itemSlots.Length || itemSlots[itemSlot] == null) { + Plugin.logger.LogError($"Player {player.playerUsername}:{player.actualClientId} tried to destroy item in slot {itemSlot} which is empty or overflowed"); + return; + } + + player.timeSinceSwitchingSlots = 0f; + player.DestroyPlayerItemInSlot_SDM(itemSlot); + ScarletNetworkManager.Instance.DestroyPlayerItemInSlotServerRpc(player, itemSlot); + } + + public static void DestroyPlayerItemInSlot_SDM(this PlayerControllerB player, int itemSlot){ + // base game does this + // better safe and slow then sorry + if (GameNetworkManager.Instance.localPlayerController == null || NetworkManager.Singleton == null || NetworkManager.Singleton.ShutdownInProgress) return; + + Plugin.logger.LogInfo($"Destroying player {player.playerUsername}:{player.actualClientId}'s item in slot {itemSlot}"); + + var heldObj = player.currentlyHeldObjectServer; + var heldObjString = heldObj ? heldObj.itemProperties.itemName : "NULL"; + var grabObj = player.ItemSlots[itemSlot]; + var grabObjString = grabObj ? grabObj.itemProperties.itemName : "NULL"; + Plugin.logger.LogInfo($"Held item {player.currentItemSlot}:{heldObjString}"); + Plugin.logger.LogInfo($"Target item {itemSlot}:{grabObjString}"); + + // fix weight and values + player.carryWeight -= Mathf.Clamp(grabObj.itemProperties.weight - 1f, 0f, 10f); + RoundManager.Instance.totalScrapValueInLevel -= grabObj.scrapValue; + + // destroy radar + if (grabObj.radarIcon) { + GameObject.Destroy(grabObj.radarIcon.gameObject); + } + + // fix character animations and UI + // if target item is the same as held item + if (player.isHoldingObject) { + if (player.currentItemSlot == itemSlot) { + player.isHoldingObject = false; + player.twoHanded = false; + if (player.IsOwner) { + player.playerBodyAnimator.SetBool("cancelHolding", true); + player.playerBodyAnimator.SetTrigger("Throw"); + HUDManager.Instance.holdingTwoHandedItem.enabled = false; + HUDManager.Instance.ClearControlTips(); + player.activatingItem = false; + } + } + + if (heldObj != null && heldObj == grabObj) { + if (player.IsOwner) { + player.SetSpecialGrabAnimationBool(false, heldObj); + } + player.currentlyHeldObjectServer = null; + } + } + + // no matter what, we need these to be called + if (player.IsOwner) { + HUDManager.Instance.itemSlotIcons[itemSlot].enabled = false; + grabObj.DiscardItemOnClient(); + } + + player.ItemSlots[itemSlot] = null; + + if (player.IsServer) grabObj.NetworkObject.Despawn(true); + } + + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/MenuManagerPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/MenuManagerPatch.cs new file mode 100644 index 0000000..bbc2961 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/MenuManagerPatch.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; + +/* + +namespace ScarletMansion.GamePatch { + public class MenuManagerPatch { + + [HarmonyPatch(typeof(MenuManager), "Awake")] + [HarmonyPostfix] + public static void AwakePatch(ref MenuManager __instance){ + try { + // this is a terrible way to do it + // but it does let me know + var parent = __instance.transform.parent; + var container = parent.Find("MenuContainer"); + var mainButtons = container.Find("MainButtons"); + var hostButton = mainButtons.Find("HostButton").gameObject; + + //var target = GameObject.Instantiate(Assets.networkObjectList.mainMenuPrefab, container); + //target.transform.localScale = Vector3.one; + } + + catch { + + } + } + + } +} + +*/ diff --git a/ScarletMansion/ScarletMansion/GamePatch/PlayerControllerBPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/PlayerControllerBPatch.cs new file mode 100644 index 0000000..6030179 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/PlayerControllerBPatch.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; +using GameNetcodeStuff; +using ScarletMansion.GamePatch.Components; + +namespace ScarletMansion.GamePatch { + public class PlayerControllerBPatch { + + [HarmonyPatch(typeof(PlayerControllerB), "Awake")] + [HarmonyPrefix] + public static void AwakePatch(ref PlayerControllerB __instance){ + if (__instance.GetComponent() == null) { + Plugin.logger.LogInfo($"Adding Scarlet player script to player {__instance.playerClientId}"); + var comp = __instance.gameObject.AddComponent(); + comp.Initialize(__instance); + } else { + Plugin.logger.LogWarning($"Player {__instance.playerClientId} already has Scarlet player script. Skipping. YOU CAN PROBABLY IGNORE THIS!"); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/FireExitEmptySpaceCheck.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/FireExitEmptySpaceCheck.cs new file mode 100644 index 0000000..f92701b --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/FireExitEmptySpaceCheck.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + public class FireExitEmptySpaceCheck : RandomProp { + + public GameObject target; + public Bounds bounds; + + public override void Process(RandomStream randomStream, Tile tile) { + var b = GetBounds(); + var layerMask = LayerMask.GetMask(new string[3] { "Room", "Railing", "MapHazards" }); + if (Physics.CheckBox(b.center, b.extents, transform.rotation, layerMask)){ + Plugin.logger.LogInfo("Disabling fire exit potential spawn due to overlapping space"); + target.SetActive(false); + } + } + + public Bounds GetBounds(){ + return transform.TransformBounds(bounds); + } + + public void OnDrawGizmosSelected(){ + var b = GetBounds(); + Gizmos.DrawWireCube(b.center, b.size); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/FloorPropBasedOnFloor.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/FloorPropBasedOnFloor.cs new file mode 100644 index 0000000..5d4d43f --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/FloorPropBasedOnFloor.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DunGen; +using UnityEngine; + +namespace ScarletMansion.GamePatch.Props { + public class FloorPropBasedOnFloor : RandomProp { + + public GameObject topFloorPrefab; + public List secondFloorPrefabs; + public GameObject mainFloorPrefab; + public List basementFloorPrefabs; + public GameObject bottomFloorPrefab; + + public override void Process(RandomStream randomStream, Tile tile) { + var baseY = KnightSpawnManager.Instance.transform.position.y; + var currentY = tile.transform.position.y; + + var floor = Mathf.RoundToInt((currentY - baseY) / 8f); + Plugin.logger.LogInfo($"F{floor}"); + // fuck it im lazy + GameObject p; + if (floor == 0) { + p = mainFloorPrefab; + } else if (floor == 1) { + p = secondFloorPrefabs[randomStream.Next(secondFloorPrefabs.Count)]; + } else if (floor == -1) { + p = basementFloorPrefabs[randomStream.Next(basementFloorPrefabs.Count)]; + } else if (floor >= 2) { + p = topFloorPrefab; + } else { + p = bottomFloorPrefab; + } + + var gameObject = GameObject.Instantiate(p); + var gameObjectTransform = gameObject.transform; + gameObjectTransform.parent = transform; + + gameObjectTransform.localPosition = Vector3.zero; + gameObjectTransform.localRotation = Quaternion.identity; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/GlobalPropWithChildren.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/GlobalPropWithChildren.cs new file mode 100644 index 0000000..7d37fae --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/GlobalPropWithChildren.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropBasic.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropBasic.cs new file mode 100644 index 0000000..57bae02 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropBasic.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + public class LocalPropSetBasic : RandomProp { + + public override void Process(RandomStream randomStream, Tile tile) { + var transformCount = transform.childCount; + var count = Mathf.Clamp(propCount.GetRandom(randomStream), 0, transformCount); + + var array = new GameObject[transformCount]; + for(var i = 0; i < transformCount; ++i){ + array[i] = transform.GetChild(i).gameObject; + } + + Utility.Shuffle(randomStream, array); + + for(var i = count; i < transformCount; ++i){ + UnityUtil.Destroy(array[i]); + } + + } + + public IntRange propCount = new IntRange(1, 1); + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropSingle.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropSingle.cs new file mode 100644 index 0000000..6f6e5dc --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/LocalPropSingle.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + public class LocalPropSingle : RandomProp { + + public override void Process(RandomStream randomStream, Tile tile) { + + if (randomizePosition){ + var x = (float)randomStream.NextDouble() * randomPositionRange; + var z = (float)randomStream.NextDouble() * randomPositionRange; + transform.localPosition = new Vector3(x, 0f, z); + } + + if (randomizeRotation){ + var y = randomRotationRange.GetRandom(randomStream); + transform.localEulerAngles = new Vector3(0f, y, 0f); + } + } + + public bool randomizePosition = false; + public float randomPositionRange = 0f; + public bool randomizeRotation = false; + public FloatRange randomRotationRange = new FloatRange(0f, 360f); + + public void OnDrawGizmosSelected(){ + if (randomizePosition) { + Utility.DrawGizmoCircle(transform, randomPositionRange, Color.green); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasic.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasic.cs new file mode 100644 index 0000000..a8fae12 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasic.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + public class RandomPrefabBasic : RandomPrefabBase { + + public override void Process(RandomStream randomStream, Tile tile) { + if (props.Count <= 0) return; + + var value = randomStream.Next(props.Count); + var prefab = props[value]; + + SpawnGameObject(randomStream, prefab); + + } + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasicBase.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasicBase.cs new file mode 100644 index 0000000..002e96f --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabBasicBase.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + public abstract class RandomPrefabBase : RandomProp { + + public List props = new List(); + + public bool randomizePosition = false; + public float randomPositionRange = 0f; + public bool randomizeRotation = true; + public FloatRange randomRotationRange = new FloatRange(0f, 360f); + + public void SpawnGameObject(RandomStream randomStream, GameObject prefab){ + var gameObject = GameObject.Instantiate(prefab); + var gameObjectTransform = gameObject.transform; + gameObjectTransform.parent = transform; + + if (randomizePosition){ + var x = (float)randomStream.NextDouble() * randomPositionRange; + var z = (float)randomStream.NextDouble() * randomPositionRange; + gameObjectTransform.localPosition = new Vector3(x, 0f, z); + } else { + gameObjectTransform.localPosition = Vector3.zero; + } + + if (randomizeRotation){ + var y = randomRotationRange.GetRandom(randomStream); + gameObjectTransform.localEulerAngles = new Vector3(0f, y, 0f); + } else { + gameObjectTransform.localRotation = Quaternion.identity; + } + } + + public void OnDrawGizmosSelected(){ + if (randomizePosition) { + Utility.DrawGizmoCircle(transform, randomPositionRange, Color.green); + } + } + + } + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabCycle.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabCycle.cs new file mode 100644 index 0000000..b8af607 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabCycle.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + public class RandomPrefabCycle : RandomPrefabBase { + + public static int cycle = 0; + + public static void UpdateCycle(int value){ + Plugin.logger.LogInfo($"Updating RandomPrefab cylce to {value}"); + cycle = value; + } + + public override void Process(RandomStream randomStream, Tile tile) { + if (props.Count <= 0) return; + + Plugin.logger.LogInfo($"Cycle {cycle}"); + var cycleValue = cycle++; + var index = cycleValue % props.Count; + var prefab = props[index]; + + SpawnGameObject(randomStream, prefab); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabWithScale.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabWithScale.cs new file mode 100644 index 0000000..451f23e --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/RandomPrefabWithScale.cs @@ -0,0 +1,32 @@ +using System; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + + [AddComponentMenu("DunGen/Random Props/Random Prefab with Scale")] + public class RandomPrefabWithScale : RandomProp { + + public override void Process(RandomStream randomStream, Tile tile) { + + if (Props.Weights.Count <= 0) return; + var value = this.Props.GetRandom(randomStream, tile.Placement.IsOnMainPath, tile.Placement.NormalizedDepth, null, true, true).Value; + var gameObject = Instantiate(value, transform); + + var t = gameObject.transform; + if (ZeroPosition) t.localPosition = Vector3.zero; + else t.localPosition = value.transform.localPosition; + + if (ZeroRotation) t.localRotation = Quaternion.identity; + else t.localRotation = value.transform.localRotation; + + } + + [AcceptGameObjectTypes(GameObjectFilter.Asset)] + public GameObjectChanceTable Props = new GameObjectChanceTable(); + + public bool ZeroPosition = true; + + public bool ZeroRotation = true; + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/Props/SpawnSyncedObjectCycle.cs b/ScarletMansion/ScarletMansion/GamePatch/Props/SpawnSyncedObjectCycle.cs new file mode 100644 index 0000000..e7cfc1c --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/Props/SpawnSyncedObjectCycle.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion.GamePatch.Props { + + public class SpawnSyncedObjectCycle : MonoBehaviour, IDungeonCompleteReceiver { + + public static int cycle; + public static Dictionary cycleDictionary; + + public SpawnSyncedObject spawn; + public int id; + public List props = new List(); + + void Reset(){ + spawn = GetComponent(); + } + + public static void UpdateCycle(int value){ + Plugin.logger.LogInfo($"Updating SpawnSyncedObject start cycle to {value}"); + cycle = value; + cycleDictionary = new Dictionary(); + } + + public int GetCycle(int id){ + if (!cycleDictionary.TryGetValue(id, out var value)){ + value = cycle; + cycleDictionary.Add(id, value); + } + + cycleDictionary[id] = value + 1; + Plugin.logger.LogInfo($"Cycle{id}: {value}"); + return value; + } + + public void OnDungeonComplete(Dungeon dungeon) { + var index = GetCycle(id) % props.Count; + var prefab = props[index]; + spawn.spawnPrefab = prefab; + } + } + + +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/RoundManagerPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/RoundManagerPatch.cs new file mode 100644 index 0000000..455a94b --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/RoundManagerPatch.cs @@ -0,0 +1,60 @@ +using System; +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 ScarletMansion.GamePatch.Managers; + +namespace ScarletMansion.GamePatch { + public class RoundManagerPatch { + + public static InjectionDictionary scrapInjection = new InjectionDictionary( + "Scrap", + typeof(RoundManagerPatch).GetMethod("ModifyScrapCount", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public), + new CodeInstruction(OpCodes.Ldfld, typeof(RoundManager).GetField("scrapAmountMultiplier")) + ); + + public static InjectionDictionary mapHazardInjection = new InjectionDictionary( + "Map Hazard", + typeof(RoundManagerPatch).GetMethod("ModifyMapCount", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public), + new CodeInstruction(OpCodes.Callvirt, typeof(AnimationCurve).GetMethod("Evaluate", BindingFlags.Instance | BindingFlags.Public)) + ); + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")] + public static IEnumerable SpawnScrapInLevelPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, scrapInjection, "SpawnScrapInLevel", 1); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(RoundManager), "SpawnMapObjects")] + public static IEnumerable SpawnMapObjectsPatch(IEnumerable instructions){ + return TranspilerUtilities.InjectMethod(instructions, mapHazardInjection, "SpawnMapObjects", 1); + } + + public static float ModifyScrapCount(float count){ + if (DunGenPatch.Patch.active == false) return count; + Plugin.logger.LogInfo($"Scrap: {count} -> {count * PluginConfig.Instance.lootMultiplierValue}"); + return count * PluginConfig.Instance.lootMultiplierValue; + } + + public static float ModifyMapCount(float count){ + if (DunGenPatch.Patch.active == false) return count; + Plugin.logger.LogInfo($"Map Hazards: {count} -> {count * PluginConfig.Instance.mapHazardsMultiplierValue}"); + return count * PluginConfig.Instance.mapHazardsMultiplierValue; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(RoundManager), "SetPowerOffAtStart")] + public static void SetPowerOffAtStartPatch(){ + DoorwayManager.onMainEntranceTeleportSpawnedEvent.Call(); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/ScarletLightPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/ScarletLightPatch.cs new file mode 100644 index 0000000..9df735a --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/ScarletLightPatch.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; +using GameNetcodeStuff; + +/* + +namespace ScarletMansion.GamePatch { + + public class ScarletLightPatch { + + [HarmonyPatch(typeof(TimeOfDay), "SetInsideLightingDimness")] + [HarmonyPostfix] + public static void SetInsideLightingDimnessPatch(ref TimeOfDay __instance){ + + if (DunGenPatch.Patch.active == false) return; + + PlayerControllerB playerControllerB = GameNetworkManager.Instance.localPlayerController; + if (GameNetworkManager.Instance.localPlayerController.isPlayerDead && GameNetworkManager.Instance.localPlayerController.spectatedPlayerScript != null) + playerControllerB = GameNetworkManager.Instance.localPlayerController.spectatedPlayerScript; + + if (playerControllerB.isInsideFactory) { + ScarletLightingManager.UpdateLights(GetLightModifier(__instance)); + } else { + ScarletLightingManager.DisableLights(); + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(StartOfRound), "ShipHasLeft")] + public static void ShipHasLeftPatch(){ + ScarletLightingManager.Clean(); + } + + public static float GetLightModifier(TimeOfDay __instance){ + float modifier; + var time = __instance.currentDayTime / __instance.totalTime; + if (time < 0.33f) { + modifier = 1f; + } else if (time < 0.63f){ + modifier = Mathf.Lerp(1f, 0.9f, InvLerp(0.33f, 0.63f, time)); + } else if (time < 0.9f){ + modifier = Mathf.Lerp(0.9f, 0f, InvLerp(0.63f, 0.9f, time)); + } else { + modifier = 0f; + } + return modifier; + } + + public static float InvLerp(float a, float b, float v){ + return (v - a) / (b - a); + } + } +} +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/GamePatch/ShotgunItemPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/ShotgunItemPatch.cs new file mode 100644 index 0000000..34ac973 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/ShotgunItemPatch.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; + +namespace ScarletMansion.GamePatch { + public class ShotgunItemPatch { + + [HarmonyPatch(typeof(ShotgunItem), "ShootGun")] + [HarmonyPostfix] + public static void ShootGunPatch(ref ShotgunItem __instance, Vector3 shotgunPosition, Vector3 shotgunForward){ + if (Components.ScarletDoorLock.ScarletDoorRaycast(__instance, shotgunPosition, shotgunForward, 30f, out var door)){ + var damage = PluginConfig.Instance.shotgunDamageValue; + door.ApplyDamageServerRpc(shotgunForward, damage); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/GamePatch/ShovelPatch.cs b/ScarletMansion/ScarletMansion/GamePatch/ShovelPatch.cs new file mode 100644 index 0000000..8372a60 --- /dev/null +++ b/ScarletMansion/ScarletMansion/GamePatch/ShovelPatch.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using HarmonyLib; + +namespace ScarletMansion.GamePatch { + public class ShovelPatch { + + [HarmonyPatch(typeof(Shovel), "HitShovel")] + [HarmonyPostfix] + public static void HitShovelPatch(ref Shovel __instance, bool cancel){ + var prev = __instance.previousPlayerHeldBy; + if (prev == null || cancel) return; + + var prevTransform = prev.gameplayCamera.transform; + var position = prevTransform.position + prevTransform.right * -0.35f; + var forward = prevTransform.forward; + if (Components.ScarletDoorLock.ScarletDoorRaycast(__instance, position, forward, 1.5f + 0.8f, out var door)){ + var damage = PluginConfig.Instance.shovelDamageValue; + door.ApplyDamageServerRpc(forward, damage); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/LoadingPatch/NetworkObjectListScriptableObject.cs b/ScarletMansion/ScarletMansion/LoadingPatch/NetworkObjectListScriptableObject.cs new file mode 100644 index 0000000..9c1715b --- /dev/null +++ b/ScarletMansion/ScarletMansion/LoadingPatch/NetworkObjectListScriptableObject.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using DunGen; + +namespace ScarletMansion { + + [CreateAssetMenu(menuName = "ScriptableObjects/NetworkObjectList")] + public class NetworkObjectListScriptableObject : ScriptableObject { + + [Header("Network Objects")] + public List networkDungeon; + public List networkDoors; + public List networkItems; + public List networkFrames; + public List networkOther; + + [Header("To Fix With InitPatch")] + public List toFixGameObjects; + public List items; + + [Header("DunGen")] + public List archetypes; + public List tilesets; + + //public GameObject mainMenuPrefab; + [Header("Main Prefabs")] + public GameObject scarletNetworkManager; + } +} diff --git a/ScarletMansion/ScarletMansion/MainMenuUpdate.cs b/ScarletMansion/ScarletMansion/MainMenuUpdate.cs new file mode 100644 index 0000000..18c70d4 --- /dev/null +++ b/ScarletMansion/ScarletMansion/MainMenuUpdate.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using TMPro; +using UnityEngine.UI; +using BepInEx.Configuration; + + +/* +namespace ScarletMansion { + public class MainMenuUpdate : MonoBehaviour { + + public static readonly string mainMessage = "The Scarlet Devil Mansion mod has updated the default values used in the config. Please click the button below to apply the new values. If this mod is part of a mod pack, it's probably best to ignore this and use the values set by the mod pack.\n\nThis window will not appear again, but the default values can still be found in the config."; + public static readonly string version1Message = "These new values and many internal changes to the dungeon's generation were made to reduce load times by about 80% on average."; + + public const int version = 2; + public static readonly string savePath = "LCGeneralSaveData"; + public static readonly string prefsKey = "SDM_MainMenu"; + + [Header("References")] + public Canvas canvas; + public TextMeshProUGUI textMesh; + public Button denyButton; + public Button acceptButton; + + public ChangeList mainMenuChanges = new ChangeList( + "MainMenu", + new ChangeInt ( PluginConfig.dunGenWidthBase ) , + new ChangeInt ( PluginConfig.dunGenLengthBase ) , + new ChangeFloat ( PluginConfig.dunGenWidthMultiFactor ) , + new ChangeFloat ( PluginConfig.dunGenLengthMultiFactor ) , + new ChangeInt ( PluginConfig.hallwayLightsWeight ) , + new ChangeInt ( PluginConfig.ceilingLightsWeight ) + ); + + public void Start(){ + //ForceVersionChange(1); + + var hasShown = GetLastVersion() >= version; + if (hasShown) { + Plugin.logger.LogInfo("Already seen update. Deny"); + return; + } + + if (!mainMenuChanges.RequiresUpdate()) { + Plugin.logger.LogInfo("Values already up to date. Deny"); + UpdateLastVersion(); + return; + } + + canvas.enabled = true; + textMesh.text = mainMessage + "\n\n" + version1Message + "\n\n" + mainMenuChanges.ToString(); + denyButton.onClick.RemoveAllListeners(); + denyButton.onClick.AddListener(CloseWindow); + + acceptButton.onClick.RemoveAllListeners(); + acceptButton.onClick.AddListener(UpdateChanges); + } + + public int GetLastVersion(){ + return ES3.Load(prefsKey, savePath, 0); + } + + public void ForceVersionChange(int v){ + ES3.Save(prefsKey, v, savePath); + } + + public void UpdateLastVersion(){ + ForceVersionChange(version); + } + + public void UpdateChanges(){ + mainMenuChanges.UpdateChanges(); + CloseWindow(); + } + + public void CloseWindow(){ + canvas.enabled = false; + UpdateLastVersion(); + } + + } +} +*/ \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/ModPatch/AdvancedCompanyPatch.cs b/ScarletMansion/ScarletMansion/ModPatch/AdvancedCompanyPatch.cs new file mode 100644 index 0000000..c457af9 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/AdvancedCompanyPatch.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; + + +namespace ScarletMansion.ModPatch { + public class AdvancedCompanyPatch : ModPatch { + + public override string warningMessage => "AC compability will stay but if it breaks in the future, I'm not fixing it. Apologies."; + + public AdvancedCompanyPatch(string guid) : base(guid) { } + + public override void AddPatch() { + try { + var desiredTypeString = "AdvancedCompany.Game.Manager+Moons, AdvancedCompany"; + var moonManagerType = System.Type.GetType(desiredTypeString); + + var GetInsideEnemiesAC = moonManagerType.GetMethod("GetInsideEnemies", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + var GetLootTableAC = moonManagerType.GetMethod("GetLootTable", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + var GetScrapAmountAC = moonManagerType.GetMethod("GetScrapAmountModifier", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + + GamePatch.LoadAssetsIntoLevelPatch.enemiesInjection.instructions.Add(new CodeInstruction(OpCodes.Call, GetInsideEnemiesAC)); + //GamePatch.LoadAssetsIntoLevelPatch.itemsInjection.instructions.Add(new CodeInstruction(OpCodes.Call, GetLootTableAC)); + GamePatch.RoundManagerPatch.scrapInjection.instructions.Add(new CodeInstruction(OpCodes.Call, GetScrapAmountAC)); + + } catch (System.Exception e) { + Plugin.logger.LogError("Failed to setup patching for Advanced Company. Custom enemies and items for SDM will not spawn, but bugs should not happen maybe possibly?"); + Plugin.logger.LogError(e); + } + } + } +} diff --git a/ScarletMansion/ScarletMansion/ModPatch/FacilityMeltdownPatch.cs b/ScarletMansion/ScarletMansion/ModPatch/FacilityMeltdownPatch.cs new file mode 100644 index 0000000..35f87af --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/FacilityMeltdownPatch.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace ScarletMansion.ModPatch { + public class FacilityMeltdownPatch : ModPatch { + + public override string version => "2.4.4"; + + public FacilityMeltdownPatch(string guid) : base(guid) { } + + public override void AddPatch(){ + GamePatch.Components.ScarletBedroom.onBedroomEndEvent.AddEvent("FacilityMeltdown", Call); + } + + public static void Call(){ + if (StartOfRound.Instance.IsHost && PluginConfig.Instance.facilityMeltdownActiveValue){ + FacilityMeltdown.API.MeltdownAPI.StartMeltdown(Plugin.modGUID); + GamePatch.Managers.AngerManager.Instance.TriggerAngerLightForever(); + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/ModPatch/LethalConfigPatch.cs b/ScarletMansion/ScarletMansion/ModPatch/LethalConfigPatch.cs new file mode 100644 index 0000000..8ae5f48 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/LethalConfigPatch.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using LethalConfig; +using LethalConfig.ConfigItems; +using BepInEx.Configuration; +using System.Reflection; + +using ChangeList = ScarletMansion.PresetConfig.ChangeList; +using ConfigEntryBundleInt = ScarletMansion.PluginConfig.ConfigEntryBundle; +using ConfigEntryBundleFloat = ScarletMansion.PluginConfig.ConfigEntryBundle; +using ConfigEntryBundleString = ScarletMansion.PluginConfig.ConfigEntryBundle; +using ConfigEntryBundleBool = ScarletMansion.PluginConfig.ConfigEntryBundle; + +namespace ScarletMansion.ModPatch { + public class LethalConfigPatch : ModPatch { + + public const string section = "_Presets"; + public const string descriptionPrefix = "These are a set of preset config values for your convience. Your config values will automatically update to these preset values every time SDM is loaded. To disable this feature, set this value to Custom."; + public const string requiresNewLobby = "Requires a lobby restart for the values to be updated."; + + public LethalConfigPatch(string guid) : base(guid) { } + + public static void ForceUIUpdate(){ + try { + var desiredTypeString = "LethalConfig.MonoBehaviours.ConfigMenu, LethalConfig"; + var configMenuType = Type.GetType(desiredTypeString); + var configMenuObject = GameObject.FindObjectOfType(configMenuType); + + var closeFunction = configMenuType.GetMethod("Close", BindingFlags.Instance | BindingFlags.Public); + var openFunction = configMenuType.GetMethod("Open", BindingFlags.Instance | BindingFlags.Public); + closeFunction.Invoke(configMenuObject, new object[] {false} ); + openFunction.Invoke(configMenuObject, new object[] {} ); + + } catch (Exception e) { + Plugin.logger.LogWarning("Could not force Lethal Config UI update"); + Plugin.logger.LogError(e.ToString()); + return; + } + + } + + public static LethalConfig.ConfigItems.Options.CanModifyResult CanModifyCallback(){ + if (ScarletNetworkManager.Instance) { + return LethalConfig.ConfigItems.Options.CanModifyResult.False("Cannot edit in game"); + } + return LethalConfig.ConfigItems.Options.CanModifyResult.True(); + } + + public static void CreateConfig(ConfigEntryBase configEntry) { + if (configEntry is ConfigEntry) CreateIntConfig(configEntry as ConfigEntry); + else if (configEntry is ConfigEntry) CreateFloatConfig(configEntry as ConfigEntry); + else if (configEntry is ConfigEntry) CreateStringConfig(configEntry as ConfigEntry); + else if (configEntry is ConfigEntry) CreateBoolConfig(configEntry as ConfigEntry); + } + + public static void CreateIntConfig(ConfigEntry configEntry){ + var options = new LethalConfig.ConfigItems.Options.IntSliderOptions{ + Section = configEntry.Definition.Section, + Name = configEntry.Definition.Key, + Description = configEntry.Description.Description, + RequiresRestart = false, + CanModifyCallback = CanModifyCallback, + }; + + var entry = new IntSliderConfigItem(configEntry, options); + LethalConfigManager.AddConfigItem(entry); + } + + public static void CreateFloatConfig(ConfigEntry configEntry){ + var options = new LethalConfig.ConfigItems.Options.FloatSliderOptions{ + Section = configEntry.Definition.Section, + Name = configEntry.Definition.Key, + Description = configEntry.Description.Description, + RequiresRestart = false, + CanModifyCallback = CanModifyCallback, + }; + + var entry = new FloatSliderConfigItem(configEntry, options); + LethalConfigManager.AddConfigItem(entry); + } + + public static void CreateStringConfig(ConfigEntry configEntry){ + var options = new LethalConfig.ConfigItems.Options.TextInputFieldOptions{ + Section = configEntry.Definition.Section, + Name = configEntry.Definition.Key, + Description = configEntry.Description.Description, + RequiresRestart = false, + CanModifyCallback = CanModifyCallback, + }; + + var entry = new TextInputFieldConfigItem(configEntry, options); + LethalConfigManager.AddConfigItem(entry); + } + + public static void CreateBoolConfig(ConfigEntry configEntry){ + var options = new LethalConfig.ConfigItems.Options.BoolCheckBoxOptions{ + Section = configEntry.Definition.Section, + Name = configEntry.Definition.Key, + Description = configEntry.Description.Description, + RequiresRestart = false, + CanModifyCallback = CanModifyCallback, + }; + + var entry = new BoolCheckBoxConfigItem(configEntry, options); + LethalConfigManager.AddConfigItem(entry); + } + + public static void CreatePresetConfig(ConfigEntry configEntry, List changeList) where T: Enum { + configEntry.SettingChanged += (obj, args) => ForceUIUpdate(); + + var description = $"{requiresNewLobby}\n\n{descriptionPrefix}\n\n"; + foreach(var c in changeList) { + description += $"{c.name}\n{c.description}\n\n"; + } + + var options = new LethalConfig.ConfigItems.Options.EnumDropDownOptions{ + Section = "_Presets", + Name = configEntry.Definition.Key, + Description = description, + RequiresRestart = false, + CanModifyCallback = CanModifyCallback, + }; + + var entry = new EnumDropDownConfigItem(configEntry, options); + LethalConfigManager.AddConfigItem(entry); + } + + public static void AutoGenerateConfigs(params object[] ignoreTargets){ + var fields = typeof(PluginConfig).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach(var p in fields){ + var value = p.GetValue(null); + if (ignoreTargets.Contains(value)) continue; + + var valueBundle = value as PluginConfig.ConfigEntryBundleBase; + if (valueBundle != null){ + foreach(var c in valueBundle.GetConfigs()){ + CreateConfig(c); + } + } + } + } + + public override void AddPatch() { + LethalConfigManager.SkipAutoGen(); + + CreatePresetConfig(PluginConfig.lcDungeonGenerationPreset.config, PresetConfig.dungeonGenerationChangeList); + CreatePresetConfig(PluginConfig.lcDungeonLightingPreset.config, PresetConfig.dungeonLightingChangeList); + + AutoGenerateConfigs(PluginConfig.lcDungeonGenerationPreset, PluginConfig.lcDungeonLightingPreset); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/ModPatch/MimicsPatch.cs b/ScarletMansion/ScarletMansion/ModPatch/MimicsPatch.cs new file mode 100644 index 0000000..444a1b6 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/MimicsPatch.cs @@ -0,0 +1,37 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using BepInEx; +using HarmonyLib; +using System.Reflection; +using System.Reflection.Emit; +using DunGen; +using ScarletMansion.GamePatch.FixValues; +using UnityEngine.Events; +using GameNetcodeStuff; +using ScarletMansion.GamePatch.Components; +using System.IO; + +namespace ScarletMansion.ModPatch { + public class MimicsPatch : ModPatch { + + public override string version => "2.6.0"; + + public MimicsPatch(string guid) : base(guid) { } + + public override void AddPatch() { + var assemblyPath = Assembly.GetExecutingAssembly().Location; + var folderPath = Path.GetDirectoryName(assemblyPath); + var pathAssemblyPath = Path.Combine(folderPath, "ScarletMansionMimicsPatch.dll"); + + var assembly = Assembly.LoadFile(pathAssemblyPath); + var type = assembly.GetType("ScarletMansion.ModPatch.Patch"); + Activator.CreateInstance(type); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/ModPatch/ModCompability.cs b/ScarletMansion/ScarletMansion/ModPatch/ModCompability.cs new file mode 100644 index 0000000..b0a9501 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/ModCompability.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BepInEx; +using BepInEx.Bootstrap; + +namespace ScarletMansion.ModPatch { + public class ModCompability { + + public const string advancedCompanyGuid = "com.potatoepet.AdvancedCompany"; + public const string lethalConfigGuid = "ainavt.lc.lethalconfig"; + public const string facilityMeldownGuid = "me.loaforc.facilitymeltdown"; + public const string reserveFlashlightGuid = "FlipMods.ReservedFlashlightSlot"; + public const string mimicsGuid = "x753.Mimics"; + + public static readonly ModPatch[] modPatches = new ModPatch[] { + new AdvancedCompanyPatch(advancedCompanyGuid), + new LethalConfigPatch(lethalConfigGuid), + new FacilityMeltdownPatch(facilityMeldownGuid), + new ReservedItemSlotPatch(reserveFlashlightGuid), + new MimicsPatch(mimicsGuid) + }; + + public static void GetActiveMods(){ + foreach(var m in modPatches) + m.CheckIfActive(); + } + + public static void ActivateActiveMods(){ + foreach(var m in modPatches) + m.Activate(); + } + + public static int GetStartOfRoundScriptLength(){ + return StartOfRound.Instance.allPlayerScripts.Length; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/ModPatch/ModPatch.cs b/ScarletMansion/ScarletMansion/ModPatch/ModPatch.cs new file mode 100644 index 0000000..2557324 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/ModPatch.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BepInEx; +using BepInEx.Bootstrap; + +namespace ScarletMansion.ModPatch { + public abstract class ModPatch { + + public string guid; + public bool active; + + public virtual string version => null; + public virtual string warningMessage => null; + + public abstract void AddPatch(); + + public ModPatch(string guid){ + this.guid = guid; + active = false; + } + + public void CheckIfActive(){ + var modLoaded = Chainloader.PluginInfos.ContainsKey(guid); + if (!modLoaded) return; + + bool validVersion; + var pluginInfo = Chainloader.PluginInfos[guid]; + var loadedVersion = pluginInfo.Metadata.Version; + if (string.IsNullOrWhiteSpace(version)){ + validVersion = true; + } else { + var requiredVersion = new Version(version); + validVersion = loadedVersion >= requiredVersion; + } + + if (validVersion) { + active = true; + Plugin.logger.LogInfo($"Loading compability patch for {guid}"); + if (!string.IsNullOrWhiteSpace(warningMessage)) Plugin.logger.LogWarning(warningMessage); + } + + else { + Plugin.logger.LogWarning($"Failed to load compability patch for {guid}. Requires version {version} but found {loadedVersion}"); + } + } + + public void Activate(){ + if (active) + AddPatch(); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/ModPatch/ReservedItemSlotPatch.cs b/ScarletMansion/ScarletMansion/ModPatch/ReservedItemSlotPatch.cs new file mode 100644 index 0000000..e109511 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ModPatch/ReservedItemSlotPatch.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ReservedItemSlotCore.Data; +using UnityEngine; + +namespace ScarletMansion.ModPatch { + public class ReservedItemSlotPatch : ModPatch { + + public override string version => "2.0.0"; + + public ReservedItemSlotPatch(string guid) : base(guid) { } + + public override void AddPatch(){ + Assets.onAssetsLoadEvent.AddEvent("LoadFlashlight", LoadFlashlight); + } + + public static void LoadFlashlight(){ + var proFlashlight = new ReservedItemData(Assets.flashlight.displayName, PlayerBone.Spine3, new Vector3(0.2f, 0.25f, 0f), new Vector3(90f, 0f, 0f)); + var flashlight = new ReservedItemData(Assets.flashlightBB.displayName, PlayerBone.Spine3, new Vector3(0.2f, 0.25f, 0f), new Vector3(90f, 0f, 0f)); + + ReservedItemSlotData.TryAddItemDataToReservedItemSlot(proFlashlight, "flashlight"); + ReservedItemSlotData.TryAddItemDataToReservedItemSlot(flashlight, "flashlight"); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/Plugin.cs b/ScarletMansion/ScarletMansion/Plugin.cs new file mode 100644 index 0000000..7158316 --- /dev/null +++ b/ScarletMansion/ScarletMansion/Plugin.cs @@ -0,0 +1,160 @@ +using System.Threading.Tasks; +using BepInEx; +using HarmonyLib; +using BepInEx.Logging; +using BepInEx.Configuration; +using UnityEngine; +using System.Reflection; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine.Networking; +using System.Collections; +using LethalLib.Modules; +using LethalLevelLoader; +using ScarletMansion.GamePatch; +using ScarletMansion.ModPatch; +using ScarletMansion.DunGenPatch; +using ScarletMansion.GamePatch.Managers; + +namespace ScarletMansion { + + [BepInPlugin(modGUID, modName, modVersion)] + + [BepInDependency("imabatby.lethallevelloader", "1.2.0.1")] + [BepInDependency("evaisa.lethallib", "0.13.2")] + + [BepInDependency(ModCompability.advancedCompanyGuid, BepInDependency.DependencyFlags.SoftDependency)] + [BepInDependency(ModCompability.lethalConfigGuid, BepInDependency.DependencyFlags.SoftDependency)] + [BepInDependency(ModCompability.facilityMeldownGuid, BepInDependency.DependencyFlags.SoftDependency)] + [BepInDependency(ModCompability.reserveFlashlightGuid, BepInDependency.DependencyFlags.SoftDependency)] + [BepInDependency(ModCompability.mimicsGuid, BepInDependency.DependencyFlags.SoftDependency)] + [BepInProcess("Lethal Company.exe")] + public class Plugin : BaseUnityPlugin { + public const string modGUID = "ImoutoSama.ScarletMansion"; + private const string modName = "Scarlet Mansion"; + private const string modVersion = "1.3.10"; + + public readonly Harmony harmony = new Harmony(modGUID); + + public static Plugin Instance {get; private set;} + public static PluginConfig MyConfig { get; internal set; } + + public static ManualLogSource logger { get; internal set; } + + void Awake(){ + if (Instance == null) Instance = this; + + logger = BepInEx.Logging.Logger.CreateLogSource(modGUID); + logger.LogInfo($"Plugin {modName} has been added!"); + + MyConfig = new PluginConfig(Config); + + //MyConfig.SerializerTest(); + + ModCompability.GetActiveMods(); + ModCompability.ActivateActiveMods(); + + //harmony.PatchAll(typeof(MenuManagerPatch)); + harmony.PatchAll(typeof(InitPatch)); + harmony.PatchAll(typeof(RoundManagerPatch)); + harmony.PatchAll(typeof(LoadAssetsIntoLevelPatch)); + harmony.PatchAll(typeof(EnemyVentPatch)); + harmony.PatchAll(typeof(JesterAIPatch)); + harmony.PatchAll(typeof(PlayerControllerBPatch)); + + harmony.PatchAll(typeof(DoorLockPatch)); + harmony.PatchAll(typeof(ShotgunItemPatch)); + harmony.PatchAll(typeof(ShovelPatch)); + + //harmony.PatchAll(typeof(OptimizePatch)); + harmony.PatchAll(typeof(DoorwayConnectionPatch)); + harmony.PatchAll(typeof(GeneratePathPatch)); + harmony.PatchAll(typeof(PostProcessPatch)); + + harmony.PatchAll(typeof(PluginConfig)); + + SetupForNetcodePatcher(); + + Assets.LoadAssetBundle(); + + var dungeonMatchPropeties = ScriptableObject.CreateInstance(); + dungeonMatchPropeties.authorNames.Add(new StringWithRarity("Alice", 10)); + + /* + var itemLevelMatchProperties = ScriptableObject.CreateInstance(); + foreach(var item in Assets.items){ + item.stringWithRarity = new StringWithRarity("Alice", 0); + itemLevelMatchProperties.authorNames.Add(item.stringWithRarity); + } + */ + + var sdmLevelMatchProperties = ScriptableObject.CreateInstance(); + sdmLevelMatchProperties.planetNames.Add(new StringWithRarity("Dine", 300)); + sdmLevelMatchProperties.planetNames.Add(new StringWithRarity("Rend", 300)); + sdmLevelMatchProperties.planetNames.Add(new StringWithRarity("Titan", 69)); + + var extendedContent = new List(); + + var extendedDungeon = ScriptableObject.CreateInstance(); + extendedDungeon.name = "Scarlet Devil Mansion"; + extendedDungeon.DungeonName = "Scarlet Devil Mansion"; + extendedDungeon.DungeonFlow = Assets.dungeon; + extendedDungeon.FirstTimeDungeonAudio = Assets.entranceAudioClip; + extendedDungeon.LevelMatchingProperties = sdmLevelMatchProperties; + + extendedDungeon.DynamicDungeonSizeMinMax = new Vector2(1f, 2f); + extendedDungeon.DynamicDungeonSizeLerpRate = 0f; + + extendedDungeon.GenerateAutomaticConfigurationOptions = true; + + extendedContent.Add(extendedDungeon); + + // items + /* + foreach(var item in Assets.items){ + var extendedItem = ScriptableObject.CreateInstance(); + extendedItem.Item = item.item; + extendedItem.DungeonMatchingProperties = dungeonMatchPropeties; + //extendedItem.LevelMatchingProperties = itemLevelMatchProperties; + + extendedContent.Add(extendedItem); + } + */ + + var extendedMod = ExtendedMod.Create("Scarlet Devil Mansion", "Alice", extendedContent.ToArray()); + + PatchedContent.RegisterExtendedMod(extendedMod); + + // + + Assets.extendedMod = extendedMod; + Assets.dungeonExtended = extendedDungeon; + + foreach(var i in Assets.scrapItems){ + Items.RegisterScrap(i.item, 0, Levels.LevelTypes.None); + NetworkPrefabs.RegisterNetworkPrefab(i.item.spawnPrefab); + } + + extendedDungeon.dungeonEvents.onBeforeDungeonGenerate.AddListener(GeneratePathPatch.GeneratePatch); + DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.onMainEntranceTeleportSpawnedFunction); + } + + void SetupForNetcodePatcher(){ + var types = Assembly.GetExecutingAssembly().GetTypes(); + foreach (var type in types) { + //Debug.Log(type); + var methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + foreach (var method in methods) { + var attributes = method.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), false); + if (attributes.Length > 0) + { + method.Invoke(null, null); + } + + } + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/PluginConfig.cs b/ScarletMansion/ScarletMansion/PluginConfig.cs new file mode 100644 index 0000000..75e6e70 --- /dev/null +++ b/ScarletMansion/ScarletMansion/PluginConfig.cs @@ -0,0 +1,512 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Unity.Netcode; +using System.Runtime.Serialization.Formatters.Binary; +using System.IO; +using HarmonyLib; +using BepInEx; +using BepInEx.Configuration; +using BepInEx.Logging; +using UnityEngine; +using System.Reflection; + +namespace ScarletMansion { + + [Serializable] + public partial class PluginConfig : SyncedInstance{ + + public const string modPrefix = "SDMansion"; + public static readonly string requestMessage = $"{modPrefix}_OnRequestConfigSync"; + public static readonly string receiveMessage = $"{modPrefix}_OnReceieveConfigSync"; + + public const string dungeonWeightPrefix = "Dungeon Weight"; + public const string dungeonGenerationPrefix = "Dungeon Generation"; + public const string dungeonGenerationBoundingBoxPrefix = "DunGen Bounding Box"; + public const string dungeonGenerationMPathsPrefix = "DunGen Main Path"; + public const string dungeonGenerationBPathOnePrefix = "DunGen Branching Path 1"; + public const string dungeonGenerationBPathTwoPrefix = "DunGen Branching Path 2"; + public const string dungeonGenerationBPathThreePrefix = "DunGen Branching Path 3"; + public const string dungeonLootAndEnemiesPrefix = "Dungeon Loot And Enemies"; + public const string dungeonFeaturesPrefix = "Dungeon Features"; + public const string dungeonPaintingEventPrefix = "Dungeon Painting Event"; + public const string dungeonLightingPrefix = "Lighting"; + public const string presetsPrefix = "_Presets"; + + /* + // basic moons + public static ConfigEntryBundle dungeonSnowWeight = new ConfigEntryBundle( + dungeonWeightPrefix, + "SDM Rend/Dine Weight", + 300, + "The weight of the dungeon appearing for dine/rend. A higher weight means a higher chance.\n100 = 25% chance, 300 = 50% chance, 900 = 75%, 99999 = 99.99% chance", + null, + new AcceptableValueRange(0, 99999) + //new ConfigDefinition("Network General", "SDM Rend/Dine Weight") + ); + + public static ConfigEntryBundle dungeonTitanWeight = new ConfigEntryBundle( + dungeonWeightPrefix, + "SDM Titan Weight", + 69, + "The weight of the dungeon appearing for titan. A higher weight means a higher chance.\n69 = 16% chance, 150 = 29%, 400 = 52% chance, 99999 = 99.99% chance", + null, + new AcceptableValueRange(0, 99999) + //new ConfigDefinition("Network General", "SDM Titan Weight") + ); + + public int dungeonSnowWeightValue; + public int dungeonTitanWeightValue; + + // custom moons + public static ConfigEntryBundle customMaps = new ConfigEntryBundle( + dungeonWeightPrefix, + "Custom Moons", + string.Empty, + "The moon(s) that SDM can spawn on, in the form of a comma separated list of selectable level names and optionally a weight value by using an '@' and weight value after it (e.g. \"Titan@300,Dine,Rend@10,CUSTOM_MOON_NAME@9999\")\nUsing this config WILL override the default weights used above.\nThe name matching is lenient and should pick it up if you use the terminal name or internal mod name. If no rarity is specified, the default weight below is used.\nThe following strings: \"all\", \"vanilla\", \"modded\", \"paid\", \"free\" are dynamic presets which add the dungeon to that specified group (string must only contain one of these, or a manual moon name list).\n", + null + //new ConfigDefinition("Network Customizability", "Custom Moons") + ); + + public static ConfigEntryBundle customMapDefaultWeight = new ConfigEntryBundle( + dungeonWeightPrefix, + "Custom Moons Weight", + 300, + "The default weight of the dungeon appearing for custom moons. A higher weight means a higher chance.\n300 = 50% chance, 99999 = 99.99% chance", + null, + new AcceptableValueRange(0, 99999) + //new ConfigDefinition("Network Customizability", "Custom Moons Weight") + ); + + public string customMapsValue; + public int customMapDefaultWeightValue; + + */ + + /* + + // dungeon generation + public static ConfigEntryBundleMinMax dunGenMultiplier = new ConfigEntryBundleMinMax( + dungeonGenerationPrefix, + "Size Multiplier Min", + "Size Multiplier Max", + 1f, + 2f, + "The minimum allowed size of the dungeon.\nEach moon modifies the size of their dungeon starting from 1 (Experimentation) to 2.35 (Titan).\nPlease check the wiki for the list of map size multipliers for every moon.", + "The maximum allowed size of the dungeon.\nEach moon modifies the size of their dungeon starting from 1 (Experimentation) to 2.35 (Titan).\nPlease check the wiki for the list of map size multipliers for every moon.", + null, + new AcceptableValueRange(1f, 10f) + ); + + public FloatRange dunGenMultiplierValue = new FloatRange("size multiplier"); + + */ + + // bounding box + public static ConfigEntryBundle dunGenWidthBase = new ConfigEntryBundle( + dungeonGenerationBoundingBoxPrefix, + "Width Base", + 120, + "The width (left-to-right) of the dungeon's bounding box.\nThe dungeon is generated within this bounding box and cannot generate tiles outside of it.\nIncreasing this value will make the dungeon less compact but less likely to fail generation and vice versa.\nDecreasing this value too much can make it impossible for the dungeon to generate.", + null, + new AcceptableValueRange(40, 200) + ); + + public static ConfigEntryBundle dunGenLengthBase = new ConfigEntryBundle( + dungeonGenerationBoundingBoxPrefix, + "Length Base", + 80, + "The length (forward-to-back) of the dungeon's bounding box.\nThe dungeon is generated within this bounding box and cannot generate tiles outside of it.\nIncreasing this value will make the dungeon less compact but less likely to fail generation and vice versa.\nDecreasing this value too much can make it impossible for the dungeon to generate.", + null, + new AcceptableValueRange(40, 200) + ); + + public static ConfigEntryBundle dunGenWidthMultiFactor = new ConfigEntryBundle( + dungeonGenerationBoundingBoxPrefix, + "Width Multiplier Factor", + 0.5f, + "The width (left-to-right) of the dungeon's bounding box increases based on the dungeon's size. That additional width is multiplied by this value.\nThe exact formula is width = base + (base * (size - 1) * factor).\nIncreasing this value will make the dungeon less compact but less likely to fail generation on bigger moons.\nSetting this value to 0 means that the base width will be a constant value no matter how small or big the moon may be.", + null, + new AcceptableValueRange(0f, 4f) + ); + + public static ConfigEntryBundle dunGenLengthMultiFactor = new ConfigEntryBundle( + dungeonGenerationBoundingBoxPrefix, + "Length Multiplier Factor", + 0.3333333f, + "The length (forward-to-back) of the dungeon's bounding box increases based on the dungeon's size. That additional length is multiplied by this value.\nThe exact formula is length = base + (base * (size - 1) * factor).\nIncreasing this value will make the dungeon less compact but less likely to fail generation on bigger moons.\nSetting this value to 0 means that the base length will be a constant value no matter how small or big the moon may be.", + null, + new AcceptableValueRange(0f, 4f) + ); + + + public int dunGenWidthBaseValue; + public int dunGenLengthBaseValue; + public float dunGenWidthMultiFactorValue; + public float dunGenLengthMultiFactorValue; + + // main paths + public static ConfigEntryBundle mainPathCount = new ConfigEntryBundle( + dungeonGenerationMPathsPrefix, + "Main Path Count", + 3, + "The amount of main paths that the modified dungeon generation code must generate.\nDecreasing this value will make it much less likely to fail generation but will lower the length consistency of branching paths.\nSetting the value to 1 will revert back to the vanilla dungeon generation.", + null, + new AcceptableValueRange(1, 3) + ); + + private static readonly string _mainPathLengthPostMessage = "Decreasing this value will make the dungeon less likely to fail generation but will lower the length consistency of branching paths and vice versa.\nIncreasing this value too much can make it impossible for the dungeon to generate."; + + public static ConfigEntryBundleMinMax mainPathLength = new ConfigEntryBundleMinMax( + dungeonGenerationMPathsPrefix, + "Main Path Length Min", + "Main Path Length Max", + 4, + 5, + $"The minimum allowed length of the main path. This value is multiplied by the dungeon's size.\n\n{_mainPathLengthPostMessage}", + $"The maximum allowed length of the main path. This value is multiplied by the dungeon's size.\n\n{_mainPathLengthPostMessage}", + null, + new AcceptableValueRange(2, 20) + ); + + public int mainPathCountValue; + public IntRange mainPathLengthValue = new IntRange("main path length"); + + // branching path + private static string _branchPathBranchCountMessage(int section, string minmax, string middleSection) => $"The {minmax} amount of branching paths in Section {section} of the dungeon generation process.\n{middleSection}\n\n{_branchPathPostCountMessage}"; + private static string _branchPathBranchDepthMessage(int section, string minmax, string middleSection) => $"The {minmax} length of the branching paths in Section {section} of the dungeon generation process.\n{middleSection}\n\n{_branchPathPostDepthMessage}"; + + private static readonly string _branchPathSectionOneMessage = "Section 1 consists of the first 30% tiles of the main path (including the mayor entrance tile)."; + private static readonly string _branchPathSectionTwoMessage = "Section 2 consists of the middle tiles (30% - 70%) of the main path."; + private static readonly string _branchPathSectionThreeMessage = "Section 3 consists of the last 30% tiles of the main path."; + + private static readonly string _branchPathPostGenericMessage = "This is not an enforced number and the dungeon generation will proceed if it can't fit any tiles."; + private static readonly string _branchPathPostCountMessage = $"Each tile on the main path will try to generate a number of branching paths equal to this number. {_branchPathPostGenericMessage}"; + private static readonly string _branchPathPostDepthMessage = $"Each branching path will try to generate a number of connecting tiles equal to this number. {_branchPathPostGenericMessage}"; + + public static ConfigEntryBundleBranchingPath branchPathSectionOne = new ConfigEntryBundleBranchingPath( + dungeonGenerationBPathOnePrefix, 1, _branchPathSectionOneMessage, 6, 8, 3, 4 + ); + + public static ConfigEntryBundleBranchingPath branchPathSectionTwo = new ConfigEntryBundleBranchingPath( + dungeonGenerationBPathTwoPrefix, 2, _branchPathSectionTwoMessage, 2, 3, 1, 2 + ); + + public static ConfigEntryBundleBranchingPath branchPathSectionThree = new ConfigEntryBundleBranchingPath( + dungeonGenerationBPathThreePrefix, 3, _branchPathSectionThreeMessage, 1, 2, 0, 1 + ); + + public BranchingPathRange branchPathSectionOneValue = new BranchingPathRange("section one"); + public BranchingPathRange branchPathSectionTwoValue = new BranchingPathRange("section two"); + public BranchingPathRange branchPathSectionThreeValue = new BranchingPathRange("section three"); + + // loot + public static ConfigEntryBundle lootMultiplier = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "Loot Multiplier", + 1.4f, + "Multiplies the total amount of loot for the dungeon.", + null, + new AcceptableValueRange(0.25f, 4f) + ); + + public static ConfigEntryBundle mapHazardsMultiplier = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "Map Hazards Multiplier", + 1.6f, + "Multiplies the total amount of map hazards (landmines, turrets) for the dungeon.", + null, + new AcceptableValueRange(0.25f, 4f) + ); + + public static ConfigEntryBundle minIndoorEnemySpawnCount = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "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(0, 3) + ); + + public static ConfigEntryBundle crystalWeight = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "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(0, 999) + ); + + public static ConfigEntryBundle crystalBrokenWeight = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "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(0, 999) + ); + + public static ConfigEntryBundle knightWeightStealPercentage = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "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(0f, 1f) + ); + + public static ConfigEntryBundle knightWeightBase = new ConfigEntryBundle( + dungeonLootAndEnemiesPrefix, + "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(0, 999) + ); + + public float lootMultiplierValue; + public int crystalWeightValue; + public int crystalBrokenWeightValue; + + public float mapHazardsMultiplierValue; + public int minIndoorEnemySpawnCountValue; + public float knightWeightStealPercentageValue; + public int knightWeightBaseValue; + + // features + + public static ConfigEntryBundle shovelDamage = new ConfigEntryBundle( + 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(0, 100) + ); + + public static ConfigEntryBundle shotgunDamage = new ConfigEntryBundle( + 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(0, 100) + ); + + public static ConfigEntryBundle lockedDoorEnemyDamageMultiplier = new ConfigEntryBundle( + 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(0f, 1f) + ); + + public static ConfigEntryBundle useSDMFireExits = new ConfigEntryBundle( + 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 paintingValue = new ConfigEntryBundle( + dungeonPaintingEventPrefix, + "Painting Value", + 100, + "The scrap value of demonic paintings.", + null, + new AcceptableValueRange(0, 400) + ); + + public static ConfigEntryBundle paintingCount = new ConfigEntryBundle( + dungeonPaintingEventPrefix, + "Painting Count", + 2, + "The maximum amount of demonic paintings that spawn in the dungeon.", + null, + new AcceptableValueRange(0, 10) + ); + + public static ConfigEntryBundleMinMax paintingExtraLoot = new ConfigEntryBundleMinMax( + 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(0, 3) + ); + + public static ConfigEntryBundle paintingSpawnEnemy = new ConfigEntryBundle( + dungeonPaintingEventPrefix, + "Spawn Enemy", + true, + "If enabled, an enemy will spawn when the bedroom's painting event ends." + ); + + public static ConfigEntryBundle paintingEnemyList = new ConfigEntryBundle( + 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 \"knight@s1,crawler,nutcracker,springman,maskedplayerenemy,jester@s1\"." + ); + + public static ConfigEntryBundle facilityMeltdownActive = new ConfigEntryBundle( + 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 facilityMeltdownActiveValue; + + // lighting + public static ConfigEntryBundle hallwayLightsWeight = new ConfigEntryBundle( + dungeonLightingPrefix, + "Hallway Lights Weight", + 225, + "The weight for a hallway wall lamp to appear on its respective walls. With the default weight of 225 against the weight of the empty wall of 75, there is a 60% chance that a wall lamp will spawn. Increasing the weight to 675 will give you a 90% chance, 75 = 50%, and so on.", + null, + new AcceptableValueRange(0, 999) + ); + + public static ConfigEntryBundle ceilingLightsWeight = new ConfigEntryBundle( + dungeonLightingPrefix, + "Chandelier Lights Weight", + 225, + "The weight for a chandelier to appear on its respective ceilings. With the default weight of 225 against the weight of the empty ceiling of 75, there is a 75% chance that a chandelier will spawn. Increasing the weight to 675 will give you a 90%, 75 = 50%, and so on.", + null, + new AcceptableValueRange(0, 999) + ); + + public static ConfigEntryBundle lightsSpawnZeroWeight = new ConfigEntryBundle( + dungeonLightingPrefix, + "0 Lights Weight", + 2, + "The weight that none of the spawned light sources (desk lamps, wall lamps, chandeliers) in a given tile will emit light. With the default weight spread of [2, 7, 1], there is a 20% chance that even in a room filled with lamps, none of them will emit light.", + new ConfigEntryBundleExtraParameters(VerifyLightingValues, false), + new AcceptableValueRange(0, 99) + ); + + public static ConfigEntryBundle lightsSpawnOneWeight = new ConfigEntryBundle( + dungeonLightingPrefix, + "1 Light Weight", + 7, + "The weight that only 1 of the spawned light sources (desk lamps, wall lamps, chandeliers) in a given tile will emit light. With the default weight spread of [2, 7, 1], there is a 70% chance that even in a room filled with lamps, only one of them will emit light.", + new ConfigEntryBundleExtraParameters(VerifyLightingValues, false), + new AcceptableValueRange(0, 99) + ); + + public static ConfigEntryBundle lightsSpawnTwoWeight = new ConfigEntryBundle( + dungeonLightingPrefix, + "2 Lights Weight", + 1, + "The weight that only 2 of the spawned light sources (desk lamps, wall lamps, chandeliers) in a given tile will emit light. With the default weight spread of [2, 7, 1], there is a 10% chance that even in a room filled with lamps, only two of them will emit light.", + new ConfigEntryBundleExtraParameters(VerifyLightingValues, true), + new AcceptableValueRange(0, 99) + ); + + /* + public static ConfigEntryBundle lightsSpawnThreeWeight = new ConfigEntryBundle( + dungeonLightingPrefix, + "3 Lights Weight", + 1, + "The weight that only 3 of the spawned light sources (desk lamps, wall lamps, chandeliers) in a given tile will emit light. With the default weight spread of [2, 3, 4, 1], there is a 10% chance that even in a room filled with lamps, only two of them will emit light.", + new AcceptableValueRange(0, 99) + ); + */ + + public int hallwayLightsWeightValue; + public int ceilingLightsWeightValue; + public int lightsSpawnZeroWeightValue; + public int lightsSpawnOneWeightValue; + public int lightsSpawnTwoWeightValue; + //public int lightsSpawnThreeWeightValue; + + public static string GetEnumNames() where T: Enum { + var str = string.Empty; + var enums = Enum.GetNames(typeof(T)).Select(e => $"\"{e}\""); + return string.Join(", ", enums); + } + + public static ConfigEntryBundle lcDungeonGenerationPreset = new ConfigEntryBundle( + presetsPrefix, + "Preset Dungeon Generation", + PresetConfig.DungeonGeneration.Default, + $"Automatically updates config values based on this preset string. If you want to disable this, change this value to \"Custom\".\nPossible values are ({GetEnumNames()}).\nPlease use the mod LethalConfig for more information on these presets.", + new ConfigEntryBundleExtraParameters((c) => PresetConfig.UpdateFromPresetValue((int)lcDungeonGenerationPreset.config.Value, PresetConfig.dungeonGenerationChangeList), true) + ); + + public static ConfigEntryBundle lcDungeonLightingPreset = new ConfigEntryBundle( + presetsPrefix, + "Preset Dungeon Lighting", + PresetConfig.DungeonLighting.Default, + $"Automatically updates config values based on this preset string. If you want to disable this, change this value to \"Custom\".\nPossible values are ({GetEnumNames()}).\nPlease use the mod LethalConfig for more information on these presets.", + new ConfigEntryBundleExtraParameters((c) => PresetConfig.UpdateFromPresetValue((int)lcDungeonLightingPreset.config.Value, PresetConfig.dungeonLightingChangeList), true) + ); + + public void AutoGenerateOnChangeConfig(ConfigFile cfg, params object[] ignoreTargets){ + var fields = typeof(PluginConfig).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach(var p in fields){ + var value = p.GetValue(this); + if (ignoreTargets.Contains(value)) continue; + + var valueBundle = value as ConfigEntryBundleBase; + if (valueBundle != null){ + var targetFieldName = $"{p.Name}Value"; + var targetField = typeof(PluginConfig).GetField(targetFieldName, BindingFlags.Public | BindingFlags.Instance); + if (targetField == null) { + Plugin.logger.LogError($"Could not find field {targetFieldName}"); + continue; + } + valueBundle.Bind(cfg, this, new ConfigEntryBundleBase.FieldPropertyInfo(targetField), this); + } + } + } + + public PluginConfig(ConfigFile cfg){ + InitInstance(this); + AutoGenerateOnChangeConfig(cfg, lcDungeonGenerationPreset, lcDungeonLightingPreset); + + lcDungeonGenerationPreset.Bind(cfg, this); + lcDungeonLightingPreset.Bind(cfg, this); + } + + public static void VerifyLightingValues(PluginConfig instance){ + Plugin.logger.LogInfo("Verifying lightsSpawn 0/1/2 values"); + if (instance.lightsSpawnZeroWeightValue == 0f && instance.lightsSpawnOneWeightValue == 0f && instance.lightsSpawnTwoWeightValue == 0f){ + instance.lightsSpawnZeroWeightValue = 1; + } + } + + public override string ToString() { + var variables = typeof(PluginConfig).GetFields(BindingFlags.Public | BindingFlags.Instance); + return string.Join(", ", variables.Select(v => $"[{v.Name}]{v.GetValue(this)}")); + } + + public void SerializerTest(){ + var bytes = SerializeToBytes(Instance); + var copy = DeserializeFromBytes(bytes); + + Plugin.logger.LogInfo($"Inst: {Instance.ToString()}"); + Plugin.logger.LogInfo($"Copy: {copy.ToString()}"); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/PluginConfigClasses.cs b/ScarletMansion/ScarletMansion/PluginConfigClasses.cs new file mode 100644 index 0000000..bef32cf --- /dev/null +++ b/ScarletMansion/ScarletMansion/PluginConfigClasses.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Unity.Netcode; +using System.Runtime.Serialization.Formatters.Binary; +using System.IO; +using HarmonyLib; +using BepInEx; +using BepInEx.Configuration; +using BepInEx.Logging; +using UnityEngine; +using System.Reflection; + +namespace ScarletMansion { + public partial class PluginConfig : SyncedInstance { + + public abstract class NumberRange { + private string name; + [SerializeField] protected T _min; + [SerializeField] protected T _max; + + public T min { + get { + return _min; + } set { + _min = value; + Verify(); + } + } + + public T max { + get { + return _max; + } set { + _max = value; + Verify(); + } + } + + public NumberRange(string name){ + this.name = name; + _min = default; + _max = default; + } + + public virtual void Verify(){ + Plugin.logger.LogInfo($"Verifying min/max for {name}"); + } + + public override string ToString() { + return $"({min}, {max})"; + } + } + + [System.Serializable] + public class IntRange : NumberRange { + + public IntRange(string name) : base(name) { } + + public override void Verify() { + base.Verify(); + _max = Mathf.Max(_min, _max); + } + + public DunGen.IntRange GetDungenIntRange(){ + return new DunGen.IntRange(min, max); + } + } + + [System.Serializable] + public class FloatRange : NumberRange { + + public FloatRange(string name) : base(name) { } + + public override void Verify() { + base.Verify(); + _max = Mathf.Max(_min, _max); + } + + public DunGen.FloatRange GetDungenFloatRange(){ + return new DunGen.FloatRange(min, max); + } + } + + [System.Serializable] + public class BranchingPathRange { + private string name; + public IntRange count; + public IntRange depth; + + public BranchingPathRange(string name){ + this.name = name; + count = new IntRange($"{name} count"); + depth = new IntRange($"{name} depth"); + } + + public void UpdateValues(DunGen.DungeonArchetype archetype){ + archetype.BranchCount = new DunGen.IntRange(count.min, count.max); + archetype.BranchingDepth = new DunGen.IntRange(depth.min, depth.max); + } + + public override string ToString() { + return $"{count.ToString()}+{depth.ToString()}"; + } + } + + public abstract class ConfigEntryBundleBase { + + public class FieldPropertyInfo { + public FieldInfo fieldInfo; + public PropertyInfo propertyInfo; + + public FieldPropertyInfo(FieldInfo fieldInfo){ + this.fieldInfo = fieldInfo; + } + + public FieldPropertyInfo(PropertyInfo propertyInfo){ + this.propertyInfo = propertyInfo; + } + + public void SetValue(object obj, object value){ + if (fieldInfo != null) fieldInfo.SetValue(obj, value); + if (propertyInfo != null) propertyInfo.SetValue(obj, value); + } + + public object GetValue(object obj){ + if (fieldInfo != null) return fieldInfo.GetValue(obj); + if (propertyInfo != null) return propertyInfo.GetValue(obj); + return null; + } + + public Type GetFieldPropertyType(){ + if (fieldInfo != null) return fieldInfo.FieldType; + if (propertyInfo != null) return propertyInfo.PropertyType; + return null; + } + } + + public abstract ConfigEntryBase[] GetConfigs(); + public abstract void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget); + + } + + public class ConfigEntryBundleExtraParameters { + public Action onSettingsChanged; + public bool callOnBind; + + public ConfigEntryBundleExtraParameters(Action onSettingsChanged, bool callOnBind){ + this.onSettingsChanged = onSettingsChanged; + this.callOnBind = callOnBind; + } + + public void CallAction(PluginConfig instance, bool isBind){ + if (isBind && !callOnBind) return; + onSettingsChanged?.Invoke(instance); + } + } + + public class ConfigEntryBundle : ConfigEntryBundleBase { + public ConfigEntry config; + public ConfigDefinition definition; + public T defaultValue; + public ConfigDescription description; + public ConfigEntryBundleExtraParameters extra; + + public ConfigEntryBundle(string section, string key, T dvalue, string desc, ConfigEntryBundleExtraParameters extra = null, AcceptableValueBase valuebase = null){ + definition = new ConfigDefinition(section, key); + defaultValue = dvalue; + description = new ConfigDescription(desc, valuebase); + + if (extra == null) extra = new ConfigEntryBundleExtraParameters(null, false); + this.extra = extra; + } + + public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) { + config = cfg.Bind(definition, defaultValue, description); + config.SettingChanged += (obj, args) => { + Plugin.logger.LogInfo($"Config settings {config.Definition.Key} changed"); + memberInfo.SetValue(memberTarget, config.Value); + extra.CallAction(instance, false); + }; + + memberInfo.SetValue(memberTarget, config.Value); + extra.CallAction(instance, true); + } + + public void Bind(ConfigFile cfg, PluginConfig instance){ + config = cfg.Bind(definition, defaultValue, description); + config.SettingChanged += (obj, args) => { + Plugin.logger.LogInfo($"Config settings {config.Definition.Key} changed"); + extra.CallAction(instance, false); + }; + + extra.CallAction(instance, true); + } + + public override ConfigEntryBase[] GetConfigs() { + return new ConfigEntryBase[] { config }; + } + + } + + public class ConfigEntryBundleMinMax : ConfigEntryBundleBase { + public ConfigEntryBundle min; + public ConfigEntryBundle max; + + public ConfigEntryBundleMinMax(string section, string keyMin, string keyMax, T defaultValueMin, T defaultValueMax, string descMin, string descMax, ConfigEntryBundleExtraParameters extra = null, AcceptableValueBase valuebase = null){ + min = new ConfigEntryBundle(section, keyMin, defaultValueMin, descMin, extra, valuebase); + max = new ConfigEntryBundle(section, keyMax, defaultValueMax, descMax, extra, valuebase); + } + + public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) { + var type = memberInfo.GetFieldPropertyType(); + var minProp = type.GetProperty("min", BindingFlags.Public | BindingFlags.Instance); + var maxProp = type.GetProperty("max", BindingFlags.Public | BindingFlags.Instance); + min.Bind(cfg, instance, new FieldPropertyInfo(minProp), memberInfo.GetValue(memberTarget)); + max.Bind(cfg, instance, new FieldPropertyInfo(maxProp), memberInfo.GetValue(memberTarget)); + } + + public override ConfigEntryBase[] GetConfigs() { + return new ConfigEntryBase[] { min.config, max.config }; + } + } + + public class ConfigEntryBundleBranchingPath : ConfigEntryBundleBase { + public ConfigEntryBundleMinMax count; + public ConfigEntryBundleMinMax depth; + + public ConfigEntryBundleBranchingPath(string section, int sectionId, string extraMessage, int countMinValue, int countMaxValue, int depthMinValue, int depthMaxValue, ConfigEntryBundleExtraParameters extra = null){ + count = new ConfigEntryBundleMinMax( + section, + $"Section {sectionId} Count Min", + $"Section {sectionId} Count Max", + countMinValue, + countMaxValue, + _branchPathBranchCountMessage(sectionId, "minimum", extraMessage), + _branchPathBranchCountMessage(sectionId, "maximum", extraMessage), + extra, + new AcceptableValueRange(0, 20) + ); + + depth = new ConfigEntryBundleMinMax( + section, + $"Section {sectionId} Depth Min", + $"Section {sectionId} Depth Max", + depthMinValue, + depthMaxValue, + _branchPathBranchDepthMessage(sectionId, "minimum", extraMessage), + _branchPathBranchDepthMessage(sectionId, "maximum", extraMessage), + extra, + new AcceptableValueRange(0, 20) + ); + } + + public override void Bind(ConfigFile cfg, PluginConfig instance, FieldPropertyInfo memberInfo, object memberTarget) { + var type = memberInfo.GetFieldPropertyType(); + var countField = type.GetField("count", BindingFlags.Public | BindingFlags.Instance); + var depthField = type.GetField("depth", BindingFlags.Public | BindingFlags.Instance); + count.Bind(cfg, instance, new FieldPropertyInfo(countField), memberInfo.GetValue(memberTarget)); + depth.Bind(cfg, instance, new FieldPropertyInfo(depthField), memberInfo.GetValue(memberTarget)); + } + + public override ConfigEntryBase[] GetConfigs() { + return new ConfigEntryBase[] { count.min.config, count.max.config, depth.min.config, depth.max.config }; + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/PluginConfigNetwork.cs b/ScarletMansion/ScarletMansion/PluginConfigNetwork.cs new file mode 100644 index 0000000..84c0105 --- /dev/null +++ b/ScarletMansion/ScarletMansion/PluginConfigNetwork.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Unity.Netcode; +using System.Runtime.Serialization.Formatters.Binary; +using System.IO; +using HarmonyLib; +using BepInEx; +using BepInEx.Configuration; +using BepInEx.Logging; +using UnityEngine; +using System.Reflection; + +namespace ScarletMansion { + public partial class PluginConfig : SyncedInstance { + + public static void RequestSync(){ + if (!IsClient) return; + using (var stream = new FastBufferWriter(IntSize, Unity.Collections.Allocator.Temp)){ + SendMessage(stream, requestMessage); + } + } + + public static void OnRequestSync(ulong clientId, FastBufferReader _){ + if (!IsHost) return; + logger.LogInfo($"Config sync request received from client: {clientId}"); + + var array = SerializeToBytes(Instance); + var value = array.Length; + using (var stream = new FastBufferWriter(value + IntSize, Unity.Collections.Allocator.Temp)){ + try { + stream.WriteValueSafe(value, default(FastBufferWriter.ForPrimitives)); + stream.WriteBytesSafe(array); + + SendMessage(stream, receiveMessage, clientId); + } catch (Exception e){ + logger.LogError($"Error occurred syncing config with client: {clientId}\n{e}"); + } + } + } + + public static void OnReceiveSync(ulong _, FastBufferReader reader){ + if (!reader.TryBeginRead(IntSize)){ + logger.LogError("Config sync error: Could not begin reading buffer."); + return; + } + + reader.ReadValueSafe(out int val, default(FastBufferWriter.ForPrimitives)); + if (!reader.TryBeginRead(val)){ + logger.LogError("Config sync error: Host could not sync."); + return; + } + + var data = new byte[val]; + reader.ReadBytesSafe(ref data, val); + SyncInstance(data); + logger.LogInfo("Successfully synced config with host."); + } + + [HarmonyPatch(typeof(GameNetcodeStuff.PlayerControllerB), "ConnectClientToPlayerObject")] + [HarmonyPostfix] + public static void InitializeLocalPlayer(){ + if (IsHost){ + MessageManager.RegisterNamedMessageHandler(requestMessage, OnRequestSync); + Synced = true; + return; + } + + Synced = false; + MessageManager.RegisterNamedMessageHandler(receiveMessage, OnReceiveSync); + RequestSync(); + } + + [HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")] + [HarmonyPostfix] + public static void PlayerLeave(){ + RevertSync(); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/PresetConfig.cs b/ScarletMansion/ScarletMansion/PresetConfig.cs new file mode 100644 index 0000000..5d9312d --- /dev/null +++ b/ScarletMansion/ScarletMansion/PresetConfig.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using BepInEx.Configuration; + +namespace ScarletMansion { + public class PresetConfig { + + public enum DungeonGeneration { + Custom, + Default, + Small, + MoreLoot, + BitMoreLoot, + Vanilla + } + + public static ChangeList customChangeList = new ChangeList("Custom", "Disables auto loading of preset config values."); + + public static ChangeList defaultGeneration = new ChangeList( + "Default", + "The default generation values. Intended for lobbies with 3+ players.", + + //new ChangeMinMaxFloat ( PluginConfig.dunGenMultiplier ), + new ChangeInt ( PluginConfig.dunGenWidthBase ), + new ChangeInt ( PluginConfig.dunGenLengthBase ), + new ChangeFloat ( PluginConfig.dunGenWidthMultiFactor ), + new ChangeFloat ( PluginConfig.dunGenLengthMultiFactor ), + + new ChangeInt ( PluginConfig.mainPathCount ), + new ChangeMinMaxInt ( PluginConfig.mainPathLength ), + + new ChangeBranchingPath( PluginConfig.branchPathSectionOne ), + new ChangeBranchingPath( PluginConfig.branchPathSectionTwo ), + new ChangeBranchingPath( PluginConfig.branchPathSectionThree ), + + new ChangeFloat ( PluginConfig.lootMultiplier ), + new ChangeInt ( PluginConfig.paintingCount ), + new ChangeMinMaxInt( PluginConfig.paintingExtraLoot ), + + new ChangeFloat ( PluginConfig.mapHazardsMultiplier ), + new ChangeInt ( PluginConfig.minIndoorEnemySpawnCount ) + + ); + + public static ChangeList smallGeneration = new ChangeList( + "Small", + "A smaller variation of the default generation values. Intended for lobbies with 1-3 players.", + defaultGeneration, + + new ChangeInt ( PluginConfig.mainPathCount, 2 ), + /* + new ChangeBranchingPath( PluginConfig.branchPathSectionOne, int.MaxValue, int.MaxValue, 3, 4 ), + new ChangeBranchingPath( PluginConfig.branchPathSectionTwo, int.MaxValue, int.MaxValue, 1, 2 ), + new ChangeBranchingPath( PluginConfig.branchPathSectionThree, int.MaxValue, int.MaxValue, 0, 1 ), + */ + new ChangeFloat ( PluginConfig.lootMultiplier, 1.2f ), + new ChangeFloat ( PluginConfig.mapHazardsMultiplier, 1.3f ) + + ); + + public static ChangeList moreLootGeneration = new ChangeList( + "More Loot, More Danger", + "Increases the amount of loot, map hazards, and starting enemies in the mansion. Intended for lobbies with 3+ players.", + defaultGeneration, + + new ChangeFloat ( PluginConfig.lootMultiplier, 1.8f ), + new ChangeInt ( PluginConfig.paintingCount, 3 ), + new ChangeMinMaxInt( PluginConfig.paintingExtraLoot, 1 , 3 ), + + new ChangeFloat ( PluginConfig.mapHazardsMultiplier, 2.2f ), + new ChangeInt ( PluginConfig.minIndoorEnemySpawnCount, 2 ) + + ); + + public static ChangeList bitMoreLootGeneration = new ChangeList( + "Bit More Loot, Bit More Danger", + "Increases the amount of loot, map hazards, and starting enemies in the mansion a bit. Intended for lobbies with 1-3 players.", + smallGeneration, + + new ChangeFloat ( PluginConfig.lootMultiplier, 1.5f ), + new ChangeInt ( PluginConfig.paintingCount, 3 ), + new ChangeMinMaxInt( PluginConfig.paintingExtraLoot, 1 , 3 ), + + new ChangeFloat ( PluginConfig.mapHazardsMultiplier, 1.75f ), + new ChangeInt ( PluginConfig.minIndoorEnemySpawnCount, 2 ) + + ); + + public static ChangeList vanillaGeneration = new ChangeList( + "Vanilla", + "Changes the dungeon generation to match closer to a vanilla dungeon.", + defaultGeneration, + + //new ChangeMinMaxFloat ( PluginConfig.dunGenMultiplier, float.MaxValue, 10f ), + new ChangeInt ( PluginConfig.dunGenWidthBase, 200 ), + new ChangeInt ( PluginConfig.dunGenLengthBase, 200 ), + new ChangeFloat ( PluginConfig.dunGenWidthMultiFactor, 4f ), + new ChangeFloat ( PluginConfig.dunGenLengthMultiFactor, 4f ), + + new ChangeInt ( PluginConfig.mainPathCount, 1 ), + new ChangeMinMaxInt ( PluginConfig.mainPathLength, 6, 9 ), + + new ChangeFloat ( PluginConfig.lootMultiplier, 1f ), + + new ChangeFloat ( PluginConfig.mapHazardsMultiplier, 1f ), + new ChangeInt ( PluginConfig.minIndoorEnemySpawnCount, 0 ) + ); + + public enum DungeonLighting { + Custom, + Default, + Bright, + Dark + } + + // 2, 7, 1 + public static ChangeList defaultLighting = new ChangeList( + "Default", + "The default lighting values.", + new ChangeInt ( PluginConfig.hallwayLightsWeight ), + new ChangeInt ( PluginConfig.ceilingLightsWeight ), + new ChangeInt ( PluginConfig.lightsSpawnZeroWeight ), + new ChangeInt ( PluginConfig.lightsSpawnOneWeight ), + new ChangeInt ( PluginConfig.lightsSpawnTwoWeight ) + //new ChangeInt ( PluginConfig.lightsSpawnThreeWeight ) + ); + + public static ChangeList brightLighting = new ChangeList( + "Bright", + "Makes light sources much more common.", + new ChangeInt ( PluginConfig.hallwayLightsWeight, 675 ), + new ChangeInt ( PluginConfig.ceilingLightsWeight, 675 ), + new ChangeInt ( PluginConfig.lightsSpawnZeroWeight, 1 ), + new ChangeInt ( PluginConfig.lightsSpawnOneWeight, 6 ), + new ChangeInt ( PluginConfig.lightsSpawnTwoWeight, 3 ) + //new ChangeInt ( PluginConfig.lightsSpawnThreeWeight, 2 ) + ); + + public static ChangeList darkLighting = new ChangeList( + "Dark", + "Makes light sources much less common.", + new ChangeInt ( PluginConfig.hallwayLightsWeight, 75 ), + new ChangeInt ( PluginConfig.ceilingLightsWeight, 75 ), + new ChangeInt ( PluginConfig.lightsSpawnZeroWeight, 3 ), + new ChangeInt ( PluginConfig.lightsSpawnOneWeight, 7 ), + new ChangeInt ( PluginConfig.lightsSpawnTwoWeight, 0 ) + //new ChangeInt ( PluginConfig.lightsSpawnThreeWeight, 0 ) + ); + + public static List dungeonGenerationChangeList = new List() { + customChangeList, + defaultGeneration, + smallGeneration, + moreLootGeneration, + bitMoreLootGeneration, + vanillaGeneration + }; + + public static List dungeonLightingChangeList = new List() { + customChangeList, + defaultLighting, + brightLighting, + darkLighting + }; + + public static void UpdateFromPresetValue(int value, List changeList){ + if (value >= 0 && value < changeList.Count) { + var target = changeList[value]; + target.UpdateChanges(); + } + } + + public class ChangeList { + + public string name; + public string description; + public Change[] changes; + + public ChangeList(string name, string description, params Change[] changes){ + this.name = name; + this.description = description; + this.changes = changes; + } + + public ChangeList(string name, string description, ChangeList baseList, params Change[] changes){ + this.name = name; + this.description = description; + + var newChanges = new List(); + foreach(var c in baseList.changes){ + newChanges.Add(c); + } + + foreach(var c in changes){ + var added = false; + for(var i = 0; i < newChanges.Count; ++i) { + if (newChanges[i].IsEqual(c)){ + newChanges[i] = c; + added = true; + break; + } + } + + if (!added) newChanges.Add(c); + } + + this.changes = newChanges.ToArray(); + } + + public void UpdateChanges(){ + Plugin.logger.LogInfo($"Updating config values with preset {name}"); + foreach(var c in changes){ + c.Update(); + } + Plugin.Instance.Config.Save(); + Plugin.Instance.Config.Reload(); + } + + public override string ToString() { + var str = string.Empty; + foreach(var c in changes){ + str += c.ToString() + "\n"; + } + + if (string.IsNullOrWhiteSpace(str)){ + str = "No changes.\n"; + } + + return str; + } + } + + public abstract class Change { + public abstract void Update(); + public abstract bool IsEqual(Change target); + } + + public abstract class ChangeType : Change { + public PluginConfig.ConfigEntryBundle configBundle; + public ConfigEntry configEntry => configBundle.config; + public T value; + + public ChangeType(PluginConfig.ConfigEntryBundle configBundle){ + this.configBundle = configBundle; + this.value = configBundle.defaultValue; + } + + public ChangeType(PluginConfig.ConfigEntryBundle configBundle, T value){ + this.configBundle = configBundle; + this.value = value; + } + + public override void Update(){ + configEntry.BoxedValue = value; + } + + public override bool IsEqual(Change obj) { + var target = obj as ChangeType; + if (target != null) return configEntry.Definition == target.configEntry.Definition; + return false; + } + + public override string ToString() { + return $"{configBundle.definition.Key}: {value}"; + } + } + + public class ChangeFloat : ChangeType { + public ChangeFloat(PluginConfig.ConfigEntryBundle configEntry) : base (configEntry){ } + public ChangeFloat(PluginConfig.ConfigEntryBundle configEntry, float value) : base (configEntry, value){ } + } + + public class ChangeInt : ChangeType { + public ChangeInt(PluginConfig.ConfigEntryBundle configEntry) : base (configEntry){ } + public ChangeInt(PluginConfig.ConfigEntryBundle configEntry, int value) : base (configEntry, value){ } + } + + public class ChangeMinMaxInt : Change { + public ChangeInt min; + public ChangeInt max; + + public ChangeMinMaxInt(PluginConfig.ConfigEntryBundleMinMax configBundle){ + min = new ChangeInt(configBundle.min); + max = new ChangeInt(configBundle.max); + } + + public ChangeMinMaxInt(PluginConfig.ConfigEntryBundleMinMax configBundle, int minValue, int maxValue){ + min = new ChangeInt(configBundle.min, minValue == int.MaxValue ? configBundle.min.defaultValue : minValue); + max = new ChangeInt(configBundle.max, maxValue == int.MaxValue ? configBundle.max.defaultValue : maxValue); + } + + public override void Update() { + min.Update(); + max.Update(); + } + + public override bool IsEqual(Change obj) { + var target = obj as ChangeMinMaxInt; + if (target != null) return min.IsEqual(target.min) && max.IsEqual(target.max); + return false; + } + + public override string ToString() { + return $"{min.ToString()}\n{max.ToString()}"; + } + } + + public class ChangeMinMaxFloat : Change{ + public ChangeFloat min; + public ChangeFloat max; + + public ChangeMinMaxFloat(PluginConfig.ConfigEntryBundleMinMax configBundle){ + min = new ChangeFloat(configBundle.min); + max = new ChangeFloat(configBundle.max); + } + + public ChangeMinMaxFloat(PluginConfig.ConfigEntryBundleMinMax configBundle, float minValue, float maxValue){ + min = new ChangeFloat(configBundle.min, minValue == float.MaxValue ? configBundle.min.defaultValue : minValue); + max = new ChangeFloat(configBundle.max, maxValue == float.MaxValue ? configBundle.max.defaultValue : maxValue); + } + + public override void Update() { + min.Update(); + max.Update(); + } + + public override bool IsEqual(Change obj) { + var target = obj as ChangeMinMaxFloat; + if (target != null) return min.IsEqual(target.min) && max.IsEqual(target.max); + return false; + } + + public override string ToString() { + return $"{min.ToString()}\n{max.ToString()}"; + } + } + + public class ChangeBranchingPath : Change{ + public ChangeMinMaxInt count; + public ChangeMinMaxInt depth; + + public ChangeBranchingPath(PluginConfig.ConfigEntryBundleBranchingPath configBundle){ + count = new ChangeMinMaxInt(configBundle.count); + depth = new ChangeMinMaxInt(configBundle.depth); + } + + public ChangeBranchingPath(PluginConfig.ConfigEntryBundleBranchingPath configBundle, int countMin, int countMax, int depthMin, int depthMax){ + count = new ChangeMinMaxInt(configBundle.count, countMin, countMax); + depth = new ChangeMinMaxInt(configBundle.depth, depthMin, depthMax); + } + + public override void Update() { + count.Update(); + depth.Update(); + } + + public override bool IsEqual(Change obj) { + var target = obj as ChangeBranchingPath; + if (target != null) return count.IsEqual(target.count) && depth.IsEqual(target.depth); + return false; + } + + public override string ToString() { + return $"{count.ToString()}\n{depth.ToString()}"; + } + } + + } +} diff --git a/ScarletMansion/ScarletMansion/PrintProperties.cs b/ScarletMansion/ScarletMansion/PrintProperties.cs new file mode 100644 index 0000000..28496bc --- /dev/null +++ b/ScarletMansion/ScarletMansion/PrintProperties.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using System.Reflection; +using Unity.Netcode; +using DunGen.Graph; +using DunGen; +using System.Collections; + +namespace ScarletMansion { + public static class PrintProperties { + + public static void PrintDungeonFlow(DungeonFlow flow){ + if (flow == null) return; + PrintObject(flow, 0, 0, typeof(DungeonFlow), typeof(GameObject)); + } + + public static void PrintGameObject(GameObject go){ + if (go == null) return; + PrintObject(go, 0, 0); + } + + public static void PrintObject(object o, int index, int depth, params Type[] ignoreList){ + if (o == null) return; + if (depth > 7) return; + + if (IsPrimitive(o)) return; + + if (index > 0 && ignoreList.Contains(o.GetType())) return; + + if (IsIEnumerable(o)){ + PrintIEnumerable(o, index, depth, ignoreList); + return; + } + + var spacing = new string(' ', index); + Plugin.logger.LogInfo($"{spacing}[{o.ToString()}]"); + + var props = o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + foreach(var pi in props){ + + if (pi.GetIndexParameters().Length > 0){ + var count = 0; + while(true) { + try { + pi.GetValue(o, new object[] { count } ); + count++; + } catch (TargetInvocationException) { break; } + } + + for(var i = 0; i < count; ++i) { + var value = pi.GetValue(o, new object[] { i } ); + Plugin.logger.LogInfo($"{spacing}{pi.Name}[{i}]: {GetObjectString(value)}"); + PrintObject(value, index + 1, depth + 1, ignoreList); + } + + + } else { + var value = pi.GetValue(o); + Plugin.logger.LogInfo($"{spacing}{pi.Name}: {GetObjectString(value)}"); + PrintObject(value, index + 1, depth + 1, ignoreList); + } + + } + + var fields = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + foreach(var fi in fields){ + var value = fi.GetValue(o); + Plugin.logger.LogInfo($"{spacing}{fi.Name}: {GetObjectString(value)}"); + PrintObject(value, index + 1, depth + 1, ignoreList); + } + + if (o is GameObject){ + var gameobject = o as GameObject; + var comps = gameobject.GetComponents(); + Plugin.logger.LogInfo("--- Components --- "); + foreach(var c in comps){ + PrintObject(c, index, depth, ignoreList); + } + + Plugin.logger.LogInfo("--- Children --- "); + foreach(Transform t in gameobject.transform){ + PrintObject(t.gameObject, index + 1, depth + 1, ignoreList); + } + } + + + Plugin.logger.LogInfo(""); + } + + public static void PrintIEnumerable(object o, int index, int depth, params Type[] ignoreList){ + if (o == null) return; + var spacing = new string(' ', index); + + var list = o as IEnumerable; + var i = 0; + foreach(var item in list){ + Plugin.logger.LogInfo($"{spacing}[IEnumerable {i}]: {GetObjectString(item)}"); + PrintObject(item, index + 1, depth + 1, ignoreList); + i++; + } + + } + + public static bool IsIEnumerable(object o){ + return o is IEnumerable; + } + + public static bool IsPrimitive(object o){ + var type = o.GetType(); + return type.IsPrimitive || type == typeof(string) || type.IsEnum || Convert.GetTypeCode(type) != TypeCode.Object; + } + + public static string GetObjectString(object o){ + return o == null ? "NULL" : o.ToString(); + } + + } +} diff --git a/ScarletMansion/ScarletMansion/Properties/AssemblyInfo.cs b/ScarletMansion/ScarletMansion/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f00e06e --- /dev/null +++ b/ScarletMansion/ScarletMansion/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下を通して制御されます +// 制御されます。アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("ScarletMansion")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ScarletMansion")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("d7e169df-3f43-44b0-a300-c23b9aa44d48")] + +// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// リビジョン +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScarletMansion/ScarletMansion/ScarletMansion.csproj b/ScarletMansion/ScarletMansion/ScarletMansion.csproj new file mode 100644 index 0000000..06df543 --- /dev/null +++ b/ScarletMansion/ScarletMansion/ScarletMansion.csproj @@ -0,0 +1,261 @@ + + + + + Debug + AnyCPU + {D7E169DF-3F43-44B0-A300-C23B9AA44D48} + Library + Properties + ScarletMansion + ScarletMansion + v4.8 + 512 + true + + + true + embedded + false + bin\Debug\ + DEBUG;TRACE + prompt + 1 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\Libraries\0Harmony.dll + + + ..\..\..\Libraries\AdvancedCompany.dll + + + ..\..\..\Libraries\Assembly-CSharp.dll + + + ..\..\..\Libraries\Assembly-CSharp-firstpass.dll + + + ..\..\..\Libraries\Assembly-CSharp-publicized.dll + + + ..\..\..\Libraries\BepInEx.dll + + + ..\..\..\Libraries\BepInEx.Harmony.dll + + + ..\..\..\Libraries\FacilityMeltdown.dll + + + ..\..\..\Libraries\LethalConfig.dll + + + ..\..\..\Libraries\LethalLevelLoader.dll + + + ..\..\..\Libraries\LethalLib.dll + + + ..\..\..\Libraries\MoreCompany.dll + + + ..\..\..\Libraries\OdinSerializer.dll + + + ..\..\..\Libraries\ReservedItemSlotCore.dll + + + + + + + + + + ..\..\..\Libraries\Unity.AI.Navigation.dll + + + ..\..\..\Libraries\Unity.Collections.dll + + + ..\..\..\Libraries\Unity.InputSystem.dll + + + ..\..\..\Libraries\Unity.Netcode.Components.dll + + + ..\..\..\Libraries\Unity.Netcode.Runtime.dll + + + ..\..\..\Libraries\Unity.RenderPipelines.Core.Runtime.dll + + + ..\..\..\Libraries\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll + + + ..\..\..\Libraries\Unity.RenderPipelines.HighDefinition.Runtime.dll + + + ..\..\..\Libraries\Unity.TextMeshPro.dll + + + ..\..\..\Libraries\UnityEngine.dll + + + ..\..\..\Libraries\UnityEngine.AIModule.dll + + + ..\..\..\Libraries\UnityEngine.AnimationModule.dll + + + ..\..\..\Libraries\UnityEngine.AssetBundleModule.dll + + + ..\..\..\Libraries\UnityEngine.AudioModule.dll + + + ..\..\..\Libraries\UnityEngine.CoreModule.dll + + + ..\..\..\Libraries\UnityEngine.InputModule.dll + + + ..\..\..\Libraries\UnityEngine.JSONSerializeModule.dll + + + ..\..\..\Libraries\UnityEngine.ParticleSystemModule.dll + + + ..\..\..\Libraries\UnityEngine.PhysicsModule.dll + + + ..\..\..\Libraries\UnityEngine.UI.dll + + + ..\..\..\Libraries\UnityEngine.UIModule.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy "D:\Previous Computer\Desktop\LethalCompany Modding\Unity Template\Assets\AssetBundles\scarletmansion" "$(SolutionDir)\scarletmansion" + + + copy "$(TargetPath)" "C:\Users\Jose Garcia\AppData\Roaming\r2modmanPlus-local\LethalCompany\profiles\SDM Debug\BepInEx\plugins\Alice-ScarletDevilMansion\$(TargetName).dll" +copy "$(TargetPath)" "D:\Previous Computer\Desktop\LethalCompany Modding\NetcodePatcher\plugins\$(TargetName).dll" + + \ No newline at end of file diff --git a/ScarletMansion/ScarletMansion/SyncedInstance.cs b/ScarletMansion/ScarletMansion/SyncedInstance.cs new file mode 100644 index 0000000..b34187c --- /dev/null +++ b/ScarletMansion/ScarletMansion/SyncedInstance.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Unity.Netcode; +using OdinSerializer; +using System.IO; +using HarmonyLib; +using BepInEx.Configuration; +using BepInEx.Logging; +using UnityEngine; + +namespace ScarletMansion { + + [Serializable] + public class SyncedInstance { + internal static CustomMessagingManager MessageManager => NetworkManager.Singleton.CustomMessagingManager; + internal static bool IsClient => NetworkManager.Singleton.IsClient; + internal static bool IsHost => NetworkManager.Singleton.IsHost; + internal static ManualLogSource logger => Plugin.logger; + + [NonSerialized] + protected static int IntSize = 4; + + public static T Default { get; private set; } + public static T Instance { get; private set; } + + public static bool Synced { get; internal set; } + + protected void InitInstance(T instance) { + Default = instance; + Instance = instance; + + // Makes sure the size of an integer is correct for the current system. + // We use 4 by default as that's the size of an int on 32 and 64 bit systems. + IntSize = sizeof(int); + } + + internal static void SyncInstance(byte[] data) { + Instance = DeserializeFromBytes(data); + Synced = true; + } + + internal static void RevertSync() { + Instance = Default; + Synced = false; + } + + public static byte[] SerializeToBytes(T val) { + try { + return SerializationUtility.SerializeValue(val, DataFormat.Binary); + } + catch (Exception e) { + logger.LogError($"Error serializing instance: {e}"); + return null; + } + } + + public static T DeserializeFromBytes(byte[] data) { + try { + return SerializationUtility.DeserializeValue(data, DataFormat.Binary); + } catch (Exception e) { + logger.LogError($"Error deserializing instance: {e}"); + return default; + } + + } + + public static void SendMessage(FastBufferWriter stream, string title, ulong clientId = 0uL){ + var pastCapacity = stream.Capacity > 1300; + var flag = pastCapacity ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.Reliable; + if (pastCapacity){ + logger.LogInfo("Stream is past capacity. Sending fragmented message"); + } + + MessageManager.SendNamedMessage(title, clientId, stream, flag); + Plugin.logger.LogInfo($"Send message to client {clientId} with title {title} and flag {flag}"); + } + + } + +} diff --git a/ScarletMansion/ScarletMansion/TranspilerUtilities.cs b/ScarletMansion/ScarletMansion/TranspilerUtilities.cs new file mode 100644 index 0000000..8e50e01 --- /dev/null +++ b/ScarletMansion/ScarletMansion/TranspilerUtilities.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using HarmonyLib; +using GameNetcodeStuff; +using System.Reflection; +using System.Reflection.Emit; +using BepInEx.Logging; +using UnityEngine; + +namespace ScarletMansion { + + public class InjectionDictionary { + + public string name; + public List instructions; + public CodeInstruction[] injections; + + int counter; + + public InjectionDictionary(string name, MethodInfo methodInjection, params CodeInstruction[] instructions) { + this.name = name; + this.injections = new CodeInstruction[] { new CodeInstruction(OpCodes.Call, methodInjection) } ; + this.instructions = instructions.ToList(); + } + + public InjectionDictionary(string name, CodeInstruction[] codeInjections, params CodeInstruction[] instructions) { + this.name = name; + this.injections = codeInjections; + this.instructions = instructions.ToList(); + } + + public void ResetCounter(){ + counter = 0; + } + + public void AddCounter() { + counter++; + } + + public void Report(string debugFunction, int? expectedCounter){ + if (counter == 0) { + Plugin.logger.LogError($"{debugFunction} could not inject {name}. Probably scary"); + } else if (!expectedCounter.HasValue) { + Plugin.logger.LogInfo($"{debugFunction} inject {name} {counter} time(s)"); + } else if (expectedCounter.Value != counter){ + Plugin.logger.LogWarning($"{debugFunction} inject {name} {counter} time(s) (Expected {expectedCounter.Value}). Probably not an error but be warned"); + } + } + + } + + public class InstructionSequence { + + public static ManualLogSource logger => Plugin.logger; + + List> seq; + string name; + string extraErrorMessage; + int stage = 0; + bool completed = false; + bool single; + + public InstructionSequence(string name, bool single = true, string extraErrorMessage = default(string)){ + this.name = name; + this.single = single; + this.extraErrorMessage = extraErrorMessage; + seq = new List>(); + } + + public void Add(Func next){ + seq.Add(next); + } + + public void AddBasic(OpCode opcode){ + seq.Add((i) => i.opcode == opcode); + } + + public void AddBasic(OpCode opcode, object operand){ + seq.Add((i) => i.opcode == opcode && i.operand == operand); + } + + public void AddBasicWithAlternateMethodName(OpCode opcode, object operand, string methodName){ + seq.Add((i) => { + if (i.opcode == opcode && i.operand == operand) return true; + + var mth = i.operand as MethodInfo; + if (mth != null && mth.Name == methodName) return true; + + return false; + }); + + } + + public void AddSpecial(OpCode opcode, Func extra){ + seq.Add((i) => i.opcode == opcode && extra.Invoke(i)); + } + + public void AddQuickInjection(MethodInfo methodInfo){ + + } + + public bool VerifyStage(CodeInstruction current){ + var s = seq[stage]; + if (s.Invoke(current)) { + stage++; + } else { + stage = 0; + } + + if (stage >= seq.Count){ + + if (completed && single){ + throw new Exception($"Found multiple valid {name} instructions"); + } + + stage = 0; + completed = true; + return true; + } + + return false; + } + + public void ReportComplete(){ + if (completed == false){ + var errorM = string.IsNullOrWhiteSpace(extraErrorMessage) ? "BIG PROBLEM!" : extraErrorMessage; + logger.LogError($"HarmonyTranspiler for {name} has failed. {errorM}"); + } + } + + } + + class TranspilerUtilities { + + public static IEnumerable InjectMethod(IEnumerable instructions, InjectionDictionary injection, string debugFunction, int? expectedCounter = default){ + var targets = injection.instructions; + var codeInjections = injection.injections; + injection.ResetCounter(); + + foreach(var i in instructions){ + foreach(var t in targets){ + if (i.opcode == t.opcode && i.operand == t.operand){ + yield return i; + foreach(var c in codeInjections) yield return c; + injection.AddCounter(); + goto GoNext; + } + } + yield return i; + + GoNext:; + } + + injection.Report(debugFunction, expectedCounter); + } + + public static bool IsInstructionNearFloatValue(CodeInstruction instruction, float value){ + return Mathf.Abs((float)instruction.operand - value) < 0.1f; + } + + } +} diff --git a/ScarletMansion/ScarletMansion/Utility.cs b/ScarletMansion/ScarletMansion/Utility.cs new file mode 100644 index 0000000..1e83fb0 --- /dev/null +++ b/ScarletMansion/ScarletMansion/Utility.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using System.Reflection; +using HarmonyLib; +using Unity.Netcode; +using DunGen.Graph; +using DunGen; +using System.Collections; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; +using Key = UnityEngine.InputSystem.Key; +using ScarletMansion.GamePatch; +using System.Diagnostics; + +namespace ScarletMansion { + + public class MyOwnCoroutine : MonoBehaviour { + private static MyOwnCoroutine _Instance; + public static MyOwnCoroutine Instance { + get { + if (_Instance == null) { + var item = new GameObject("My Own Coroutine"); + _Instance = item.AddComponent(); + DontDestroyOnLoad(item); + } + return _Instance; + } + } + + + public static Coroutine AddCoroutine(IEnumerator c){ + return Instance.StartCoroutine(c); + } + + } + + public abstract class KeyboardDebug { + public string name; + public T current; + protected T min; + protected T max; + protected T delta; + + protected Key decrease; + protected Key increase; + + public KeyboardDebug(string name, T current, T min, T max, T delta, Key decrease, Key increase){ + this.name = name; + this.current = current; + this.min = min; + this.max = max; + this.delta = delta; + this.decrease = decrease; + this.increase = increase; + } + + public bool Update(){ + var direction = 0; + if (Utility.IfKeyPress(decrease)){ + direction = -1; + } else if (Utility.IfKeyPress(increase)){ + direction = 1; + } + + if (direction != 0){ + UpdateValue(direction); + return true; + } + + return false; + } + + protected abstract void UpdateValue(int direction); + + + } + + public class KeyboardFloatDebug : KeyboardDebug{ + + public KeyboardFloatDebug(string name, float current, float min, float max, float delta, Key decrease, Key increase) : + base(name, current, min, max, delta, decrease, increase) { } + + protected override void UpdateValue(int direction) { + current = Mathf.Clamp(current + direction * delta, min, max); + Plugin.logger.LogInfo($"{name}: {current}"); + } + } + + public class ActionList { + public string name; + public List<(string name, Action action)> actionList; + + public ActionList(string name){ + this.name = name; + actionList = new List<(string, Action)>(); + } + + public void AddEvent(string name, Action act){ + actionList.Add((name, act)); + } + + public void Call(){ + foreach(var pair in actionList){ + try { + pair.action.Invoke(); + } catch (Exception e) { + Plugin.logger.LogError($"Error with event {name}/{pair.name}"); + Plugin.logger.LogError(e.ToString()); + } + } + } + } + + public static class Utility { + + public class ParsedString { + public string main; + public string[] parameters; + + public ParsedString(string main, string[] parameters){ + this.main = main; + this.parameters = parameters; + } + + public ParsedString(string[] all){ + main = all[0]; + parameters = all.Skip(1).ToArray(); + } + + public string GetParameter(string startString){ + foreach(var p in parameters){ + if (!string.IsNullOrWhiteSpace(p) && p.StartsWith(startString)) return p; + } + return null; + } + } + + public static ParsedString[] ParseString(string str, string[] mainSep, string[] paraSep){ + var mainEntries = str.Split(mainSep, StringSplitOptions.RemoveEmptyEntries); + var result = new ParsedString[mainEntries.Length]; + for(var i = 0; i < mainEntries.Length; ++i){ + var miniEntries = mainEntries[i].Split(paraSep, StringSplitOptions.RemoveEmptyEntries); + result[i] = new ParsedString(miniEntries); + } + return result; + } + + public static int TryParseInt(string str, int defaultValue){ + if (!string.IsNullOrWhiteSpace(str)){ + var tryParse = int.TryParse(str, out var result); + if (tryParse) return result; + Plugin.logger.LogError($"Error trying to parse the int value in {str}. Proceeding string not an int? Using to default value {defaultValue}"); + return defaultValue; + } + Plugin.logger.LogError($"Error trying to parse the int value in {str}. No proceeding string? Using to default value {defaultValue}"); + return defaultValue; + } + + public static bool IfKeyPress(params Key[] keys){ + foreach(var k in keys){ + var dis = Keyboard.current[k].displayName; + var real = Keyboard.current.FindKeyOnCurrentKeyboardLayout(dis); + if (real.wasPressedThisFrame) return true; + } + return false; + } + + public static Transform FindChildRecurvisely(Transform t, string name){ + if (t.name == name) return t; + + foreach(Transform child in t){ + var downSearch = FindChildRecurvisely(child, name); + if (downSearch != null) return downSearch; + } + return null; + } + + public static List GetDungeonItems(){ + return LoadAssetsIntoLevelPatch.GetItems(RoundManager.Instance.currentLevel.spawnableScrap); + } + + public static int GetDungeonItemId(Item item, bool debug = false){ + var items = GetDungeonItems(); + + if (debug) { + for(var i = 0; i < items.Count; ++i){ + Plugin.logger.LogInfo($"{i}: {items[i].spawnableItem.itemName}"); + } + } + + for(var i = 0; i < items.Count; ++i){ + if (items[i].spawnableItem == item) return i; + } + return -1; + } + + public static int GetGlobalItemId(Item item, bool debug = false){ + var items = StartOfRound.Instance.allItemsList.itemsList; + + if (debug) { + for(var i = 0; i < items.Count; ++i){ + Plugin.logger.LogInfo($"{i}: {items[i].itemName}"); + } + } + + for(var i = 0; i < items.Count; ++i){ + if (items[i] == item) return i; + } + return -1; + } + + public static Tile GetClosestTileToPlayer(){ + var dungeon = GameObject.FindObjectOfType(); + var tiles = dungeon.Generator.CurrentDungeon.AllTiles; + var localPlayer = StartOfRound.Instance.localPlayerController.transform.position; + var closestTile = tiles.OrderBy(t => Vector3.SqrMagnitude(t.transform.position - localPlayer)).FirstOrDefault(); + return closestTile; + } + + public static void PrintCollisionMatrix(int layer){ + var strList = new List(); + for(var i = 0; i < 32; ++i) { + var result = !Physics.GetIgnoreLayerCollision(layer, i); + if (result) strList.Add($"({i}){LayerMask.LayerToName(i)}"); + } + var combine = string.Join(", ", strList); + Plugin.logger.LogInfo($"({layer}){LayerMask.LayerToName(layer)}: {combine}"); + } + + public static void PrintCodeInstructions(IEnumerable instructions){ + foreach(var i in instructions){ + var objString = i.operand != null ? $"[{i.operand.GetType().ToString()}] {i.operand.ToString()}" : "NULL"; + Plugin.logger.LogInfo($"{i.opcode.ToString()}: {objString}"); + } + } + + public static void DrawGizmoCircle(Transform transform, float radius, Color color){ + var matrix = Gizmos.matrix; + Gizmos.color = color; + Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, new Vector3(1f, 0.01f, 1f)); + Gizmos.DrawWireSphere(Vector3.zero, radius); + Gizmos.matrix = matrix; + } + + public static void Shuffle (RandomStream rng, T[] array) { + var n = array.Length; + while (n > 1) { + var k = rng.Next(n--); + var temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } + + public static void Shuffle (System.Random rng, T[] array) { + var n = array.Length; + while (n > 1) { + var k = rng.Next(n--); + var temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } + + public static MethodInfo GetGenericMethod(Type type, string methodName, Type genericType, BindingFlags flags){ + var methods = type.GetMethods(flags); + + var method = methods + .Where(m => m.Name == methodName) + .Where(m => { + var args = m.GetGenericArguments(); + return args.Length >= 1; + }) + .Select(m => m.MakeGenericMethod(genericType)) + .FirstOrDefault(); + return method; + } + + /* + + public static GameObject GetParentWithNetworkObject(GameObject g){ + var par = g.GetComponentInParent(); + return par ? par.gameObject : null; + } + + public static void PrintGameObjectToChildren(GameObject g, int index){ + PrintGameObject(g, index); + foreach(Transform child in g.transform){ + PrintGameObjectToChildren(child.gameObject, index + 1); + } + } + + public static void PrintGameObject(GameObject g, int index){ + var spacing = new string(' ', index); + Plugin.logger.LogInfo($"{spacing}[{g.name}]"); + Plugin.logger.LogInfo($"{spacing}{g.activeSelf}"); + Plugin.logger.LogInfo($"{spacing}{g.layer}"); + Plugin.logger.LogInfo($"{spacing}{g.tag}"); + + var comp = g.GetComponents(); + foreach(var c in comp){ + PrintObject(c, index, 0); + } + Plugin.logger.LogInfo(""); + } + + */ + + public static void PrintSpecialCaseObject(object c, int index){ + if (c == null) return; + var spacing = new string(' ', index + 1); + + if (c is InteractEvent) { + var list = c as InteractEvent; + for(var i = 0; i < list.GetPersistentEventCount(); i++){ + var call = list.GetPersistentListenerState(i); + Plugin.logger.LogInfo($"{spacing}CALL: {call}"); + } + } + + if (c is InteractEventFloat) { + var list = c as InteractEventFloat; + for(var i = 0; i < list.GetPersistentEventCount(); i++){ + var call = list.GetPersistentMethodName(i); + Plugin.logger.LogInfo($"{spacing}CALL: {call}"); + } + } + + if (c is BooleanEvent) { + var list = c as BooleanEvent; + for(var i = 0; i < list.GetPersistentEventCount(); i++){ + var call = list.GetPersistentMethodName(i); + Plugin.logger.LogInfo($"{spacing}CALL: {call}"); + } + } + + if (c is Material){ + var m = c as Material; + try { + var met = m.GetFloat("_Metallic"); + var smo = m.GetFloat("_Smoothness"); + Plugin.logger.LogInfo($"{spacing}METAL: {met}"); + Plugin.logger.LogInfo($"{spacing}SMOOTH: {smo}"); + } catch { + Plugin.logger.LogInfo($"{spacing}FAILED READING"); + } + } + + /* + if (c is RuntimeAnimatorController){ + var a = c as RuntimeAnimatorController; + var clips = a.animationClips; + for(var i = 0; i < clips.Length; ++i){ + var clip = clips[i]; + Plugin.logger.LogInfo($"{spacing}CLIP: {clip.name}"); + } + } + + if (c is IntRange){ + var a = c as IntRange; + Plugin.logger.LogInfo($"{spacing}[{a.Min} -> {a.Max}]"); + } + */ + + } + + } +} diff --git a/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.sln b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.sln new file mode 100644 index 0000000..4d96fa3 --- /dev/null +++ b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScarletMansionMimicsPatch", "ScarletMansionMimicsPatch\ScarletMansionMimicsPatch.csproj", "{6AAB73EC-489E-4BAA-8BD1-7CA944359CEC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6AAB73EC-489E-4BAA-8BD1-7CA944359CEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AAB73EC-489E-4BAA-8BD1-7CA944359CEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AAB73EC-489E-4BAA-8BD1-7CA944359CEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AAB73EC-489E-4BAA-8BD1-7CA944359CEC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B882D00A-7717-487D-A2CE-1452D76BFEB5} + EndGlobalSection +EndGlobal diff --git a/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Patch.cs b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Patch.cs new file mode 100644 index 0000000..cbeeac3 --- /dev/null +++ b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Patch.cs @@ -0,0 +1,115 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using BepInEx; +using HarmonyLib; +using Mimics; +using Mimics.API; +using System.Reflection; +using System.Reflection.Emit; +using DunGen; +using ScarletMansion.GamePatch.FixValues; +using UnityEngine.Events; +using GameNetcodeStuff; +using ScarletMansion.GamePatch.Components; + +namespace ScarletMansion.ModPatch { + + public class Patch { + + public Patch() { + MimicsAPI.GetAPI().RegisterMimicEventHandler(new SDMMimicEventHandler()); + Plugin.Instance.harmony.PatchAll(typeof(Patch)); + } + + public class SDMMimicEventHandler : MimicEventHandler + { + public override string ModGUID => Plugin.modGUID; + + public override bool IsMyInteriorLoaded => DunGenPatch.Patch.active; + + public override void OnMimicCreated(MimicDoor mimicDoor, Doorway doorway) { + var c = doorway.transform.parent.GetComponentInChildren(); + if (c != null) { + c.overrideConnector = true; + c.overrideNoDoorway = true; + } + + var fixfireexit = doorway.GetComponentInChildren(true); + fixfireexit.ForcefullyEnableDoorway(); + + var mimicDoorMesh = Utility.FindChildRecurvisely(mimicDoor.transform, "DoorMesh"); + var mimicFrame = Utility.FindChildRecurvisely(mimicDoor.transform, "Frame"); + var mimicLight = Utility.FindChildRecurvisely(mimicDoor.transform, "Light"); + + if (fixfireexit.EnableVanillaFireExit){ + FixDoorwayForVanillaFireExit(fixfireexit, mimicDoorMesh, mimicFrame, mimicLight); + } else { + FixDoorwayForSDMFireExit(fixfireexit, mimicDoorMesh, mimicFrame, mimicLight); + } + + Plugin.logger.LogInfo("Fixed a doorway for a mimic"); + } + + public override void OnMimicAttackStart(MimicDoor mimicDoor, PlayerControllerB playerToAttack) { + var doorway = mimicDoor.GetComponentInParent(); + var fireexit = doorway.GetComponentInChildren(); + fireexit?.DisableEnablePortal(); + } + } + + + private static bool colorBlindModeLastValue; + + [HarmonyPrefix] + [HarmonyPatch("Mimics.Mimics+RoundManagerPatch, Mimics", "SetExitIDsPatch")] + public static void SetExitIDsPatchPrefix(){ + colorBlindModeLastValue = Mimics.Mimics.ColorBlindMode; + } + + [HarmonyPostfix] + [HarmonyPatch("Mimics.Mimics+RoundManagerPatch, Mimics", "SetExitIDsPatch")] + public static void SetExitIDsPatchPostfix(){ + Mimics.Mimics.ColorBlindMode = colorBlindModeLastValue; + } + + private static void FixDoorwayForVanillaFireExit(FixFireExit fixFireExit, Transform mimicDoorMesh, Transform mimicFrame, Transform mimicLight){ + if (mimicDoorMesh != null) { + mimicDoorMesh.localPosition = new Vector3(-0.07f, 0f, 0.06f); + var sc = mimicDoorMesh.localScale; + sc.y = -0.0002f; + sc.z = -0.000161f; + mimicDoorMesh.localScale = sc; + } else { + Plugin.logger.LogWarning("Could not find DoorMesh in mimic gameobject. It will look weird but nothing should break"); + } + + if (mimicFrame != null) { + mimicFrame.localPosition = new Vector3(0f, -0.02f, 0.07f); + mimicFrame.localScale = new Vector3(1.08f, 1.008f, 1.02f); + } else { + Plugin.logger.LogWarning("Could not find Frame in mimic gameobject. It will look weird but nothing should break"); + } + } + + private static void FixDoorwayForSDMFireExit(FixFireExit fixFireExit, Transform mimicDoorMesh, Transform mimicFrame, Transform mimicLight){ + Mimics.Mimics.ColorBlindMode = true; + fixFireExit.ForcefullyEnableSDMRender(); + fixFireExit.sdmFireExit.enablePortalAnimation = true; + + if (mimicDoorMesh != null) mimicDoorMesh.gameObject.SetActive(false); + else Plugin.logger.LogWarning("Could not find DoorMesh in mimic gameobject. It will look weird but nothing should break"); + + if (mimicFrame != null) mimicFrame.gameObject.SetActive(false); + else Plugin.logger.LogWarning("Could not find Frame in mimic gameobject. It will look weird but nothing should break"); + + if (mimicLight != null) mimicLight.gameObject.SetActive(false); + else Plugin.logger.LogWarning("Could not find Light in mimic gameobject. It will look weird but nothing should break"); + } + + } +} diff --git a/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Properties/AssemblyInfo.cs b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a82ba11 --- /dev/null +++ b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下を通して制御されます +// 制御されます。アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("ScarletMansionMimicsPatch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ScarletMansionMimicsPatch")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("6aab73ec-489e-4baa-8bd1-7ca944359cec")] + +// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// リビジョン +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.csproj b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.csproj new file mode 100644 index 0000000..ba43b82 --- /dev/null +++ b/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch/ScarletMansionMimicsPatch.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {6AAB73EC-489E-4BAA-8BD1-7CA944359CEC} + Library + Properties + ScarletMansionMimicsPatch + ScarletMansionMimicsPatch + v4.8 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\Libraries\0Harmony.dll + + + ..\..\..\Libraries\Assembly-CSharp-publicized.dll + + + ..\..\..\Libraries\BepInEx.dll + + + ..\..\..\Libraries\Mimics.dll + + + ..\..\..\Libraries\ScarletMansion.dll + + + + + + + + + + + ..\..\..\Libraries\UnityEngine.dll + + + ..\..\..\Libraries\UnityEngine.CoreModule.dll + + + + + + + + \ No newline at end of file