Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Behemoths v1.0.0
Behemoths.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using Behemoths.Configuration; using Behemoths.Patches; using Behemoths.Services; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using ScalerCore; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("Vippy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+53b7c28de9b66c8d70e13cb917c1c900e3037d6a")] [assembly: AssemblyProduct("Behemoths")] [assembly: AssemblyTitle("Behemoths")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Behemoths { [BepInPlugin("Vippy.Behemoths", "Behemoths", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { internal static Plugin Instance { get; private set; } internal static ManualLogSource Logger => Instance.BaseLogger; private ManualLogSource BaseLogger => ((BaseUnityPlugin)this).Logger; internal Harmony? Harmony { get; set; } private void Awake() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_006a: Expected O, but got Unknown Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; PluginConfig.Init(((BaseUnityPlugin)this).Config); ((Component)this).gameObject.AddComponent<LiveTuning>().Setup(((BaseUnityPlugin)this).Config); if (Harmony == null) { Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); Harmony val2 = val; Harmony = val; } Harmony.PatchAll(); Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} loaded. LogLevel={PluginConfig.LoggingLevel.Value}"); } internal static void LogAlways(string msg) { Logger.LogInfo((object)msg); } internal static void LogInfo(string msg) { if (PluginConfig.LoggingLevel.Value >= VerbosityLevel.Debug) { Logger.LogInfo((object)msg); } } internal static void LogVerbose(string msg) { if (PluginConfig.LoggingLevel.Value >= VerbosityLevel.Verbose) { Logger.LogDebug((object)msg); } } } internal static class BuildInfo { public const string Version = "1.0.0"; } } namespace Behemoths.Services { internal class BehemothTremor : MonoBehaviour { private const float Interval = 0.45f; private const float MinSpeed = 1f; private const float NearDistance = 3f; private const float FarDistance = 14f; private const float Duration = 0.3f; private EnemyParent? _parent; private Rigidbody? _rb; private float _timer; public void Setup(EnemyParent parent, Rigidbody rb) { _parent = parent; _rb = rb; } private void Update() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_parent == (Object)null || (Object)(object)_rb == (Object)null || !_parent.Spawned) { return; } _timer -= Time.deltaTime; if (_timer > 0f) { return; } _timer = 0.45f; if (!PluginConfig.BossTremors.Value) { return; } Vector3 velocity = _rb.velocity; if (!(((Vector3)(ref velocity)).sqrMagnitude < 1f)) { GameDirector instance = GameDirector.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.CameraShake == (Object)null)) { float num = 1.2f * PluginConfig.BossSizeMultiplier.Value; instance.CameraShake.ShakeDistance(num, 3f, 14f, _rb.position, 0.3f); } } } } internal static class BossAnnouncer { public static bool Show(MoonUI mui) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || (Object)(object)mui.textTitle == (Object)null) { return false; } int num = Mathf.Clamp(instance.moonLevel, 0, instance.moons.Count); SetGraphic(mui.moonGraphicPreviousest, instance.MoonGetIcon(num - 2)); SetGraphic(mui.moonGraphicPrevious, instance.MoonGetIcon(num - 1)); SetGraphic(mui.moonGraphicCurrent, instance.MoonGetIcon(num)); SetGraphic(mui.moonGraphicNext, instance.MoonGetIcon(num + 1)); float value = PluginConfig.BossSizeMultiplier.Value; float value2 = PluginConfig.BossHealthMultiplier.Value; float value3 = PluginConfig.BossDamageMultiplier.Value; float value4 = PluginConfig.OrbValueMultiplier.Value; float value5 = PluginConfig.ValuableValueMultiplier.Value; List<MoonAttribute> list = new List<MoonAttribute> { new MoonAttribute { text = ((value > 1.01f) ? $"Behemoths roam this level, {value:0.#}x their size" : "Behemoths roam this level") }, new MoonAttribute { text = $"{value2:0.#}x the health, hits {value3:0.#}x as hard" }, new MoonAttribute { text = $"Their orbs are worth {value4:0.#}x" } }; if (value5 > 1.01f) { list.Add(new MoonAttribute { text = $"Every valuable is worth {value5:0.#}x more" }); } ((TMP_Text)mui.textTitle).text = "BOSS LEVEL"; mui.attributes = list; mui.SetState((State)1); instance.moonLevelChanged = false; Plugin.LogAlways("[Announce] BOSS LEVEL popup shown"); return true; } private static void SetGraphic(RawImage img, Texture tex) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)img == (Object)null)) { img.texture = tex; ((Graphic)img).color = (((Object)(object)tex != (Object)null) ? Color.white : Color.clear); } } } internal static class BossOdds { public static float ChanceFor(Level level) { float num = MapWeight(level); if (num <= 0f) { return 0f; } float value = PluginConfig.BaseChance.Value; float num2 = Mathf.Max(value, PluginConfig.MaxChance.Value); float num3 = Mathf.Lerp(value, num2, SemiFunc.RunGetDifficultyMultiplier1()); if (num3 >= 100f) { return 100f; } return Mathf.Clamp(num3 * num, 0f, 100f); } public static float MapWeight(Level level) { string name = (((Object)(object)level != (Object)null) ? ((Object)level).name : string.Empty); if (Has(name, "Manor")) { return PluginConfig.MapManor.Value; } if (Has(name, "Arctic")) { return PluginConfig.MapArctic.Value; } if (Has(name, "Wizard")) { return PluginConfig.MapWizard.Value; } if (Has(name, "Museum")) { return PluginConfig.MapMuseum.Value; } return PluginConfig.MapOther.Value; } public static string MapName(Level level) { if ((Object)(object)level == (Object)null) { return "unknown map"; } string name = ((Object)level).name; if (!name.StartsWith("Level - ", StringComparison.Ordinal)) { return name; } return name.Substring("Level - ".Length); } private static bool Has(string name, string token) { return name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; } } internal static class BossRoundService { private const string LastBossStat = "behemothsLastBossLevel"; private static readonly HashSet<Enemy> _bosses = new HashSet<Enemy>(); private static readonly HashSet<EnemyParent> _healthBoosted = new HashSet<EnemyParent>(); private static readonly Dictionary<EnemyParent, int> _baseHealth = new Dictionary<EnemyParent, int>(); private static readonly Color OrbColor = new Color(1f, 0.55f, 0.05f); public static bool IsBossLevel { get; private set; } public static bool LootBoosted { get; private set; } public static void ResetLevel() { ReachPatch.Forget(); IsBossLevel = false; _bosses.Clear(); _healthBoosted.Clear(); _baseHealth.Clear(); LootBoosted = false; } public static void DecideForLevel() { if (!SemiFunc.RunIsLevel()) { return; } if (!PluginConfig.Enabled.Value) { Plugin.LogAlways("[Decide] mod disabled, normal level"); return; } RunManager instance = RunManager.instance; int num = instance.levelsCompleted + 1; string text = BossOdds.MapName(instance.levelCurrent); if (num < PluginConfig.EarliestLevel.Value) { Plugin.LogAlways($"[Decide] level {num} ({text}) below earliest ({PluginConfig.EarliestLevel.Value}), normal level"); return; } int num2 = LastBossLevel(); int value = PluginConfig.CooldownLevels.Value; if (num2 > 0 && num - num2 <= value) { Plugin.LogAlways($"[Decide] level {num} ({text}) on cooldown ({num - num2} since the boss on level {num2}, cooldown {value}), normal level"); return; } float num3 = BossOdds.ChanceFor(instance.levelCurrent); float num4 = Random.Range(0f, 100f); if (num4 < num3) { IsBossLevel = true; StatsManager.instance.runStats["behemothsLastBossLevel"] = num; Plugin.LogAlways($"[Decide] level {num} ({text}) is a BOSS LEVEL (rolled {num4:F1} < {num3:F1})"); } else { Plugin.LogAlways($"[Decide] level {num} ({text}) normal (rolled {num4:F1} >= {num3:F1})"); } } private static int LastBossLevel() { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return 0; } if (!instance.runStats.TryGetValue("behemothsLastBossLevel", out var value)) { return 0; } return value; } public static void Promote(EnemyParent parent) { if (!IsBossLevel || (Object)(object)parent == (Object)null) { return; } Enemy enemy = parent.Enemy; if ((Object)(object)enemy == (Object)null) { return; } ApplyScale(enemy); ApplyResistance(enemy); if (_healthBoosted.Add(parent)) { if (enemy.HasHealth && (Object)(object)enemy.Health != (Object)null) { _baseHealth[parent] = enemy.Health.health; } ApplyHealth(enemy); ApplyTremor(parent, enemy); Plugin.LogInfo("[Promote] " + parent.enemyName + " is now a boss"); } _bosses.Add(enemy); } public static bool IsBoss(Enemy enemy) { if ((Object)(object)enemy != (Object)null) { return _bosses.Contains(enemy); } return false; } public static void Retune() { if (!IsBossLevel || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } int num = 0; foreach (KeyValuePair<EnemyParent, int> item in _baseHealth) { EnemyParent key = item.Key; if ((Object)(object)key == (Object)null) { continue; } Enemy enemy = key.Enemy; if (!((Object)(object)enemy == (Object)null)) { RetuneHealth(enemy, item.Value); if (((Component)enemy).gameObject.activeInHierarchy) { RetuneScale(enemy); ApplyResistance(enemy); num++; } } } Plugin.LogAlways($"[Tune] size {PluginConfig.BossSizeMultiplier.Value:0.##} collider {PluginConfig.BossColliderCap.Value:0.##} height {PluginConfig.BossHeightCap.Value:0.##} width {PluginConfig.BossWidthCap.Value:0.##} health x{PluginConfig.BossHealthMultiplier.Value:0.##} resist {PluginConfig.BossDamageResistance.Value:0.##} damage x{PluginConfig.BossDamageMultiplier.Value:0.##}: {num} live boss(es) updated"); } private static void RetuneScale(Enemy enemy) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (!enemy.HasRigidbody || (Object)(object)enemy.Rigidbody == (Object)null) { return; } GameObject gameObject = ((Component)enemy.Rigidbody).gameObject; ScaleController controller = ScaleManager.GetController(gameObject); if ((Object)(object)controller == (Object)null) { return; } ScaleOptions val = BuildOptions(); if (val.Factor <= 1f) { if (controller.IsScaled) { ScaleManager.ForceRestore(gameObject); } } else if (!controller.IsScaled) { ScaleManager.ApplyIfNotScaled(gameObject, val); } else if (Mathf.Approximately(controller.CurrentOptions.Factor, val.Factor)) { ScaleManager.ForceUpdateOptions(gameObject, val); } else { ScaleManager.ForceApply(gameObject, val); } } private static void RetuneHealth(Enemy enemy, int baseHealth) { if (enemy.HasHealth && !((Object)(object)enemy.Health == (Object)null) && baseHealth > 0 && !KeepsNormalHealth(enemy) && !enemy.Health.dead) { EnemyHealth health = enemy.Health; int num = BossMaxHealth(baseHealth); if (num != health.health) { float num2 = ((health.health > 0) ? ((float)health.healthCurrent / (float)health.health) : 1f); health.health = num; health.healthCurrent = Mathf.Clamp(Mathf.RoundToInt((float)num * num2), 1, num); } } } public static int BoostHit(int damage, int maxHealth) { float value = PluginConfig.BossDamageMultiplier.Value; if (damage <= 0 || value <= 1f) { return damage; } int num = Mathf.RoundToInt((float)damage * value); float value2 = PluginConfig.BossHitCap.Value; if (value2 < 100f) { int num2 = Mathf.RoundToInt((float)maxHealth * value2 / 100f); num = Mathf.Min(num, Mathf.Max(damage, num2)); } return num; } public static void BoostLevelValuables() { if (!IsBossLevel || LootBoosted) { return; } LootBoosted = true; float value = PluginConfig.ValuableValueMultiplier.Value; if (value == 1f) { return; } int num = 0; float num2 = 0f; ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>(); foreach (ValuableObject val in array) { if (!((Object)(object)val == (Object)null) && !IsEnemyOrb(val) && val.dollarValueSet) { float dollarValueCurrent = val.dollarValueCurrent; float num3 = Mathf.Round(dollarValueCurrent * value); SetValue(val, num3); num2 += num3 - dollarValueCurrent; num++; } } if (num2 != 0f && (Object)(object)RoundDirector.instance != (Object)null) { RoundDirector instance = RoundDirector.instance; instance.haulGoalMax += (int)num2; } Plugin.LogAlways($"[Loot] boosted {num} valuable(s) x{value} (+{num2:F0})"); } public static void BoostLateValuable(ValuableObject valuable) { if (IsBossLevel && LootBoosted) { float value = PluginConfig.ValuableValueMultiplier.Value; if (value != 1f) { float num = Mathf.Round(valuable.dollarValueCurrent * value); SetValue(valuable, num); Plugin.LogInfo($"[Loot] late valuable {((Object)valuable).name} x{value} -> {num}"); } } } public static void SetValue(ValuableObject valuable, float value) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (SemiFunc.IsMultiplayer()) { if ((Object)(object)valuable.photonView != (Object)null && valuable.photonView.ViewID != 0) { valuable.photonView.RPC("DollarValueSetRPC", (RpcTarget)0, new object[1] { value }); } } else { valuable.DollarValueSetRPC(value, default(PhotonMessageInfo)); } } public static bool IsEnemyOrb(ValuableObject valuable) { return (Object)(object)OrbOf(valuable) != (Object)null; } private static EnemyValuable? OrbOf(ValuableObject valuable) { return ((Component)valuable).GetComponentInParent<EnemyValuable>() ?? ((Component)valuable).GetComponentInChildren<EnemyValuable>(); } public static void TintOrb(ValuableObject valuable) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!PluginConfig.OrbGlow.Value) { return; } EnemyValuable val = OrbOf(valuable); if (!((Object)(object)val == (Object)null)) { val.fresnelColorDefault = OrbColor; val.fresnelColorIndestructible = OrbColor; if ((Object)(object)val.outerMaterial != (Object)null) { val.outerMaterial.SetColor("_FresnelColor", OrbColor); } } } private static void ApplyScale(Enemy enemy) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (enemy.HasRigidbody && !((Object)(object)enemy.Rigidbody == (Object)null)) { ScaleOptions val = BuildOptions(); if (!(val.Factor <= 1f)) { ScaleManager.ApplyIfNotScaled(((Component)enemy.Rigidbody).gameObject, val); } } } private static ScaleOptions BuildOptions() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) ScaleOptions growth = ScaleOptions.Growth; growth.Factor = PluginConfig.BossSizeMultiplier.Value; growth.AllowedTargets = (ScaleTargets)2; growth.EnemyPhysicalFactorCap = PluginConfig.BossColliderCap.Value; growth.EnemyWidthFactorCap = PluginConfig.BossWidthCap.Value; growth.EnemyHeightFactorCap = PluginConfig.BossHeightCap.Value; growth.EnemyNavRadiusFactorCap = 1f; growth.RejectExternalApply = true; growth.IgnoreBonkExpand = true; return growth; } private static void ApplyResistance(Enemy enemy) { if (enemy.HasHealth && !((Object)(object)enemy.Health == (Object)null)) { float num = Mathf.Clamp(PluginConfig.BossDamageResistance.Value, 0f, 0.9f); enemy.Health.OverrideDamageResistance(num, (num > 0f) ? 1E+09f : 0f); } } private static void ApplyHealth(Enemy enemy) { if (enemy.HasHealth && !((Object)(object)enemy.Health == (Object)null) && !(PluginConfig.BossHealthMultiplier.Value <= 1f) && !KeepsNormalHealth(enemy)) { int num = BossMaxHealth(enemy.Health.health); enemy.Health.health = num; enemy.Health.healthCurrent = num; Plugin.LogVerbose($"[Promote] health -> {num}"); } } private static int BossMaxHealth(int baseHealth) { return Mathf.Max(1, Mathf.RoundToInt((float)baseHealth * Mathf.Max(1f, PluginConfig.BossHealthMultiplier.Value))); } private static bool KeepsNormalHealth(Enemy enemy) { return (Object)(object)((Component)enemy).GetComponentInChildren<EnemyTick>(true) != (Object)null; } public static float BodyWidth() { float value = PluginConfig.BossSizeMultiplier.Value; float value2 = PluginConfig.BossWidthCap.Value; float value3 = PluginConfig.BossColliderCap.Value; if (value2 > 0f && value > value2) { return Mathf.Max(1f, value2); } if (value3 > 0f && value > value3) { return Mathf.Max(1f, value3); } return Mathf.Max(1f, value); } private static void ApplyTremor(EnemyParent parent, Enemy enemy) { if (enemy.HasRigidbody && !((Object)(object)enemy.Rigidbody == (Object)null)) { GameObject gameObject = ((Component)enemy.Rigidbody).gameObject; if (!((Object)(object)gameObject.GetComponent<BehemothTremor>() != (Object)null)) { gameObject.AddComponent<BehemothTremor>().Setup(parent, enemy.Rigidbody.rb); } } } } internal class LiveTuning : MonoBehaviour { private const float PollSeconds = 1f; private const float SettleSeconds = 0.3f; private ConfigFile _config; private DateTime _seenWrite; private float _nextPoll; private float _applyAt = -1f; private float _ownWritesUntil = -1f; public void Setup(ConfigFile config) { _config = config; _seenWrite = WriteTime(); _config.SettingChanged += OnSettingChanged; } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { _applyAt = Time.unscaledTime + 0.3f; _ownWritesUntil = Time.unscaledTime + 1f + 0.3f; } private void Update() { if (Time.unscaledTime >= _nextPoll) { _nextPoll = Time.unscaledTime + 1f; DateTime dateTime = WriteTime(); if (dateTime != _seenWrite) { if (Time.unscaledTime < _ownWritesUntil) { _seenWrite = dateTime; } else { try { _config.Reload(); } catch (IOException) { return; } _seenWrite = WriteTime(); Plugin.LogAlways("[Tune] config file changed, reloaded"); } } } if (_applyAt >= 0f && Time.unscaledTime >= _applyAt) { _applyAt = -1f; BossRoundService.Retune(); } } private DateTime WriteTime() { if (!File.Exists(_config.ConfigFilePath)) { return DateTime.MinValue; } return File.GetLastWriteTimeUtc(_config.ConfigFilePath); } } } namespace Behemoths.Patches { [HarmonyPatch(typeof(EnemyDirector), "Awake")] internal static class EnemyDirectorAwakePatch { [HarmonyPostfix] public static void Postfix() { BossRoundService.ResetLevel(); if (SemiFunc.IsMasterClientOrSingleplayer()) { BossRoundService.DecideForLevel(); } } } [HarmonyPatch(typeof(EnemyParent), "SpawnRPC")] internal static class EnemySpawnPatch { [HarmonyPostfix] public static void Postfix(EnemyParent __instance) { if (SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.RunIsLevel()) { BossRoundService.Promote(__instance); } } } [HarmonyPatch(typeof(EnemyValuable), "Start")] internal static class EnemyValuableSpawnPatch { [HarmonyPostfix] public static void Postfix(EnemyValuable __instance) { if (SemiFunc.IsMasterClientOrSingleplayer() && BossRoundService.IsBossLevel) { Rigidbody componentInChildren = ((Component)__instance).GetComponentInChildren<Rigidbody>(); if ((Object)(object)componentInChildren != (Object)null && !componentInChildren.isKinematic) { componentInChildren.collisionDetectionMode = (CollisionDetectionMode)2; } } } } [HarmonyPatch(typeof(HurtCollider), "PlayerHurt")] internal static class HurtColliderPlayerHurtPatch { [HarmonyPrefix] public static void Prefix(HurtCollider __instance, PlayerAvatar _player) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) if (!GameManager.Multiplayer() || !PhotonNetwork.IsMasterClient || !BossRoundService.IsBossLevel || (Object)(object)_player == (Object)null || _player.photonView.IsMine || !__instance.playerLogic || __instance.detectionOnly || __instance.playerKill || __instance.ignorePlayers.Contains(_player)) { return; } Enemy enemyHost = __instance.enemyHost; if ((Object)(object)enemyHost == (Object)null || !BossRoundService.IsBoss(enemyHost)) { return; } int playerDamage = __instance.playerDamage; if (playerDamage <= 0) { return; } PlayerHealth playerHealth = _player.playerHealth; if ((Object)(object)playerHealth == (Object)null || playerHealth.health <= 0 || _player.deadSet) { return; } int num = BossRoundService.BoostHit(playerDamage, playerHealth.maxHealth) - playerDamage; if (num > 0) { Vector3 position = _player.PlayerVisionTarget.VisionTransform.position; if (__instance.CanHit(((Component)_player).gameObject, __instance.playerDamageCooldown, __instance.playerRayCast, position, (HitType)0)) { int num2 = SemiFunc.EnemyGetIndex(enemyHost); Plugin.LogInfo($"[Hit] {((Object)enemyHost).name} hurt {_player.playerName} +{num} on top of {playerDamage}"); playerHealth.photonView.RPC("HurtOtherRPC", _player.photonView.Owner, new object[5] { num, Vector3.zero, true, num2, false }); } } } } [HarmonyPatch(typeof(MoonUI), "Check")] internal static class MoonUIPatch { [HarmonyPrefix] public static bool Prefix(MoonUI __instance) { if (!SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsLevel()) { return true; } if (!BossRoundService.IsBossLevel || !PluginConfig.AnnounceBossLevel.Value) { return true; } return !BossAnnouncer.Show(__instance); } } [HarmonyPatch(typeof(PlayerHealth), "HurtOther")] internal static class PlayerHealthHurtOtherPatch { [HarmonyPrefix] public static void Prefix(PlayerHealth __instance, ref int damage, int enemyIndex) { if (GameManager.Multiplayer() && !__instance.photonView.IsMine && SemiFunc.IsMasterClientOrSingleplayer() && BossRoundService.IsBossLevel && enemyIndex >= 0 && damage > 0) { Enemy val = SemiFunc.EnemyGetFromIndex(enemyIndex); if (!((Object)(object)val == (Object)null) && BossRoundService.IsBoss(val)) { int num = BossRoundService.BoostHit(damage, __instance.maxHealth); Plugin.LogInfo($"[Hit] {((Object)val).name} hurt other {damage} -> {num}"); damage = num; } } } } [HarmonyPatch(typeof(PlayerHealth), "Hurt")] internal static class PlayerHealthHurtPatch { [HarmonyPrefix] public static void Prefix(PlayerHealth __instance, ref int damage, int enemyIndex) { if (SemiFunc.IsMasterClientOrSingleplayer() && BossRoundService.IsBossLevel && enemyIndex >= 0 && damage > 0 && (!GameManager.Multiplayer() || __instance.photonView.IsMine)) { Enemy val = SemiFunc.EnemyGetFromIndex(enemyIndex); if (!((Object)(object)val == (Object)null) && BossRoundService.IsBoss(val)) { int num = BossRoundService.BoostHit(damage, __instance.maxHealth); Plugin.LogInfo($"[Hit] {((Object)val).name} hurt host {damage} -> {num}"); damage = num; } } } } [HarmonyPatch] internal static class ReachPatch { private static readonly (string Type, string Method, float[] Metres)[] Targets = new(string, string, float[])[7] { ("EnemyRobe", "StateTargetPlayer", new float[1] { 2f }), ("EnemyRobe", "StateLookUnder", new float[1] { 2.5f }), ("EnemyAnimal", "StateGoToPlayer", new float[1] { 3f }), ("EnemyFloater", "StateNotice", new float[1] { 2.5f }), ("EnemySlowWalker", "StateLookUnder", new float[1] { 3f }), ("EnemyUpscream", "StateGoToPlayer", new float[1] { 1.5f }), ("EnemyValuableThrower", "StateTargetPlayer", new float[2] { 3f, 5f }) }; private static readonly Dictionary<MonoBehaviour, Enemy?> owners = new Dictionary<MonoBehaviour, Enemy>(); private static IEnumerable<MethodBase> TargetMethods() { (string Type, string Method, float[] Metres)[] targets = Targets; for (int i = 0; i < targets.Length; i++) { (string Type, string Method, float[] Metres) tuple = targets[i]; string item = tuple.Type; string item2 = tuple.Method; MethodBase methodBase = AccessTools.Method(AccessTools.TypeByName(item), item2, (Type[])null, (Type[])null); if (methodBase != null) { yield return methodBase; continue; } Plugin.LogAlways("[Reach] " + item + "." + item2 + " is not in this build, its reach stays vanilla"); } } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { float[] metres = Targets.First(((string Type, string Method, float[] Metres) t) => t.Type == original.DeclaringType.Name && t.Method == original.Name).Metres; MethodInfo scale = AccessTools.Method(typeof(ReachPatch), "Scale", (Type[])null, (Type[])null); MethodInfo distance = AccessTools.Method(typeof(Vector3), "Distance", new Type[2] { typeof(Vector3), typeof(Vector3) }, (Type[])null); int swapped = 0; bool afterDistance = false; foreach (CodeInstruction code in instructions) { yield return code; if (afterDistance && code.opcode == OpCodes.Ldc_R4) { object operand = code.operand; if (operand is float) { float f = (float)operand; if (metres.Any((float m) => Mathf.Approximately(m, f))) { yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)scale); swapped++; } } } afterDistance = code.opcode == OpCodes.Call && code.operand is MethodInfo methodInfo && methodInfo == distance; } Plugin.LogInfo($"[Reach] {original.DeclaringType.Name}.{original.Name}: {swapped} distance(s) now follow boss size"); } public static float Scale(float metres, MonoBehaviour self) { if (!BossRoundService.IsBossLevel || (Object)(object)self == (Object)null) { return metres; } if (!owners.TryGetValue(self, out Enemy value)) { value = ((Component)self).GetComponent<Enemy>() ?? ((Component)self).GetComponentInParent<Enemy>(); owners[self] = value; } if ((Object)(object)value == (Object)null || !BossRoundService.IsBoss(value)) { return metres; } return metres * BossRoundService.BodyWidth(); } internal static void Forget() { owners.Clear(); } } [HarmonyPatch(typeof(RoundDirector), "StartRoundLogic")] internal static class RoundDirectorStartPatch { [HarmonyPostfix] public static void Postfix() { if (SemiFunc.IsMasterClientOrSingleplayer()) { BossRoundService.BoostLevelValuables(); } } } [HarmonyPatch(typeof(ValuableObject), "DollarValueSetLogic")] internal static class ValuablePatch { [HarmonyPrefix] public static void Prefix(ValuableObject __instance, out bool __state) { __state = __instance.dollarValueSet; } [HarmonyPostfix] public static void Postfix(ValuableObject __instance, bool __state) { if (__state || !SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsLevel() || !BossRoundService.IsBossLevel) { return; } if (!BossRoundService.IsEnemyOrb(__instance)) { BossRoundService.BoostLateValuable(__instance); return; } float value = PluginConfig.OrbValueMultiplier.Value; if (value != 1f) { float num = Mathf.Round(__instance.dollarValueCurrent * value); BossRoundService.SetValue(__instance, num); Plugin.LogVerbose($"[Loot] orb value x{value} -> {num}"); } GrowOrb(__instance); BossRoundService.TintOrb(__instance); } private static void GrowOrb(ValuableObject valuable) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) PhysGrabObject physGrabObject = valuable.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { return; } Rigidbody component = ((Component)physGrabObject).GetComponent<Rigidbody>(); float num = (((Object)(object)component != (Object)null) ? component.mass : 0f); if ((Object)(object)component != (Object)null && !component.isKinematic) { component.collisionDetectionMode = (CollisionDetectionMode)2; } float value = PluginConfig.OrbSizeMultiplier.Value; if (value > 1f) { ScaleOptions growth = ScaleOptions.Growth; growth.Factor = value; growth.AllowedTargets = (ScaleTargets)8; growth.RejectExternalApply = true; growth.IgnoreBonkExpand = true; growth.SuppressValueDropExpand = true; ScaleManager.ApplyIfNotScaled(((Component)physGrabObject).gameObject, growth); if ((Object)(object)component != (Object)null) { component.position += Vector3.up * 0.5f * (value - 1f); } } float value2 = PluginConfig.OrbWeightMultiplier.Value; if (value2 > 1f && (Object)(object)component != (Object)null && num > 0f) { Plugin.LogVerbose(string.Format(arg2: physGrabObject.massOriginal = (component.mass = num * value2), format: "[Loot] boss orb size x{0}, weight x{1} ({2:F1})", arg0: value, arg1: value2)); } float value3 = PluginConfig.OrbDurability.Value; if (value3 > 1f) { PhysGrabObjectImpactDetector componentInChildren = ((Component)physGrabObject).GetComponentInChildren<PhysGrabObjectImpactDetector>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.fragilityMultiplier = 1f / value3; } } } } } namespace Behemoths.Configuration { public enum VerbosityLevel { Off, Debug, Verbose } internal static class PluginConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<bool> AnnounceBossLevel; public static ConfigEntry<VerbosityLevel> LoggingLevel; public static ConfigEntry<float> BaseChance; public static ConfigEntry<float> MaxChance; public static ConfigEntry<int> CooldownLevels; public static ConfigEntry<int> EarliestLevel; public static ConfigEntry<float> BossSizeMultiplier; public static ConfigEntry<float> BossHealthMultiplier; public static ConfigEntry<float> BossDamageMultiplier; public static ConfigEntry<float> BossDamageResistance; public static ConfigEntry<float> BossHitCap; public static ConfigEntry<bool> BossTremors; public static ConfigEntry<float> OrbValueMultiplier; public static ConfigEntry<float> ValuableValueMultiplier; public static ConfigEntry<bool> OrbGlow; public static ConfigEntry<float> MapManor; public static ConfigEntry<float> MapArctic; public static ConfigEntry<float> MapWizard; public static ConfigEntry<float> MapMuseum; public static ConfigEntry<float> MapOther; public static ConfigEntry<float> BossColliderCap; public static ConfigEntry<float> BossHeightCap; public static ConfigEntry<float> BossWidthCap; public static ConfigEntry<float> OrbSizeMultiplier; public static ConfigEntry<float> OrbWeightMultiplier; public static ConfigEntry<float> OrbDurability; public static void Init(ConfigFile config) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Expected O, but got Unknown //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Expected O, but got Unknown //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Expected O, but got Unknown //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Expected O, but got Unknown //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Expected O, but got Unknown //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Expected O, but got Unknown //IL_0372: Unknown result type (might be due to invalid IL or missing references) //IL_037c: Expected O, but got Unknown //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Expected O, but got Unknown //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Expected O, but got Unknown //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0424: Expected O, but got Unknown //IL_0452: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Expected O, but got Unknown //IL_048a: Unknown result type (might be due to invalid IL or missing references) //IL_0494: Expected O, but got Unknown //IL_04c2: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Expected O, but got Unknown //IL_04fa: Unknown result type (might be due to invalid IL or missing references) //IL_0504: Expected O, but got Unknown //IL_0532: Unknown result type (might be due to invalid IL or missing references) //IL_053c: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Turns boss levels on or off."); AnnounceBossLevel = config.Bind<bool>("General", "AnnounceBossLevel", true, "Shows BOSS LEVEL on the game's moon-phase screen once the level loads, with this level's numbers. Only the host sees it. On a level that is also a moon change it replaces the moon screen, and the moon's modifiers still apply."); LoggingLevel = config.Bind<VerbosityLevel>("General", "LogLevel", VerbosityLevel.Off, "Off logs a few lines per level: the boss roll, the loot boost and the popup. Debug adds every Behemoth, hit and orb. Verbose logs everything."); BaseChance = config.Bind<float>("Boss Levels", "BaseChance", 10f, new ConfigDescription("Percent chance a level is a boss level at the start of a run. It climbs toward MaxChance along the game's difficulty curve.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); MaxChance = config.Bind<float>("Boss Levels", "MaxChance", 60f, new ConfigDescription("Percent chance from level 10 on. If it is lower than BaseChance, BaseChance wins. A chance of 100 means every eligible level, on any map whose odds are above 0.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); CooldownLevels = config.Bind<int>("Boss Levels", "CooldownLevels", 3, new ConfigDescription("Normal levels that must pass after a boss level before another can roll. 0 allows back to back. Saved with the run.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>())); EarliestLevel = config.Bind<int>("Boss Levels", "EarliestLevel", 3, new ConfigDescription("First level that can be a boss level. The game holds its nastier monsters back for the first two levels too.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>())); BossSizeMultiplier = config.Bind<float>("Boss Monsters", "SizeMultiplier", 1.4f, new ConfigDescription("How much bigger a Behemoth is. Its body grows wider with it but stays door height, so it can still follow you through the building. Players need ScalerCore to see the size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 4f), Array.Empty<object>())); BossHealthMultiplier = config.Bind<float>("Boss Monsters", "HealthMultiplier", 3f, new ConfigDescription("Health compared to a normal monster of the same kind. The Tick keeps its normal health.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); BossDamageMultiplier = config.Bind<float>("Boss Monsters", "DamageMultiplier", 2f, new ConfigDescription("How much harder a Behemoth hits. Applies to every player, with or without the mod.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>())); BossDamageResistance = config.Bind<float>("Boss Monsters", "DamageResistance", 0.5f, new ConfigDescription("Share of incoming damage a Behemoth ignores, on top of its extra health. 0.5 takes half damage, 0 turns it off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.9f), Array.Empty<object>())); BossHitCap = config.Bind<float>("Boss Monsters", "HitCap", 75f, new ConfigDescription("The most one boosted hit can take, as a percent of a player's max health, unless the normal hit already took more. 100 turns the cap off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 100f), Array.Empty<object>())); BossTremors = config.Bind<bool>("Boss Monsters", "Tremors", true, "Shakes the host's camera when a Behemoth moves nearby."); OrbValueMultiplier = config.Bind<float>("Boss Loot", "OrbValueMultiplier", 2.5f, new ConfigDescription("Cash value of the orb a Behemoth drops, compared to a normal orb. Every player gets it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>())); ValuableValueMultiplier = config.Bind<float>("Boss Loot", "ValuableMultiplier", 1.5f, new ConfigDescription("Cash value of every other valuable on a boss level. The extraction goal does not go up, so the extra is profit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>())); OrbGlow = config.Bind<bool>("Boss Loot", "OrbGlow", true, "Amber glow on Behemoth orbs. Only the host sees it."); MapManor = config.Bind<float>("Map Odds", "Manor", 1f, new ConfigDescription("Multiplier on the boss-level chance at Headman Manor. 0 means never.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); MapArctic = config.Bind<float>("Map Odds", "Arctic", 0.75f, new ConfigDescription("Multiplier on the chance at McJannek Station. Lower by default, the corridors are tight.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); MapWizard = config.Bind<float>("Map Odds", "Wizard", 1.25f, new ConfigDescription("Multiplier on the chance at Swiftbroom Academy. Higher by default, the halls are big.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); MapMuseum = config.Bind<float>("Map Odds", "Museum", 1.25f, new ConfigDescription("Multiplier on the chance at the Museum of Human Art. Higher by default, the galleries are open.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); MapOther = config.Bind<float>("Map Odds", "OtherMaps", 1f, new ConfigDescription("Multiplier on the chance on any other map, modded ones included.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); BossColliderCap = config.Bind<float>("Tuning", "ColliderCap", 0f, new ConfigDescription("Holds the whole body (hitbox, grab body, reach) at this multiple of vanilla while the look keeps growing. 0 lets the body follow SizeMultiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 4f), Array.Empty<object>())); BossHeightCap = config.Bind<float>("Tuning", "HeightCap", 1f, new ConfigDescription("How tall the body may get, as a multiple of vanilla. 1 keeps door height. 0 lets it grow as tall as it looks, and a big one gets stuck in doorways.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 4f), Array.Empty<object>())); BossWidthCap = config.Bind<float>("Tuning", "WidthCap", 0f, new ConfigDescription("How wide the body may get, as a multiple of vanilla. 0 follows the look. Try 1.5 if a map's doorways give wide Behemoths trouble.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 4f), Array.Empty<object>())); OrbSizeMultiplier = config.Bind<float>("Tuning", "OrbSizeMultiplier", 2f, new ConfigDescription("How big a Behemoth orb is. Players need ScalerCore to see the size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 6f), Array.Empty<object>())); OrbWeightMultiplier = config.Bind<float>("Tuning", "OrbWeightMultiplier", 2.5f, new ConfigDescription("How heavy a Behemoth orb is, set apart from its size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); OrbDurability = config.Bind<float>("Tuning", "OrbDurability", 5f, new ConfigDescription("How much knocking around a Behemoth orb takes before it loses value. 5 is about five times a normal valuable, 1 is normal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); } } }