using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CombatSharedFeatures; using GameNetcodeStuff; using HarmonyLib; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("LethalBattleTweaks")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("lengjing")] [assembly: AssemblyProduct("LethalBattleTweaks")] [assembly: AssemblyCopyright("Copyright © lengjing 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("8e5a2c62-6a18-4f9e-8948-596a50b89631")] [assembly: AssemblyFileVersion("1.0.7.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.7.0")] namespace CombatSharedFeatures { public static class PublicFeatureConfig { public static ConfigEntry RefillEnabled { get; private set; } public static ConfigEntry RefillInterval { get; private set; } public static ConfigEntry RefillTotalCap { get; private set; } public static ConfigEntry RefillDelay { get; private set; } public static ConfigEntry SpawnDelay { get; private set; } public static ConfigEntry RefillBatchSize { get; private set; } public static ConfigEntry RefillFlowermanInit { get; private set; } public static ConfigEntry RefillFlowermanMax { get; private set; } public static ConfigEntry RefillFlowermanWeight { get; private set; } public static ConfigEntry RefillNutcrackerInit { get; private set; } public static ConfigEntry RefillNutcrackerMax { get; private set; } public static ConfigEntry RefillNutcrackerWeight { get; private set; } public static ConfigEntry RefillCrawlerInit { get; private set; } public static ConfigEntry RefillCrawlerMax { get; private set; } public static ConfigEntry RefillCrawlerWeight { get; private set; } public static ConfigEntry RefillCentipedeInit { get; private set; } public static ConfigEntry RefillCentipedeMax { get; private set; } public static ConfigEntry RefillCentipedeWeight { get; private set; } public static ConfigEntry RefillHoarderBugInit { get; private set; } public static ConfigEntry RefillHoarderBugMax { get; private set; } public static ConfigEntry RefillHoarderBugWeight { get; private set; } public static ConfigEntry RefillMaskedInit { get; private set; } public static ConfigEntry RefillMaskedMax { get; private set; } public static ConfigEntry RefillMaskedWeight { get; private set; } public static ConfigEntry RefillSandSpiderInit { get; private set; } public static ConfigEntry RefillSandSpiderMax { get; private set; } public static ConfigEntry RefillSandSpiderWeight { get; private set; } public static ConfigEntry RefillButlerInit { get; private set; } public static ConfigEntry RefillButlerMax { get; private set; } public static ConfigEntry RefillButlerWeight { get; private set; } public static ConfigEntry RefillStingrayInit { get; private set; } public static ConfigEntry RefillStingrayMax { get; private set; } public static ConfigEntry RefillStingrayWeight { get; private set; } public static ConfigEntry StartingCredits { get; private set; } public static ConfigEntry DisableHazards { get; private set; } public static ConfigEntry DisableVanillaEnemies { get; private set; } public static ConfigEntry DisableScrap { get; private set; } public static ConfigEntry NoLockedDoors { get; private set; } public static ConfigEntry DisableFriendlyMeleeDamage { get; private set; } public static ConfigEntry ButlerInstantOpenDoors { get; private set; } public static ConfigEntry NutcrackerNoFriendlyFire { get; private set; } public static ConfigEntry NutcrackerDropGunAndShells { get; private set; } public static ConfigEntry ButlerDropKnifeAndBees { get; private set; } public static ConfigEntry TZPHealingEnabled { get; private set; } public static ConfigEntry TZPHealAmount { get; private set; } public static ConfigEntry LootDropsEnabled { get; private set; } public static ConfigEntry TZPDropChance { get; private set; } public static ConfigEntry StunGrenadeDropChance { get; private set; } public static ConfigEntry CorpseCleanupEnabled { get; private set; } public static ConfigEntry CorpseCleanupDelay { get; private set; } public static ConfigEntry CurfewEnabled { get; private set; } public static ConfigEntry CurfewDuration { get; private set; } public static ConfigEntry CurfewRepeatInterval { get; private set; } public static ConfigEntry ScoreboardEnabled { get; private set; } public static ConfigEntry MvpEnabled { get; private set; } public static ConfigEntry ScoreboardKey { get; private set; } public static ConfigEntry ScoreWeight { get; private set; } public static ConfigEntry HitWeight { get; private set; } public static ConfigEntry ScoreboardBroadcastEnabled { get; private set; } public static ConfigEntry ScoreboardBroadcastInterval { get; private set; } public static ConfigEntry ScoreboardBroadcastTopCount { get; private set; } public static ConfigEntry ScoreFlowerman { get; private set; } public static ConfigEntry ScoreNutcracker { get; private set; } public static ConfigEntry ScoreCrawler { get; private set; } public static ConfigEntry ScoreCentipede { get; private set; } public static ConfigEntry ScoreHoarderBug { get; private set; } public static ConfigEntry ScoreMasked { get; private set; } public static ConfigEntry ScoreSandSpider { get; private set; } public static ConfigEntry ScoreButler { get; private set; } public static ConfigEntry ScoreStingray { get; private set; } public static ConfigEntry RescueEnabled { get; private set; } public static ConfigEntry RescueImmunityDuration { get; private set; } public static ConfigEntry EnableCrawlerScale { get; private set; } public static ConfigEntry CrawlerScale { get; private set; } public static ConfigEntry EnableButlerScale { get; private set; } public static ConfigEntry ButlerScale { get; private set; } private static string Bilingual(string en, string zh) { return en + " / " + zh; } public static void Bind(ConfigFile config) { //IL_08d2: Unknown result type (might be due to invalid IL or missing references) //IL_08dc: Expected O, but got Unknown //IL_0909: Unknown result type (might be due to invalid IL or missing references) //IL_0913: Expected O, but got Unknown //IL_0abd: Unknown result type (might be due to invalid IL or missing references) //IL_0ac7: Expected O, but got Unknown //IL_0b24: Unknown result type (might be due to invalid IL or missing references) //IL_0b2e: Expected O, but got Unknown //IL_0b8b: Unknown result type (might be due to invalid IL or missing references) //IL_0b95: Expected O, but got Unknown string text = "Enemy Refill"; RefillEnabled = config.Bind(text, "Enabled", true, Bilingual("Enable dynamic indoor enemy refill.", "启用设施内动态补怪。")); RefillInterval = config.Bind(text, "Interval", 5f, Bilingual("Seconds between refill checks.", "每次检查是否需要补怪的间隔秒数。")); RefillTotalCap = config.Bind(text, "TotalCap", 18, Bilingual("Total indoor enemies the refill system tries to maintain.", "补怪系统尝试维持的设施内怪物总上限。")); RefillDelay = config.Bind(text, "RefillDelay", 10, Bilingual("Delay after landing before refill starts.", "飞船落地后开始补怪前的延迟秒数。")); SpawnDelay = config.Bind(text, "SpawnDelay", 0.3f, Bilingual("Delay between spawned enemies during a refill batch.", "单次补怪批次中每只怪之间的生成间隔。")); RefillBatchSize = config.Bind(text, "BatchSize", 3, Bilingual("Maximum enemies spawned per refill batch.", "每次补怪批次最多生成的怪物数量。")); RefillFlowermanInit = config.Bind("Enemy Refill - Flowerman", "Init", 0, Bilingual("Initial Flowerman count.", "开局额外生成的小黑数量。")); RefillFlowermanMax = config.Bind("Enemy Refill - Flowerman", "Max", 1, Bilingual("Maximum Flowerman refill count.", "小黑可被补怪到的最大数量。")); RefillFlowermanWeight = config.Bind("Enemy Refill - Flowerman", "Weight", 10, Bilingual("Flowerman refill weight.", "小黑的补怪权重。")); RefillNutcrackerInit = config.Bind("Enemy Refill - Nutcracker", "Init", 0, Bilingual("Initial Nutcracker count.", "开局额外生成的胡桃夹子数量。")); RefillNutcrackerMax = config.Bind("Enemy Refill - Nutcracker", "Max", 1, Bilingual("Maximum Nutcracker refill count.", "胡桃夹子可被补怪到的最大数量。")); RefillNutcrackerWeight = config.Bind("Enemy Refill - Nutcracker", "Weight", 10, Bilingual("Nutcracker refill weight.", "胡桃夹子的补怪权重。")); RefillCrawlerInit = config.Bind("Enemy Refill - Crawler", "Init", 0, Bilingual("Initial Crawler count.", "开局额外生成的半身鱼数量。")); RefillCrawlerMax = config.Bind("Enemy Refill - Crawler", "Max", 1, Bilingual("Maximum Crawler refill count.", "半身鱼可被补怪到的最大数量。")); RefillCrawlerWeight = config.Bind("Enemy Refill - Crawler", "Weight", 10, Bilingual("Crawler refill weight.", "半身鱼的补怪权重。")); RefillCentipedeInit = config.Bind("Enemy Refill - Centipede", "Init", 0, Bilingual("Initial Centipede count.", "开局额外生成的抱脸虫数量。")); RefillCentipedeMax = config.Bind("Enemy Refill - Centipede", "Max", 2, Bilingual("Maximum Centipede refill count.", "抱脸虫可被补怪到的最大数量。")); RefillCentipedeWeight = config.Bind("Enemy Refill - Centipede", "Weight", 10, Bilingual("Centipede refill weight.", "抱脸虫的补怪权重。")); RefillHoarderBugInit = config.Bind("Enemy Refill - HoarderBug", "Init", 0, Bilingual("Initial HoarderBug count.", "开局额外生成的囤积虫数量。")); RefillHoarderBugMax = config.Bind("Enemy Refill - HoarderBug", "Max", 3, Bilingual("Maximum HoarderBug refill count.", "囤积虫可被补怪到的最大数量。")); RefillHoarderBugWeight = config.Bind("Enemy Refill - HoarderBug", "Weight", 10, Bilingual("HoarderBug refill weight.", "囤积虫的补怪权重。")); RefillMaskedInit = config.Bind("Enemy Refill - Masked", "Init", 0, Bilingual("Initial Masked count.", "开局额外生成的假人数量。")); RefillMaskedMax = config.Bind("Enemy Refill - Masked", "Max", 1, Bilingual("Maximum Masked refill count.", "假人可被补怪到的最大数量。")); RefillMaskedWeight = config.Bind("Enemy Refill - Masked", "Weight", 10, Bilingual("Masked refill weight.", "假人的补怪权重。")); RefillSandSpiderInit = config.Bind("Enemy Refill - SandSpider", "Init", 0, Bilingual("Initial SandSpider count.", "开局额外生成的蜘蛛数量。")); RefillSandSpiderMax = config.Bind("Enemy Refill - SandSpider", "Max", 2, Bilingual("Maximum SandSpider refill count.", "蜘蛛可被补怪到的最大数量。")); RefillSandSpiderWeight = config.Bind("Enemy Refill - SandSpider", "Weight", 10, Bilingual("SandSpider refill weight.", "蜘蛛的补怪权重。")); RefillButlerInit = config.Bind("Enemy Refill - Butler", "Init", 0, Bilingual("Initial Butler count.", "开局额外生成的管家数量。")); RefillButlerMax = config.Bind("Enemy Refill - Butler", "Max", 2, Bilingual("Maximum Butler refill count.", "管家可被补怪到的最大数量。")); RefillButlerWeight = config.Bind("Enemy Refill - Butler", "Weight", 10, Bilingual("Butler refill weight.", "管家的补怪权重。")); RefillStingrayInit = config.Bind("Enemy Refill - Stingray", "Init", 0, Bilingual("Initial Stingray count.", "开局额外生成的黏滑鱼数量。")); RefillStingrayMax = config.Bind("Enemy Refill - Stingray", "Max", 1, Bilingual("Maximum Stingray refill count.", "黏滑鱼可被补怪到的最大数量。")); RefillStingrayWeight = config.Bind("Enemy Refill - Stingray", "Weight", 10, Bilingual("Stingray refill weight.", "黏滑鱼的补怪权重。")); StartingCredits = config.Bind("World Rules", "StartingCredits", 60, Bilingual("Starting company credits.", "开局公司金钱。")); DisableHazards = config.Bind("World Rules", "DisableHazards", false, Bilingual("Disable landmines, turrets, and spikes.", "禁用地雷、炮塔和尖刺。")); DisableVanillaEnemies = config.Bind("World Rules", "DisableVanillaEnemies", false, Bilingual("Disable vanilla natural enemy spawning.", "禁用原版自然刷怪。")); DisableScrap = config.Bind("World Rules", "DisableScrap", false, Bilingual("Disable indoor scrap spawning.", "禁用室内废料刷新。")); NoLockedDoors = config.Bind("World Rules", "NoLockedDoors", true, Bilingual("Automatically unlock locked doors.", "自动解锁上锁的门。")); DisableFriendlyMeleeDamage = config.Bind("World Rules", "DisableFriendlyMeleeDamage", true, Bilingual("Disable shovel and knife friendly fire.", "禁用铲子和小刀的玩家友伤。")); ButlerInstantOpenDoors = config.Bind("World Rules", "ButlerInstantOpenDoors", true, Bilingual("Make butlers open doors almost instantly.", "让管家几乎瞬间开门。")); NutcrackerNoFriendlyFire = config.Bind("World Rules", "NutcrackerNoFriendlyFire", true, Bilingual("Prevent nutcracker shotgun blasts from damaging monsters while still allowing player damage.", "让胡桃夹子不会伤害怪物,但仍然可以伤害玩家。")); NutcrackerDropGunAndShells = config.Bind("World Rules", "NutcrackerDropGunAndShells", false, Bilingual("Allow nutcrackers to drop their shotgun and shotgun shells on death.", "允许胡桃夹子死亡时掉落枪和子弹。")); ButlerDropKnifeAndBees = config.Bind("World Rules", "ButlerDropKnifeAndBees", false, Bilingual("Allow butlers to spawn their knife and bees on death.", "允许管家死亡时掉落菜刀并生成蜜蜂。")); TZPHealingEnabled = config.Bind("TZP Healing", "Enabled", true, Bilingual("Allow TZP inhalant to heal once per canister.", "允许每罐 TZP 提供一次回血。")); TZPHealAmount = config.Bind("TZP Healing", "HealAmount", 20, Bilingual("Healing amount per TZP use.", "每次 TZP 触发时的回血量。")); LootDropsEnabled = config.Bind("Loot Drops", "Enabled", true, Bilingual("Allow enemies to drop combat supplies.", "允许敌人掉落战斗补给。")); TZPDropChance = config.Bind("Loot Drops", "TZPDropChance", 0.1f, Bilingual("Chance for supported enemies to drop TZP.", "支持的敌人掉落 TZP 的概率。")); StunGrenadeDropChance = config.Bind("Loot Drops", "StunGrenadeDropChance", 0.05f, Bilingual("Chance for supported enemies to drop a stun grenade.", "支持的敌人掉落闪光弹的概率。")); CorpseCleanupEnabled = config.Bind("Corpse Cleanup", "Enabled", true, Bilingual("Automatically remove enemy corpses after a delay.", "延迟一段时间后自动清理怪物尸体。")); CorpseCleanupDelay = config.Bind("Corpse Cleanup", "Delay", 5f, Bilingual("Delay in seconds before supported enemy corpses are removed.", "支持的怪物尸体在被清理前等待的秒数。")); CurfewEnabled = config.Bind("Curfew Execution", "Enabled", false, Bilingual("After landing, execute players who remain outside the facility after the timer, including players idling on the ship.", "飞船落地倒计时结束后处决仍在设施外的玩家,飞船内挂机玩家也会被处决。")); CurfewDuration = config.Bind("Curfew Execution", "Duration", 60f, Bilingual("Seconds after landing before players outside the facility are condemned.", "飞船落地后多少秒开始处决设施外玩家。")); CurfewRepeatInterval = config.Bind("Curfew Execution", "RepeatInterval", 5f, Bilingual("Seconds between judge dog retry attempts and outside checks.", "审判犬失败重试和室外检查的间隔秒数。")); ScoreboardEnabled = config.Bind("Combat Scoreboard", "Enabled", true, Bilingual("Enable the host combat scoreboard.", "启用主机战斗记分面板。")); MvpEnabled = config.Bind("Combat Scoreboard", "MvpEnabled", true, Bilingual("Broadcast the round MVP in chat at game over.", "游戏结束时在聊天栏播报本局 MVP。")); ScoreboardKey = config.Bind("Combat Scoreboard", "ToggleKey", (Key)16, Bilingual("Keyboard key used to show or hide the scoreboard.", "显示或隐藏记分面板的按键。")); ScoreWeight = config.Bind("Combat Scoreboard", "ScoreWeight", 7, Bilingual("Weight of kill score in total score.", "击杀得分在总分中的权重。")); HitWeight = config.Bind("Combat Scoreboard", "HitWeight", 3, Bilingual("Weight of hit count in total score.", "命中次数在总分中的权重。")); ScoreboardBroadcastEnabled = config.Bind("Scoreboard Broadcast", "Enabled", true, Bilingual("Broadcast the top players in chat.", "在聊天栏播报排名。")); ScoreboardBroadcastInterval = config.Bind("Scoreboard Broadcast", "Interval", 10f, new ConfigDescription(Bilingual("Seconds between ranking broadcasts.", "排名播报间隔秒数。"), (AcceptableValueBase)(object)new AcceptableValueRange(5f, 300f), Array.Empty())); ScoreboardBroadcastTopCount = config.Bind("Scoreboard Broadcast", "TopCount", 3, new ConfigDescription(Bilingual("Number of top players to broadcast.", "聊天栏播报前几名。"), (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); ScoreFlowerman = config.Bind("Combat Scoring", "Flowerman", 3, Bilingual("Score for killing a Flowerman.", "击杀小黑的得分。")); ScoreNutcracker = config.Bind("Combat Scoring", "Nutcracker", 2, Bilingual("Score for killing a Nutcracker.", "击杀胡桃夹子的得分。")); ScoreCrawler = config.Bind("Combat Scoring", "Crawler", 2, Bilingual("Score for killing a Crawler.", "击杀半身鱼的得分。")); ScoreCentipede = config.Bind("Combat Scoring", "Centipede", 1, Bilingual("Score for killing a Centipede.", "击杀抱脸虫的得分。")); ScoreHoarderBug = config.Bind("Combat Scoring", "HoarderBug", 1, Bilingual("Score for killing a Hoarder Bug.", "击杀囤积虫的得分。")); ScoreMasked = config.Bind("Combat Scoring", "Masked", 1, Bilingual("Score for killing a Masked enemy.", "击杀假人的得分。")); ScoreSandSpider = config.Bind("Combat Scoring", "SandSpider", 2, Bilingual("Score for killing a Sand Spider.", "击杀蜘蛛的得分。")); ScoreButler = config.Bind("Combat Scoring", "Butler", 2, Bilingual("Score for killing a Butler.", "击杀管家的得分。")); ScoreStingray = config.Bind("Combat Scoring", "Stingray", 2, Bilingual("Score for killing a Stingray.", "击杀黏滑鱼的得分。")); RescueEnabled = config.Bind("Rescue", "Enabled", true, Bilingual("Allow the host to rescue an outside player with the ship teleporter.", "允许主机使用飞船传送器救援设施外玩家。")); RescueImmunityDuration = config.Bind("Rescue", "ImmunityDuration", 45f, new ConfigDescription(Bilingual("Seconds of curfew immunity after a successful rescue.", "成功救援后免处决的秒数。"), (AcceptableValueBase)(object)new AcceptableValueRange(1f, 300f), Array.Empty())); EnableCrawlerScale = config.Bind("Enemy Scale", "EnableCrawlerScale", false, Bilingual("Enable Crawler scaling.", "启用半身鱼体型放大。")); CrawlerScale = config.Bind("Enemy Scale", "CrawlerScale", 1.5f, new ConfigDescription(Bilingual("Crawler local scale multiplier.", "半身鱼体型倍率。"), (AcceptableValueBase)(object)new AcceptableValueRange(1f, 2f), Array.Empty())); EnableButlerScale = config.Bind("Enemy Scale", "EnableButlerScale", false, Bilingual("Enable Butler scaling.", "启用管家体型放大。")); ButlerScale = config.Bind("Enemy Scale", "ButlerScale", 1.3f, new ConfigDescription(Bilingual("Butler local scale multiplier.", "管家体型倍率。"), (AcceptableValueBase)(object)new AcceptableValueRange(1f, 2f), Array.Empty())); } } public enum PublicFeatureMode { PublicStandalone, PrivateExtended } public static class PublicFeatureRuntime { [HarmonyPatch(typeof(StartOfRound), "StartGame")] private static class StartOfRoundStartGamePatch { [HarmonyPrefix] private static void Prefix() { if (!((Object)(object)StartOfRound.Instance == (Object)null) && ((NetworkBehaviour)StartOfRound.Instance).IsServer) { HandleRoundStart(); } } } [HarmonyPatch(typeof(StartOfRound), "gameOverAnimation")] private static class GameOverAnimationPatch { [HarmonyPostfix] private static void Postfix() { CombatScoreboardSystem.ScheduleMvpAnnouncement(); } } [HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")] private static class RoundEndPatch { [HarmonyPrefix] private static void Prefix() { CombatScoreboardSystem.EndRound(); CurfewExecutionSystem.Stop(); } } private static BaseUnityPlugin _plugin; private static ConfigFile _config; private static Func _privateBossPredicate; private static bool _isInitialized; private static ManualLogSource _logger = Logger.CreateLogSource("PublicFeatureRuntime"); public static BaseUnityPlugin PluginInstance => _plugin; public static ManualLogSource Logger => _logger; public static PublicFeatureMode Mode { get; private set; } public static bool IsEnabled => _isInitialized; public static void Initialize(BaseUnityPlugin plugin, PublicFeatureMode mode, ManualLogSource logger) { if (!((Object)(object)_plugin == (Object)(object)plugin) || !_isInitialized) { _plugin = plugin; _config = plugin.Config; Mode = mode; _logger = logger ?? _logger; PublicFeatureConfig.Bind(_config); _config.SettingChanged += HandleConfigChanged; _isInitialized = true; CombatScoreboardSystem.Initialize(); } } public static void OnPluginStart() { if (_isInitialized) { PublicFeatureEnemyCatalog.CacheAllEnemyTypes(); RefillSystem.Restart(); } } public static void Shutdown() { if (_isInitialized) { RefillSystem.Stop(); CurfewExecutionSystem.Stop(); CombatScoreboardSystem.Stop(); EnemyScalePatches.ResetRoundState(); if (_config != null) { _config.SettingChanged -= HandleConfigChanged; } _plugin = null; _config = null; _privateBossPredicate = null; _isInitialized = false; } } public static void HandleRoundStart() { if (_isInitialized) { PublicFeatureEnemyCatalog.CacheAllEnemyTypes(); ItemAndLootPatches.ResetRoundState(); CurfewExecutionSystem.ResetRoundState(); CombatScoreboardSystem.ResetRoundState(); EnemyScalePatches.ResetRoundState(); RefillSystem.Restart(); CurfewExecutionSystem.Restart(); CombatScoreboardSystem.StartBroadcast(); } } public static void HandleConfigChanged(object sender, SettingChangedEventArgs e) { if (_isInitialized && e != null && e.ChangedSetting != null) { string text = e.ChangedSetting.Definition.Section ?? string.Empty; if (text.StartsWith("Enemy Refill", StringComparison.OrdinalIgnoreCase)) { RefillSystem.Restart(); } else if (text.StartsWith("Combat Scoreboard", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Combat Scoring", StringComparison.OrdinalIgnoreCase)) { CombatScoreboardSystem.ResetRoundState(); } else if (text.StartsWith("Curfew Execution", StringComparison.OrdinalIgnoreCase)) { CurfewExecutionSystem.Restart(); } else if (text.StartsWith("Scoreboard Broadcast", StringComparison.OrdinalIgnoreCase)) { CombatScoreboardSystem.RestartBroadcast(); } } } public static void SetPrivateBossPredicate(Func predicate) { _privateBossPredicate = predicate; } public static bool IsPrivateBossEnemy(EnemyAI enemy) { if (_privateBossPredicate != null && (Object)(object)enemy != (Object)null) { return _privateBossPredicate(enemy); } return false; } } public static class PublicFeatureEnemyCatalog { private static readonly Dictionary EnemyTypeCache = new Dictionary(); private static readonly Dictionary EnemyNameAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "HoarderBug", "Hoarding bug" }, { "SandSpider", "Bunker Spider" }, { "Masked", "Masked" }, { "Centipede", "Centipede" }, { "Flowerman", "Flowerman" }, { "Nutcracker", "Nutcracker" }, { "Crawler", "Crawler" }, { "Butler", "Butler" }, { "Stingray", "Stingray" } }; public static int CachedEnemyCount => EnemyTypeCache.Count; public static void CacheAllEnemyTypes() { if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.levels == null) { return; } EnemyTypeCache.Clear(); SelectableLevel[] levels = StartOfRound.Instance.levels; foreach (SelectableLevel val in levels) { foreach (SpawnableEnemyWithRarity enemy in val.Enemies) { CacheEnemyType(enemy.enemyType); } foreach (SpawnableEnemyWithRarity daytimeEnemy in val.DaytimeEnemies) { CacheEnemyType(daytimeEnemy.enemyType); } foreach (SpawnableEnemyWithRarity outsideEnemy in val.OutsideEnemies) { CacheEnemyType(outsideEnemy.enemyType); } } } public static EnemyType GetEnemyTypeByName(string name) { if (string.IsNullOrEmpty(name)) { return null; } string text = name; if (EnemyNameAliases.TryGetValue(name, out var value)) { text = value; } if (EnemyTypeCache.TryGetValue(text, out var value2)) { return value2; } if (text != name && EnemyTypeCache.TryGetValue(name, out value2)) { return value2; } foreach (KeyValuePair item in EnemyTypeCache) { if (item.Key.Equals(text, StringComparison.OrdinalIgnoreCase) || item.Key.Equals(name, StringComparison.OrdinalIgnoreCase)) { return item.Value; } } foreach (KeyValuePair item2 in EnemyTypeCache) { if (item2.Key.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 || item2.Key.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0) { return item2.Value; } } return null; } private static void CacheEnemyType(EnemyType type) { if (!((Object)(object)type == (Object)null) && !EnemyTypeCache.ContainsKey(type.enemyName)) { EnemyTypeCache[type.enemyName] = type; } } } public static class RefillSystem { private struct MonsterConfig { public string Name; public int Init; public int Max; public int Weight; public MonsterConfig(string name, int init, int max, int weight) { Name = name; Init = init; Max = max; Weight = weight; } } private static readonly List CachedRefillConfigs = new List(); private static readonly List<(MonsterConfig Config, float Score)> RefillCandidatesBuffer = new List<(MonsterConfig, float)>(); private static readonly List ValidSpawnNodesBuffer = new List(); private static Coroutine _refillCoroutine; public static void Restart() { Stop(); if ((Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null && PublicFeatureConfig.RefillEnabled.Value) { _refillCoroutine = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(RefillEnemiesRoutine()); } } public static void Stop() { if (_refillCoroutine != null && (Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(_refillCoroutine); _refillCoroutine = null; } } private static IEnumerator RefillEnemiesRoutine() { while ((Object)(object)RoundManager.Instance == (Object)null) { yield return (object)new WaitForSeconds(1f); } while (!((Object)(object)StartOfRound.Instance == (Object)null) && !((Object)(object)RoundManager.Instance == (Object)null)) { yield return (object)new WaitUntil((Func)(() => (Object)(object)StartOfRound.Instance == (Object)null || (StartOfRound.Instance.shipHasLanded && !StartOfRound.Instance.inShipPhase && RoundManager.Instance.allEnemyVents != null && RoundManager.Instance.allEnemyVents.Length != 0))); if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null) { break; } if (!PublicFeatureConfig.RefillEnabled.Value || !((NetworkBehaviour)RoundManager.Instance).IsHost) { yield return (object)new WaitForSeconds(1f); continue; } if (PublicFeatureEnemyCatalog.CachedEnemyCount == 0) { PublicFeatureEnemyCatalog.CacheAllEnemyTypes(); } yield return SpawnInitialEnemiesCoroutine(); for (float elapsed = 0f; elapsed < (float)PublicFeatureConfig.RefillDelay.Value; elapsed += 1f) { if ((Object)(object)StartOfRound.Instance == (Object)null) { break; } if (StartOfRound.Instance.inShipPhase) { break; } yield return (object)new WaitForSeconds(1f); } if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.inShipPhase) { continue; } while ((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.shipHasLanded && !StartOfRound.Instance.inShipPhase) { if (PublicFeatureConfig.RefillEnabled.Value) { yield return CheckAndRefillEnemiesCoroutine(); } yield return (object)new WaitForSeconds(PublicFeatureConfig.RefillInterval.Value); if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.inShipPhase) { break; } } } } private static IEnumerator SpawnInitialEnemiesCoroutine() { List monsterConfigs = GetMonsterConfigs(); foreach (MonsterConfig item in monsterConfigs) { if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.inShipPhase) { yield break; } if (item.Init > 0) { EnemyType enemyTypeByName = PublicFeatureEnemyCatalog.GetEnemyTypeByName(item.Name); if ((Object)(object)enemyTypeByName != (Object)null) { yield return SpawnEnemyCoroutine(enemyTypeByName, item.Init); } } } } private static IEnumerator CheckAndRefillEnemiesCoroutine() { if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null) { yield break; } List monsterConfigs = GetMonsterConfigs(); Dictionary configTypeMap = new Dictionary(); foreach (MonsterConfig item2 in monsterConfigs) { configTypeMap[item2.Name] = PublicFeatureEnemyCatalog.GetEnemyTypeByName(item2.Name); } List spawnedEnemies = RoundManager.Instance.SpawnedEnemies; int num = 0; Dictionary currentCounts = new Dictionary(); foreach (MonsterConfig item3 in monsterConfigs) { currentCounts[item3.Name] = 0; } for (int i = 0; i < spawnedEnemies.Count; i++) { EnemyAI val = spawnedEnemies[i]; if ((Object)(object)val == (Object)null || val.isEnemyDead) { continue; } foreach (MonsterConfig item4 in monsterConfigs) { EnemyType val2 = configTypeMap[item4.Name]; if ((Object)(object)val2 != (Object)null && (Object)(object)val.enemyType == (Object)(object)val2) { currentCounts[item4.Name]++; num++; break; } } } int num2 = PublicFeatureConfig.RefillTotalCap.Value - num; if (num2 <= 0) { yield break; } int toSpawn = Mathf.Min(num2, PublicFeatureConfig.RefillBatchSize.Value); for (int j = 0; j < toSpawn; j++) { RefillCandidatesBuffer.Clear(); foreach (MonsterConfig item5 in monsterConfigs) { if (currentCounts[item5.Name] < item5.Max && item5.Weight > 0) { float item = (float)item5.Weight / (float)(currentCounts[item5.Name] + 1); RefillCandidatesBuffer.Add((item5, item)); } } if (RefillCandidatesBuffer.Count == 0) { break; } float num3 = 0f; foreach (var item6 in RefillCandidatesBuffer) { num3 += item6.Score; } float num4 = Random.Range(0f, num3); MonsterConfig selectedConfig = RefillCandidatesBuffer.Last().Config; float num5 = 0f; foreach (var item7 in RefillCandidatesBuffer) { num5 += item7.Score; if (num4 < num5) { (selectedConfig, _) = item7; break; } } EnemyType val3 = configTypeMap[selectedConfig.Name]; if ((Object)(object)val3 != (Object)null) { yield return SpawnEnemyCoroutine(val3, 1); currentCounts[selectedConfig.Name]++; } } } private static List GetMonsterConfigs() { CachedRefillConfigs.Clear(); CachedRefillConfigs.Add(new MonsterConfig("Flowerman", PublicFeatureConfig.RefillFlowermanInit.Value, PublicFeatureConfig.RefillFlowermanMax.Value, PublicFeatureConfig.RefillFlowermanWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("Nutcracker", PublicFeatureConfig.RefillNutcrackerInit.Value, PublicFeatureConfig.RefillNutcrackerMax.Value, PublicFeatureConfig.RefillNutcrackerWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("Crawler", PublicFeatureConfig.RefillCrawlerInit.Value, PublicFeatureConfig.RefillCrawlerMax.Value, PublicFeatureConfig.RefillCrawlerWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("Centipede", PublicFeatureConfig.RefillCentipedeInit.Value, PublicFeatureConfig.RefillCentipedeMax.Value, PublicFeatureConfig.RefillCentipedeWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("HoarderBug", PublicFeatureConfig.RefillHoarderBugInit.Value, PublicFeatureConfig.RefillHoarderBugMax.Value, PublicFeatureConfig.RefillHoarderBugWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("Masked", PublicFeatureConfig.RefillMaskedInit.Value, PublicFeatureConfig.RefillMaskedMax.Value, PublicFeatureConfig.RefillMaskedWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("SandSpider", PublicFeatureConfig.RefillSandSpiderInit.Value, PublicFeatureConfig.RefillSandSpiderMax.Value, PublicFeatureConfig.RefillSandSpiderWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("Butler", PublicFeatureConfig.RefillButlerInit.Value, PublicFeatureConfig.RefillButlerMax.Value, PublicFeatureConfig.RefillButlerWeight.Value)); CachedRefillConfigs.Add(new MonsterConfig("Stingray", PublicFeatureConfig.RefillStingrayInit.Value, PublicFeatureConfig.RefillStingrayMax.Value, PublicFeatureConfig.RefillStingrayWeight.Value)); return CachedRefillConfigs; } private static IEnumerator SpawnEnemyCoroutine(EnemyType type, int count) { if ((Object)(object)type == (Object)null || count <= 0 || (Object)(object)RoundManager.Instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null) { yield break; } GameObject[] aiNodes = RoundManager.Instance.insideAINodes; if (aiNodes == null || aiNodes.Length == 0) { yield break; } ValidSpawnNodesBuffer.Clear(); for (int i = 0; i < aiNodes.Length; i++) { ValidSpawnNodesBuffer.Add(i); } for (int j = 0; j < count; j++) { if (ValidSpawnNodesBuffer.Count == 0) { for (int k = 0; k < aiNodes.Length; k++) { ValidSpawnNodesBuffer.Add(k); } } int index = Random.Range(0, ValidSpawnNodesBuffer.Count); int num = ValidSpawnNodesBuffer[index]; GameObject val = aiNodes[num]; if ((Object)(object)val == (Object)null) { ValidSpawnNodesBuffer.RemoveAt(index); j--; continue; } Vector3 position = val.transform.position; Quaternion val2 = Quaternion.Euler(0f, val.transform.eulerAngles.y, 0f); try { GameObject obj = Object.Instantiate(type.enemyPrefab, position, val2); obj.GetComponentInChildren().Spawn(true); EnemyAI component = obj.GetComponent(); if ((Object)(object)component != (Object)null) { RoundManager.Instance.SpawnedEnemies.Add(component); component.enemyType.isOutsideEnemy = false; component.allAINodes = aiNodes; component.SyncPositionToClients(); if (type.enemyName == "Flowerman") { component.EnableEnemyMesh(true, false, false); } } ValidSpawnNodesBuffer.RemoveAt(index); } catch (Exception arg) { PublicFeatureRuntime.Logger.LogError((object)$"[RefillSystem] Failed to spawn {type.enemyName}: {arg}"); } if (j < count - 1) { yield return (object)new WaitForSeconds(PublicFeatureConfig.SpawnDelay.Value); if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null || StartOfRound.Instance.inShipPhase) { break; } } } } } public static class CurfewExecutionSystem { private sealed class ExecutionTaskState { internal string Reason; internal int AttemptCount; internal float StartedAt; internal float NextRetryAt; internal bool PendingAnnounced; } private const string ExecutionReason = "怯战"; private static readonly Vector3 JudgeDogHiddenPosition = new Vector3(0f, -500f, 0f); private static readonly Dictionary CondemnedPlayers = new Dictionary(); private static readonly Dictionary RescueImmunityExpiry = new Dictionary(); private static readonly HashSet RescuedPlayersThisRound = new HashSet(); private static Coroutine _curfewCoroutine; private static Coroutine _executionCoroutine; private static Coroutine _rescueCoroutine; private static MouthDogAI _judgeDog; private static ulong? _activeExecutionTarget; private static int _judgeDogExecutionSerial; public static void Restart() { Stop(); if (PublicFeatureRuntime.IsEnabled && PublicFeatureConfig.CurfewEnabled.Value && !((Object)(object)PublicFeatureRuntime.PluginInstance == (Object)null)) { _curfewCoroutine = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(CurfewRoutine()); _executionCoroutine = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(ProcessExecutionLoop()); } } public static void Stop() { if ((Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null) { if (_curfewCoroutine != null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(_curfewCoroutine); } if (_executionCoroutine != null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(_executionCoroutine); } if (_rescueCoroutine != null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(_rescueCoroutine); } } _curfewCoroutine = null; _executionCoroutine = null; _rescueCoroutine = null; } public static void ResetRoundState() { CondemnedPlayers.Clear(); RescueImmunityExpiry.Clear(); RescuedPlayersThisRound.Clear(); _activeExecutionTarget = null; _judgeDog = null; _judgeDogExecutionSerial = 0; } private static IEnumerator CurfewRoutine() { while ((Object)(object)TimeOfDay.Instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null) { yield return null; } float duration = Mathf.Max(0f, PublicFeatureConfig.CurfewDuration.Value); HUDManager instance = HUDManager.Instance; if (instance != null) { instance.AddTextToChatOnServer("可以怕,但不能怂," + FormatCurfewDuration(duration) + "s后在设施外的玩家将被无情处决", -1); } while (!StartOfRound.Instance.shipHasLanded) { if (!CanWaitForLanding()) { yield break; } yield return (object)new WaitForSeconds(0.5f); } float landingTime = Time.realtimeSinceStartup; while (Time.realtimeSinceStartup - landingTime < duration) { if (!IsRoundValidForCurfew()) { yield break; } yield return (object)new WaitForSeconds(1f); } HUDManager instance2 = HUDManager.Instance; if (instance2 != null) { instance2.AddTextToChatOnServer("处决开始!所有设施外人员将被无情处决", -1); } while (IsRoundValidForCurfew()) { CondemnOutsidePlayersOnce(); yield return (object)new WaitForSeconds(Mathf.Max(1f, PublicFeatureConfig.CurfewRepeatInterval.Value)); } } private static IEnumerator ProcessExecutionLoop() { while (PublicFeatureRuntime.IsEnabled && PublicFeatureConfig.CurfewEnabled.Value) { if (!IsRoundValidForCurfew()) { yield return (object)new WaitForSeconds(1f); continue; } float time = Time.time; RemoveStaleCondemnedPlayers(); if (_activeExecutionTarget.HasValue) { ProcessActiveExecutionTarget(time); } if (!_activeExecutionTarget.HasValue) { KeyValuePair keyValuePair = CondemnedPlayers.OrderBy((KeyValuePair kv) => kv.Value.StartedAt).FirstOrDefault(); if (!keyValuePair.Equals(default(KeyValuePair))) { _activeExecutionTarget = keyValuePair.Key; } } yield return (object)new WaitForSeconds(1f); } } private static void CondemnOutsidePlayersOnce() { if (StartOfRound.Instance?.allPlayerScripts == null) { return; } PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && val.isPlayerControlled && !val.isPlayerDead && !val.isInsideFactory && !IsRescueImmune(val.playerClientId)) { ExecutePlayer(val, "怯战"); } } } private static void ExecutePlayer(PlayerControllerB player, string reason) { if (!((Object)(object)player == (Object)null) && !player.isPlayerDead) { if (CondemnedPlayers.TryGetValue(player.playerClientId, out var value)) { value.Reason = reason; return; } CondemnedPlayers[player.playerClientId] = new ExecutionTaskState { Reason = reason, AttemptCount = 0, StartedAt = Time.time, NextRetryAt = Time.time, PendingAnnounced = false }; } } private static void ProcessActiveExecutionTarget(float now) { ulong value = _activeExecutionTarget.Value; if (!CondemnedPlayers.TryGetValue(value, out var value2)) { _activeExecutionTarget = null; return; } PlayerControllerB val = FindPlayer(value); if ((Object)(object)val == (Object)null) { CondemnedPlayers.Remove(value); _activeExecutionTarget = null; } else if (val.isPlayerDead) { HUDManager instance = HUDManager.Instance; if (instance != null) { instance.AddTextToChatOnServer("" + val.playerUsername + "已被处决", -1); } CondemnedPlayers.Remove(value); _activeExecutionTarget = null; } else if (IsRescueImmune(value) || val.isInsideFactory) { CondemnedPlayers.Remove(value); _activeExecutionTarget = null; } else { if (now < value2.NextRetryAt) { return; } if (!value2.PendingAnnounced) { HUDManager instance2 = HUDManager.Instance; if (instance2 != null) { instance2.AddTextToChatOnServer("" + val.playerUsername + "因怯战已被审判犬追猎", -1); } value2.PendingAnnounced = true; } if (!TryExecutePlayerByJudgeDog(val)) { KillPlayerDirectlyForExecution(val); } value2.AttemptCount++; value2.NextRetryAt = now + Mathf.Max(1f, PublicFeatureConfig.CurfewRepeatInterval.Value); } } private static bool TryExecutePlayerByJudgeDog(PlayerControllerB activePlayer) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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) if ((Object)(object)activePlayer == (Object)null || activePlayer.isPlayerDead) { return false; } MouthDogAI orCreateJudgeDog = GetOrCreateJudgeDog(); if ((Object)(object)orCreateJudgeDog == (Object)null) { return false; } Vector3 val = ((Component)activePlayer).transform.position + ((Component)activePlayer).transform.forward * -1.5f; ((EnemyAI)orCreateJudgeDog).EnableEnemyMesh(true, false, false); ((Component)orCreateJudgeDog).transform.position = val; ((EnemyAI)orCreateJudgeDog).serverPosition = val; ((Component)orCreateJudgeDog).transform.LookAt(((Component)activePlayer).transform.position); ((EnemyAI)orCreateJudgeDog).SyncPositionToClients(); activePlayer.inAnimationWithEnemy = (EnemyAI)(object)orCreateJudgeDog; orCreateJudgeDog.KillPlayerServerRpc((int)activePlayer.playerClientId); int executionSerial = ++_judgeDogExecutionSerial; BaseUnityPlugin pluginInstance = PublicFeatureRuntime.PluginInstance; if (pluginInstance != null) { ((MonoBehaviour)pluginInstance).StartCoroutine(MoveJudgeDogBackAfterExecution(orCreateJudgeDog, activePlayer, executionSerial)); } return true; } private static MouthDogAI GetOrCreateJudgeDog() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_judgeDog != (Object)null && !((EnemyAI)_judgeDog).isEnemyDead) { return _judgeDog; } MouthDogAI[] array = Object.FindObjectsOfType(); foreach (MouthDogAI val in array) { if ((Object)(object)val != (Object)null && ((Object)((Component)val).gameObject).name.Contains("PublicJudgeDog") && !((EnemyAI)val).isEnemyDead) { _judgeDog = val; return _judgeDog; } } EnemyType val2 = FindMouthDogType(); if ((Object)(object)val2 == (Object)null || (Object)(object)val2.enemyPrefab == (Object)null) { return null; } GameObject obj = Object.Instantiate(val2.enemyPrefab, JudgeDogHiddenPosition, Quaternion.identity); ((Object)obj).name = "PublicJudgeDog"; NetworkObject componentInChildren = obj.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null && !componentInChildren.IsSpawned) { componentInChildren.Spawn(true); } _judgeDog = obj.GetComponent(); if ((Object)(object)_judgeDog != (Object)null) { ((EnemyAI)_judgeDog).isOutside = true; if ((Object)(object)((EnemyAI)_judgeDog).agent != (Object)null) { ((Behaviour)((EnemyAI)_judgeDog).agent).enabled = false; } ((EnemyAI)_judgeDog).EnableEnemyMesh(false, false, false); ((EnemyAI)_judgeDog).SwitchToBehaviourState(0); } return _judgeDog; } private static EnemyType FindMouthDogType() { EnemyType val = PublicFeatureEnemyCatalog.GetEnemyTypeByName("MouthDog"); if ((Object)(object)val != (Object)null) { return val; } RoundManager instance = RoundManager.Instance; if ((Object)(object)instance?.currentLevel == (Object)null) { return null; } if (instance.currentLevel.OutsideEnemies != null) { val = instance.currentLevel.OutsideEnemies.Select((SpawnableEnemyWithRarity e) => e.enemyType).FirstOrDefault((Func)((EnemyType t) => (Object)(object)t != (Object)null && t.enemyName.IndexOf("Dog", StringComparison.OrdinalIgnoreCase) >= 0)); } if ((Object)(object)val == (Object)null && instance.currentLevel.DaytimeEnemies != null) { val = instance.currentLevel.DaytimeEnemies.Select((SpawnableEnemyWithRarity e) => e.enemyType).FirstOrDefault((Func)((EnemyType t) => (Object)(object)t != (Object)null && t.enemyName.IndexOf("Dog", StringComparison.OrdinalIgnoreCase) >= 0)); } return val; } private static IEnumerator MoveJudgeDogBackAfterExecution(MouthDogAI dog, PlayerControllerB target, int executionSerial) { float startTime = Time.timeSinceLevelLoad; yield return (object)new WaitUntil((Func)(() => (Object)(object)target == (Object)null || (Object)(object)target.deadBody != (Object)null || Time.timeSinceLevelLoad - startTime > 2f)); yield return (object)new WaitForSeconds(3f); if (executionSerial == _judgeDogExecutionSerial && (Object)(object)dog != (Object)null && !((EnemyAI)dog).isEnemyDead) { ((EnemyAI)dog).EnableEnemyMesh(false, false, false); ((EnemyAI)dog).SwitchToBehaviourState(0); ((Component)dog).transform.position = JudgeDogHiddenPosition; ((EnemyAI)dog).serverPosition = JudgeDogHiddenPosition; ((EnemyAI)dog).SyncPositionToClients(); } } private static void KillPlayerDirectlyForExecution(PlayerControllerB player) { //IL_003b: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || player.isPlayerDead) { return; } try { if (((NetworkBehaviour)player).IsOwner) { player.KillPlayer(Vector3.zero, true, (CauseOfDeath)0, 0, default(Vector3), false); } else { player.DamagePlayerFromOtherClientServerRpc(1000, Vector3.zero, (int)player.playerClientId); } } catch (Exception ex) { ManualLogSource logger = PublicFeatureRuntime.Logger; if (logger != null) { logger.LogWarning((object)("[CurfewExecution] Direct fallback failed for " + player.playerUsername + ": " + ex.Message)); } } } private static void RemoveStaleCondemnedPlayers() { List list = new List(); foreach (KeyValuePair condemnedPlayer in CondemnedPlayers) { if ((Object)(object)FindPlayer(condemnedPlayer.Key) == (Object)null) { list.Add(condemnedPlayer.Key); } } foreach (ulong item in list) { CondemnedPlayers.Remove(item); if (_activeExecutionTarget == item) { _activeExecutionTarget = null; } } } public static bool TryStartRescue(PlayerControllerB targetPlayer) { if (!PublicFeatureConfig.RescueEnabled.Value || (Object)(object)targetPlayer == (Object)null || !IsRoundValidForRescue()) { return false; } if (targetPlayer.isPlayerDead || !targetPlayer.isPlayerControlled || targetPlayer.isInHangarShipRoom) { return false; } ulong playerClientId = targetPlayer.playerClientId; if (RescuedPlayersThisRound.Contains(playerClientId) || IsRescueImmune(playerClientId) || _rescueCoroutine != null) { return false; } if ((Object)(object)PublicFeatureRuntime.PluginInstance == (Object)null) { return false; } _rescueCoroutine = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(RescueTeleportCoroutine(targetPlayer)); return true; } private static IEnumerator RescueTeleportCoroutine(PlayerControllerB targetPlayer) { ShipTeleporter teleporter = ((IEnumerable)Object.FindObjectsOfType()).FirstOrDefault((Func)((ShipTeleporter teleporterObject) => (Object)(object)teleporterObject != (Object)null && !teleporterObject.isInverseTeleporter)); if ((Object)(object)teleporter == (Object)null) { ShowRescueFailure("救援失败:飞船上没有普通传送器。"); _rescueCoroutine = null; yield break; } if ((Object)(object)teleporter.buttonTrigger == (Object)null || !teleporter.buttonTrigger.interactable) { ShowRescueFailure("救援失败:传送器正在冷却。"); _rescueCoroutine = null; yield break; } if ((Object)(object)StartOfRound.Instance?.mapScreen == (Object)null || StartOfRound.Instance.mapScreen.radarTargets == null) { ShowRescueFailure("救援失败:雷达尚未准备好。"); _rescueCoroutine = null; yield break; } int num = -1; for (int num2 = 0; num2 < StartOfRound.Instance.mapScreen.radarTargets.Count; num2++) { Transform val = StartOfRound.Instance.mapScreen.radarTargets[num2]?.transform; PlayerControllerB val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponentInChildren()); if ((Object)(object)val2 != (Object)null && val2.playerClientId == targetPlayer.playerClientId) { num = num2; break; } } if (num < 0) { ShowRescueFailure("救援失败:无法在雷达上定位该玩家。"); _rescueCoroutine = null; yield break; } if (!TrySetRadarTarget(num)) { ShowRescueFailure("救援失败:雷达目标切换失败。"); _rescueCoroutine = null; yield break; } yield return (object)new WaitForSeconds(0.3f); if (!IsRoundValidForRescue() || (Object)(object)targetPlayer == (Object)null || targetPlayer.isPlayerDead || targetPlayer.isInHangarShipRoom) { ShowRescueFailure("救援取消:目标状态已改变。"); _rescueCoroutine = null; yield break; } if (!TryActivateTeleporter(teleporter)) { ShowRescueFailure("救援失败:传送器调用失败。"); _rescueCoroutine = null; yield break; } yield return (object)new WaitForSeconds(1.5f); if (!IsRoundValidForRescue() || (Object)(object)targetPlayer == (Object)null || targetPlayer.isPlayerDead || !targetPlayer.isInHangarShipRoom) { ShowRescueFailure("救援失败:传送器未能将目标送回飞船。"); _rescueCoroutine = null; yield break; } ulong playerClientId = targetPlayer.playerClientId; RescuedPlayersThisRound.Add(playerClientId); RescueImmunityExpiry[playerClientId] = Time.realtimeSinceStartup + Mathf.Max(1f, PublicFeatureConfig.RescueImmunityDuration.Value); CondemnedPlayers.Remove(playerClientId); if (_activeExecutionTarget == playerClientId) { _activeExecutionTarget = null; } HUDManager instance = HUDManager.Instance; if (instance != null) { instance.AddTextToChatOnServer($"[救援] {targetPlayer.playerUsername} 已被救回飞船,{PublicFeatureConfig.RescueImmunityDuration.Value:0.#}秒内免处决。", -1); } _rescueCoroutine = null; } private static bool TrySetRadarTarget(int targetIndex) { try { StartOfRound.Instance.mapScreen.SwitchRadarTargetServerRpc(targetIndex); return true; } catch (Exception ex) { ManualLogSource logger = PublicFeatureRuntime.Logger; if (logger != null) { logger.LogWarning((object)("[Rescue] Radar target selection failed: " + ex.Message)); } return false; } } private static bool TryActivateTeleporter(ShipTeleporter teleporter) { try { teleporter.PressTeleportButtonServerRpc(); return true; } catch (Exception ex) { ManualLogSource logger = PublicFeatureRuntime.Logger; if (logger != null) { logger.LogWarning((object)("[Rescue] Teleporter activation failed: " + ex.Message)); } return false; } } private static bool IsRescueImmune(ulong playerId) { if (!RescueImmunityExpiry.TryGetValue(playerId, out var value)) { return false; } if (Time.realtimeSinceStartup < value) { return true; } RescueImmunityExpiry.Remove(playerId); return false; } private static void ShowRescueFailure(string message) { HUDManager instance = HUDManager.Instance; if (instance != null) { instance.AddTextToChatOnServer("" + message + "", -1); } } private static PlayerControllerB FindPlayer(ulong playerClientId) { return StartOfRound.Instance?.allPlayerScripts?.FirstOrDefault((Func)((PlayerControllerB p) => (Object)(object)p != (Object)null && p.playerClientId == playerClientId)); } private static bool IsRoundValidForRescue() { if (PublicFeatureRuntime.IsEnabled && (Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)RoundManager.Instance != (Object)null && (((NetworkBehaviour)RoundManager.Instance).IsServer || ((NetworkBehaviour)RoundManager.Instance).IsHost) && StartOfRound.Instance.shipHasLanded) { return !StartOfRound.Instance.inShipPhase; } return false; } private static string FormatCurfewDuration(float duration) { return duration.ToString("0.##", CultureInfo.InvariantCulture); } private static bool IsRoundValidForCurfew() { if (!PublicFeatureRuntime.IsEnabled || !PublicFeatureConfig.CurfewEnabled.Value) { return false; } if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null) { return false; } if (!((NetworkBehaviour)RoundManager.Instance).IsServer && !((NetworkBehaviour)RoundManager.Instance).IsHost) { return false; } if (StartOfRound.Instance.inShipPhase || !StartOfRound.Instance.shipHasLanded) { return false; } return true; } private static bool CanWaitForLanding() { if (!PublicFeatureRuntime.IsEnabled || !PublicFeatureConfig.CurfewEnabled.Value) { return false; } if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null) { return false; } if (!((NetworkBehaviour)RoundManager.Instance).IsServer && !((NetworkBehaviour)RoundManager.Instance).IsHost) { return false; } return true; } } [HarmonyPatch(typeof(RoundManager))] public static class WorldRulePatches { [HarmonyPatch("SpawnMapObjects")] [HarmonyPrefix] private static void SpawnMapObjectsPrefix(RoundManager __instance) { if (!((NetworkBehaviour)__instance).IsHost || !PublicFeatureConfig.DisableHazards.Value || __instance.currentLevel.indoorMapHazards == null) { return; } IndoorMapHazard[] indoorMapHazards = __instance.currentLevel.indoorMapHazards; foreach (IndoorMapHazard val in indoorMapHazards) { if (!((Object)(object)val.hazardType == (Object)null) && !((Object)(object)val.hazardType.prefabToSpawn == (Object)null)) { string name = ((Object)val.hazardType.prefabToSpawn).name; if (name.Contains("Landmine") || name.Contains("Turret") || name.Contains("Spike")) { val.numberToSpawn = AnimationCurve.Constant(0f, 1f, 0f); } } } } [HarmonyPatch("PlotOutEnemiesForNextHour")] [HarmonyPrefix] private static bool PlotOutEnemiesPrefix(RoundManager __instance) { if (((NetworkBehaviour)__instance).IsHost) { return !PublicFeatureConfig.DisableVanillaEnemies.Value; } return true; } [HarmonyPatch("SpawnEnemiesOutside")] [HarmonyPrefix] private static bool SpawnEnemiesOutsidePrefix(RoundManager __instance) { if (((NetworkBehaviour)__instance).IsHost) { return !PublicFeatureConfig.DisableVanillaEnemies.Value; } return true; } [HarmonyPatch("SpawnDaytimeEnemiesOutside")] [HarmonyPrefix] private static bool SpawnDaytimeEnemiesOutsidePrefix(RoundManager __instance) { if (((NetworkBehaviour)__instance).IsHost) { return !PublicFeatureConfig.DisableVanillaEnemies.Value; } return true; } [HarmonyPatch("SpawnScrapInLevel")] [HarmonyPrefix] private static bool SpawnScrapInLevelPrefix(RoundManager __instance) { if (((NetworkBehaviour)__instance).IsHost) { return !PublicFeatureConfig.DisableScrap.Value; } return true; } } [HarmonyPatch(typeof(Terminal), "Awake")] public static class PublicTerminalPatch { [HarmonyPostfix] private static void Postfix(Terminal __instance) { if (__instance.groupCredits == 60) { __instance.groupCredits = PublicFeatureConfig.StartingCredits.Value; } } } [HarmonyPatch(typeof(TimeOfDay), "Awake")] public static class PublicTimeOfDayPatch { [HarmonyPostfix] private static void Postfix(TimeOfDay __instance) { if (__instance.quotaVariables != null) { __instance.quotaVariables.startingCredits = PublicFeatureConfig.StartingCredits.Value; } } } [HarmonyPatch(typeof(DoorLock), "LockDoor")] public static class PublicDoorLockPatch { [HarmonyPostfix] private static void Postfix(DoorLock __instance) { if (PublicFeatureConfig.NoLockedDoors.Value && !((Object)(object)RoundManager.Instance == (Object)null) && (((NetworkBehaviour)RoundManager.Instance).IsServer || ((NetworkBehaviour)RoundManager.Instance).IsHost)) { __instance.UnlockDoorServerRpc(); } } } [HarmonyPatch(typeof(ButlerEnemyAI), "Start")] public static class PublicButlerDoorPatch { [HarmonyPostfix] private static void Postfix(ButlerEnemyAI __instance) { if (PublicFeatureConfig.ButlerInstantOpenDoors.Value) { ((EnemyAI)__instance).openDoorSpeedMultiplier = 100f; } } } [HarmonyPatch(typeof(PlayerControllerB), "DamagePlayerFromOtherClientClientRpc")] public static class PublicFriendlyFirePatch { [HarmonyPrefix] private static bool Prefix(PlayerControllerB __instance, int damageAmount, Vector3 hitDirection, int playerWhoHit, int newHealthAmount) { if (!PublicFeatureConfig.DisableFriendlyMeleeDamage.Value) { return true; } if (damageAmount <= 0 || (Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null) { return true; } CentipedeAI[] array = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i].clingingToPlayer == (Object)(object)__instance) { return true; } } if (playerWhoHit >= 0 && playerWhoHit < StartOfRound.Instance.allPlayerScripts.Length) { PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[playerWhoHit]; if ((Object)(object)val != (Object)null && (Object)(object)val.currentlyHeldObjectServer != (Object)null && (val.currentlyHeldObjectServer is Shovel || val.currentlyHeldObjectServer is KnifeItem)) { return false; } } return true; } } [HarmonyPatch(typeof(ShotgunItem), "ShootGun")] public static class PublicNutcrackerShotgunPatch { [HarmonyPrefix] private static bool Prefix(ShotgunItem __instance, Vector3 shotgunPosition, Vector3 shotgunForward, ref RaycastHit[] ___enemyColliders, EnemyAI ___heldByEnemy) { //IL_001a: 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 (!PublicFeatureConfig.NutcrackerNoFriendlyFire.Value) { return true; } if (!(___heldByEnemy is NutcrackerEnemyAI)) { return true; } RunNutcrackerShotgunWithoutMonsterDamage(__instance, shotgunPosition, shotgunForward); return false; } private static void RunNutcrackerShotgunWithoutMonsterDamage(ShotgunItem shotgun, Vector3 shotgunPosition, Vector3 shotgunForward) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) shotgun.isReloading = false; bool flag = ((GrabbableObject)shotgun).isHeld && (Object)(object)((GrabbableObject)shotgun).playerHeldBy != (Object)null && (Object)(object)((GrabbableObject)shotgun).playerHeldBy == (Object)(object)GameNetworkManager.Instance.localPlayerController; if (flag) { ((GrabbableObject)shotgun).playerHeldBy.playerBodyAnimator.SetTrigger("ShootShotgun"); } RoundManager.PlayRandomClip(shotgun.gunShootAudio, shotgun.gunShootSFX, true, 1f, 1840, 1000); WalkieTalkie.TransmitOneShotAudio(shotgun.gunShootAudio, shotgun.gunShootSFX[0], 1f); shotgun.gunShootParticle.Play(true); shotgun.shellsLoaded = Mathf.Clamp(shotgun.shellsLoaded - 1, 0, 2); PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController; if (!((Object)(object)localPlayerController == (Object)null)) { float num = Vector3.Distance(((Component)localPlayerController).transform.position, ((Component)shotgun.shotgunRayPoint).transform.position); bool flag2 = false; int num2 = 0; float num3 = 0f; Vector3 val = localPlayerController.playerCollider.ClosestPoint(shotgunPosition); if (!flag && !Physics.Linecast(shotgunPosition, val, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1) && Vector3.Angle(shotgunForward, val - shotgunPosition) < 30f) { flag2 = true; } if (num < 5f) { num3 = 0.8f; HUDManager.Instance.ShakeCamera((ScreenShakeType)1); num2 = 100; } if (num < 15f) { num3 = 0.5f; HUDManager.Instance.ShakeCamera((ScreenShakeType)1); num2 = 100; } else if (num < 23f) { HUDManager.Instance.ShakeCamera((ScreenShakeType)0); num2 = 40; } else if (num < 30f) { num2 = 20; } if (num3 > 0f && SoundManager.Instance.timeSinceEarsStartedRinging > 16f) { ((MonoBehaviour)shotgun).StartCoroutine(DelayedEarsRinging(num3)); } Ray val2 = default(Ray); ((Ray)(ref val2))..ctor(shotgunPosition, shotgunForward); RaycastHit val3 = default(RaycastHit); if (Physics.Raycast(val2, ref val3, 30f, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1)) { ((Component)shotgun.gunBulletsRicochetAudio).transform.position = ((Ray)(ref val2)).GetPoint(((RaycastHit)(ref val3)).distance - 0.5f); shotgun.gunBulletsRicochetAudio.Play(); } if (flag2) { localPlayerController.DamagePlayer(num2, true, true, (CauseOfDeath)7, 0, false, shotgun.shotgunRayPoint.forward * 30f); } } } private static IEnumerator DelayedEarsRinging(float effectSeverity) { yield return (object)new WaitForSeconds(0.6f); SoundManager.Instance.earsRingingTimer = effectSeverity; } } public static class ItemAndLootPatches { private static class HoarderBugItemCacheCleaner { public static void CleanupHoarderBugItemCaches() { if (HoarderBugAI.HoarderBugItems != null) { HoarderBugAI.HoarderBugItems.RemoveAll((HoarderBugItem item) => item == null || (Object)(object)item.itemGrabbableObject == (Object)null || (Object)(object)((Component)item.itemGrabbableObject).gameObject == (Object)null || item.itemGrabbableObject.deactivated || item.itemGrabbableObject.itemUsedUp); } if (HoarderBugAI.grabbableObjectsInMap != null) { HoarderBugAI.grabbableObjectsInMap.RemoveAll((GameObject obj) => (Object)(object)obj == (Object)null); } CleanupLiveHoarderBugReferences(null); } public static void RemoveHoarderBugItem(GrabbableObject grabbable) { if ((Object)(object)grabbable == (Object)null) { return; } if (HoarderBugAI.HoarderBugItems != null) { HoarderBugAI.HoarderBugItems.RemoveAll((HoarderBugItem item) => item == null || (Object)(object)item.itemGrabbableObject == (Object)null || (Object)(object)item.itemGrabbableObject == (Object)(object)grabbable); } if (HoarderBugAI.grabbableObjectsInMap != null) { GameObject grabbableObject = ((Component)grabbable).gameObject; HoarderBugAI.grabbableObjectsInMap.RemoveAll((GameObject obj) => (Object)(object)obj == (Object)null || (Object)(object)obj == (Object)(object)grabbableObject); } CleanupLiveHoarderBugReferences(grabbable); } private static void CleanupLiveHoarderBugReferences(GrabbableObject grabbable) { HoarderBugAI[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (HoarderBugAI val in array) { if ((Object)(object)val == (Object)null) { continue; } if ((Object)(object)val.targetItem == (Object)null || (Object)(object)val.targetItem == (Object)(object)grabbable) { val.targetItem = null; } if (val.heldItem != null) { GrabbableObject itemGrabbableObject = val.heldItem.itemGrabbableObject; if ((Object)(object)itemGrabbableObject == (Object)null || (Object)(object)itemGrabbableObject == (Object)(object)grabbable) { val.heldItem = null; } } } } } [HarmonyPatch(typeof(HoarderBugAI), "DoAIInterval")] private static class HoarderBugStaleItemCleanupPatch { [HarmonyPrefix] private static void Prefix() { HoarderBugItemCacheCleaner.CleanupHoarderBugItemCaches(); } } [HarmonyPatch(typeof(GrabbableObject), "OnDestroy")] private static class HoarderBugDestroyedItemCleanupPatch { [HarmonyPrefix] private static void Prefix(GrabbableObject __instance) { HoarderBugItemCacheCleaner.RemoveHoarderBugItem(__instance); } } [HarmonyPatch(typeof(NutcrackerEnemyAI), "DropGun")] private static class NutcrackerDropGunPatch { [HarmonyPrefix] private static bool Prefix(NutcrackerEnemyAI __instance) { if (PublicFeatureConfig.NutcrackerDropGunAndShells.Value) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.gun == (Object)null) { return false; } ShotgunItem gun = __instance.gun; __instance.gun = null; if (((NetworkBehaviour)__instance).IsServer || ((NetworkBehaviour)__instance).IsHost) { if ((Object)(object)((NetworkBehaviour)gun).NetworkObject != (Object)null && ((NetworkBehaviour)gun).NetworkObject.IsSpawned) { ((NetworkBehaviour)gun).NetworkObject.Despawn(true); } else if ((Object)(object)((Component)gun).gameObject != (Object)null) { Object.Destroy((Object)(object)((Component)gun).gameObject); } } return false; } } [HarmonyPatch(typeof(NutcrackerEnemyAI), "SpawnShotgunShells")] private static class NutcrackerShellDropPatch { [HarmonyPrefix] private static bool Prefix() { return PublicFeatureConfig.NutcrackerDropGunAndShells.Value; } } [HarmonyPatch(typeof(ButlerEnemyAI), "ButlerBlowUpAndPop")] private static class ButlerDeathDropPatch { [HarmonyPrefix] private static bool Prefix(ButlerEnemyAI __instance, ref IEnumerator __result) { if (PublicFeatureConfig.ButlerDropKnifeAndBees.Value) { return true; } __result = ButlerBlowUpWithoutDrops(__instance); return false; } private static IEnumerator ButlerBlowUpWithoutDrops(ButlerEnemyAI instance) { ((EnemyAI)instance).creatureAnimator.SetTrigger("Popping"); ((EnemyAI)instance).creatureAnimator.SetLayerWeight(1, 0f); instance.popAudio.PlayOneShot(((EnemyAI)instance).enemyType.audioClips[3]); yield return (object)new WaitForSeconds(1.1f); ((EnemyAI)instance).creatureAnimator.SetBool("popFinish", true); instance.popAudio.Play(); instance.popAudioFar.Play(); instance.popParticle.Play(true); float num = Vector3.Distance(((Component)GameNetworkManager.Instance.localPlayerController).transform.position, ((Component)instance).transform.position); if (num < 8f) { Landmine.SpawnExplosion(((Component)instance).transform.position + Vector3.up * 0.15f, false, 0f, 2f, 30, 80f, (GameObject)null, false); HUDManager.Instance.ShakeCamera((ScreenShakeType)1); SoundManager.Instance.earsRingingTimer = 0.8f; } else if (num < 27f) { HUDManager.Instance.ShakeCamera((ScreenShakeType)1); } } } [HarmonyPatch(typeof(SandSpiderAI), "KillEnemy")] private static class SandSpiderWebCleanupPatch { [HarmonyPostfix] private static void Postfix(SandSpiderAI __instance) { if ((Object)(object)__instance == (Object)null || (!((NetworkBehaviour)__instance).IsServer && !((NetworkBehaviour)__instance).IsHost)) { return; } try { for (int num = __instance.webTraps.Count - 1; num >= 0; num--) { __instance.BreakWebServerRpc(num, 1); } } catch (Exception arg) { PublicFeatureRuntime.Logger.LogWarning((object)$"[SandSpider] Failed to clear webs on death: {arg}"); } } } [HarmonyPatch(typeof(EnemyAI), "KillEnemy")] private static class EnemyKillDropPatch { [HarmonyPostfix] private static void Postfix(EnemyAI __instance, bool destroy) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } if (!destroy) { TryScheduleCorpseCleanup(__instance); } if (!destroy && PublicFeatureConfig.LootDropsEnabled.Value && !((Object)(object)RoundManager.Instance == (Object)null) && ((NetworkBehaviour)RoundManager.Instance).IsServer && !((Object)(object)__instance.enemyType == (Object)null) && SupportsCombatDrops(__instance) && !PublicFeatureRuntime.IsPrivateBossEnemy(__instance)) { EnsureLootItemsCached(); if ((Object)(object)_tzpItem != (Object)null && Random.value <= PublicFeatureConfig.TZPDropChance.Value) { SpawnLoot(((Component)__instance).transform.position, _tzpItem); } if ((Object)(object)_stunGrenadeItem != (Object)null && Random.value <= PublicFeatureConfig.StunGrenadeDropChance.Value) { SpawnLoot(((Component)__instance).transform.position, _stunGrenadeItem); } } } } [HarmonyPatch(typeof(PlayerControllerB), "DamagePlayerClientRpc")] private static class PlayerDamageSyncPatch { [HarmonyPostfix] private static void Postfix(PlayerControllerB __instance, int newHealthAmount) { if (((NetworkBehaviour)__instance).IsOwner && newHealthAmount >= 20 && __instance.criticallyInjured) { __instance.criticallyInjured = false; if ((Object)(object)__instance.playerBodyAnimator != (Object)null) { __instance.playerBodyAnimator.SetBool("Limp", false); } __instance.bleedingHeavily = false; } } } [HarmonyPatch(typeof(TetraChemicalItem), "Update")] private static class TZPHealingPatch { [HarmonyPostfix] private static void Postfix(TetraChemicalItem __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)((NetworkBehaviour)__instance).NetworkObject == (Object)null || !PublicFeatureConfig.TZPHealingEnabled.Value || (Object)(object)((NetworkBehaviour)__instance).NetworkManager == (Object)null || !((NetworkBehaviour)__instance).NetworkManager.IsServer) { return; } if (UsedTZPIndices.Contains(((NetworkBehaviour)__instance).NetworkObjectId)) { ulong networkObjectId = ((NetworkBehaviour)__instance).NetworkObjectId; if ((Object)(object)((GrabbableObject)__instance).playerHeldBy == (Object)null) { if (!UsedTZPOnGroundSince.TryGetValue(networkObjectId, out var value)) { UsedTZPOnGroundSince[networkObjectId] = Time.time; } else if (Time.time - value > 2f) { UsedTZPOnGroundSince.Remove(networkObjectId); if (((NetworkBehaviour)__instance).NetworkObject.IsSpawned) { ((NetworkBehaviour)__instance).NetworkObject.Despawn(true); } } } else { UsedTZPOnGroundSince.Remove(networkObjectId); } } else { if (!(bool)AccessTools.Field(typeof(TetraChemicalItem), "emittingGas").GetValue(__instance) || (Object)(object)((GrabbableObject)__instance).playerHeldBy == (Object)null) { return; } PlayerControllerB playerHeldBy = ((GrabbableObject)__instance).playerHeldBy; if (!playerHeldBy.isPlayerDead) { HealPlayerFromTzpNetworked(playerHeldBy, PublicFeatureConfig.TZPHealAmount.Value); if (playerHeldBy.criticallyInjured) { playerHeldBy.MakeCriticallyInjured(false); } UsedTZPIndices.Add(((NetworkBehaviour)__instance).NetworkObjectId); } } } } private static readonly HashSet UsedTZPIndices = new HashSet(); private static readonly Dictionary UsedTZPOnGroundSince = new Dictionary(); private static readonly Dictionary PendingTzpCriticalHealTargets = new Dictionary(); private static readonly Dictionary PendingTzpCriticalHealCoroutines = new Dictionary(); private static Item _tzpItem; private static Item _stunGrenadeItem; public static void ResetRoundState() { UsedTZPIndices.Clear(); UsedTZPOnGroundSince.Clear(); PendingTzpCriticalHealTargets.Clear(); foreach (Coroutine value in PendingTzpCriticalHealCoroutines.Values) { if (value != null && (Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(value); } } PendingTzpCriticalHealCoroutines.Clear(); _tzpItem = null; _stunGrenadeItem = null; } private static void EnsureLootItemsCached() { if (((Object)(object)_tzpItem != (Object)null && (Object)(object)_stunGrenadeItem != (Object)null) || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.allItemsList == (Object)null) { return; } foreach (Item items in StartOfRound.Instance.allItemsList.itemsList) { if ((Object)(object)_tzpItem == (Object)null && items.itemName == "TZP-Inhalant") { _tzpItem = items; } else if ((Object)(object)_stunGrenadeItem == (Object)null && items.itemName == "Stun grenade") { _stunGrenadeItem = items; } } } private static bool SupportsCombatDrops(EnemyAI enemy) { if (!(enemy is FlowermanAI) && !(enemy is ButlerEnemyAI) && !(enemy is NutcrackerEnemyAI) && !(enemy is MaskedPlayerEnemy) && !(enemy is SandSpiderAI) && !(enemy is CentipedeAI) && !(enemy is CrawlerAI) && !(enemy is HoarderBugAI) && !(enemy is BaboonBirdAI) && !(enemy is StingrayAI) && !(enemy is CaveDwellerAI)) { return enemy is GiantKiwiAI; } return true; } private static bool SupportsCorpseCleanup(EnemyAI enemy) { if (!(enemy is FlowermanAI) && !(enemy is ButlerEnemyAI) && !(enemy is NutcrackerEnemyAI) && !(enemy is MaskedPlayerEnemy) && !(enemy is SandSpiderAI) && !(enemy is CentipedeAI) && !(enemy is CrawlerAI) && !(enemy is HoarderBugAI) && !(enemy is BaboonBirdAI) && !(enemy is StingrayAI) && !(enemy is CaveDwellerAI)) { return enemy is GiantKiwiAI; } return true; } private static void TryScheduleCorpseCleanup(EnemyAI enemy) { if (!PublicFeatureConfig.CorpseCleanupEnabled.Value || (Object)(object)enemy == (Object)null || !SupportsCorpseCleanup(enemy) || PublicFeatureRuntime.IsPrivateBossEnemy(enemy) || (!((NetworkBehaviour)enemy).IsServer && !((NetworkBehaviour)enemy).IsHost)) { return; } try { if (Traverse.Create((object)enemy).Field("deathCoroutine").Value == null) { float delay = Mathf.Max(0f, PublicFeatureConfig.CorpseCleanupDelay.Value); Coroutine value = ((MonoBehaviour)enemy).StartCoroutine(CorpseCleanupCoroutine(enemy, delay)); Traverse.Create((object)enemy).Field("deathCoroutine").SetValue((object)value); } } catch (Exception arg) { PublicFeatureRuntime.Logger.LogError((object)$"[CorpseCleanup] Failed to schedule cleanup for {enemy.enemyType?.enemyName ?? ((Object)enemy).name}: {arg}"); } } private static IEnumerator CorpseCleanupCoroutine(EnemyAI enemy, float delay) { yield return (object)new WaitForSeconds(delay); if (!((Object)(object)enemy == (Object)null)) { if ((Object)(object)((NetworkBehaviour)enemy).NetworkObject != (Object)null && ((NetworkBehaviour)enemy).NetworkObject.IsSpawned && (((NetworkBehaviour)enemy).IsServer || ((NetworkBehaviour)enemy).IsHost)) { ((NetworkBehaviour)enemy).NetworkObject.Despawn(true); } else if ((Object)(object)((Component)enemy).gameObject != (Object)null) { Object.Destroy((Object)(object)((Component)enemy).gameObject); } } } private static void SpawnLoot(Vector3 position, Item item) { //IL_0019: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null || (Object)(object)item.spawnPrefab == (Object)null) { return; } try { Vector3 val = position + Vector3.up * 0.5f + Random.insideUnitSphere * 0.2f; GameObject val2 = Object.Instantiate(item.spawnPrefab, val, Random.rotation); GrabbableObject component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { component.startFallingPosition = val; component.FallToGround(true, true, default(Vector3)); NetworkObject component2 = val2.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.Spawn(true); } } } catch (Exception arg) { PublicFeatureRuntime.Logger.LogError((object)$"[LootDrops] Failed to spawn {item.itemName}: {arg}"); } } private static void HealPlayerFromTzpNetworked(PlayerControllerB player, int amount) { if ((Object)(object)player == (Object)null || player.isPlayerDead || amount <= 0) { return; } ulong playerClientId = player.playerClientId; if (PendingTzpCriticalHealTargets.TryGetValue(playerClientId, out var value)) { int num = Mathf.Clamp(Mathf.Max(player.health, value) + amount, 20, 100); if (num > value) { PendingTzpCriticalHealTargets[playerClientId] = num; } return; } int health = player.health; int num2 = Mathf.Clamp(health + amount, 0, 100); if (num2 <= health) { return; } if (player.health > 0 && (player.health < 20 || player.criticallyInjured) && num2 >= 20) { if (player.health < 19) { player.DamagePlayerClientRpc(-(19 - player.health), 19); } else if (player.health != 19) { player.DamagePlayerClientRpc(0, 19); } SchedulePendingTzpCriticalHeal(player, num2); } else { player.DamagePlayerClientRpc(-(num2 - player.health), num2); } } private static void SchedulePendingTzpCriticalHeal(PlayerControllerB player, int finalHealth) { if (!((Object)(object)player == (Object)null) && !player.isPlayerDead && !((Object)(object)PublicFeatureRuntime.PluginInstance == (Object)null)) { ulong playerClientId = player.playerClientId; PendingTzpCriticalHealTargets[playerClientId] = finalHealth; if (!PendingTzpCriticalHealCoroutines.TryGetValue(playerClientId, out var value) || value == null) { PendingTzpCriticalHealCoroutines[playerClientId] = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(ApplyPendingTzpCriticalHeal(player, playerClientId)); } } } private static IEnumerator ApplyPendingTzpCriticalHeal(PlayerControllerB player, ulong playerId) { yield return (object)new WaitForSeconds(1.25f); PendingTzpCriticalHealCoroutines.Remove(playerId); if (!PendingTzpCriticalHealTargets.TryGetValue(playerId, out var value)) { yield break; } PendingTzpCriticalHealTargets.Remove(playerId); if ((Object)(object)player == (Object)null || player.isPlayerDead || player.health < 19 || player.health > 20) { yield break; } value = Mathf.Clamp(value, 20, 100); if (value <= player.health) { if (player.health >= 20) { player.DamagePlayerClientRpc(0, player.health); } } else { player.DamagePlayerClientRpc(-(value - player.health), value); } } } public static class CombatScoreboardSystem { private sealed class PlayerScoreState { internal ulong PlayerId; internal string PlayerName; internal int Hits; internal int KillScore; } public sealed class PlayerScoreSnapshot { public PlayerControllerB Player { get; private set; } public string PlayerName { get; private set; } public int Hits { get; private set; } public int KillScore { get; private set; } public float TotalScore { get; private set; } internal PlayerScoreSnapshot(PlayerControllerB player, string playerName, int hits, int killScore, float totalScore) { Player = player; PlayerName = playerName; Hits = hits; KillScore = killScore; TotalScore = totalScore; } } private const int WindowId = 41982; private static readonly Dictionary PlayerScores = new Dictionary(); private static readonly Dictionary LastHitters = new Dictionary(); private static readonly HashSet ProcessedKills = new HashSet(); private static Coroutine _broadcastCoroutine; private static Coroutine _mvpAnnouncementCoroutine; private static CombatScoreboardBehaviour _behaviour; private static bool _windowVisible; private static bool _gameOverMvpAnnounced; private static bool _gameOverMvpPending; private static Rect _windowRect = new Rect(12f, 12f, 580f, 160f); public static void Initialize() { if (!((Object)(object)PublicFeatureRuntime.PluginInstance == (Object)null) && (Object)(object)_behaviour == (Object)null) { _behaviour = ((Component)PublicFeatureRuntime.PluginInstance).gameObject.GetComponent(); if ((Object)(object)_behaviour == (Object)null) { _behaviour = ((Component)PublicFeatureRuntime.PluginInstance).gameObject.AddComponent(); } } } public static void ResetRoundState() { PlayerScores.Clear(); LastHitters.Clear(); ProcessedKills.Clear(); _windowVisible = false; _gameOverMvpAnnounced = false; _gameOverMvpPending = false; if (_mvpAnnouncementCoroutine != null && (Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(_mvpAnnouncementCoroutine); } _mvpAnnouncementCoroutine = null; SeedControlledPlayers(); } private static void SeedControlledPlayers() { PlayerControllerB[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.allPlayerScripts : null); if (array == null) { return; } PlayerControllerB[] array2 = array; foreach (PlayerControllerB val in array2) { if ((Object)(object)val != (Object)null && val.isPlayerControlled) { GetOrCreatePlayerState(val); } } } public static void StartBroadcast() { StopBroadcast(); if ((Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null && PublicFeatureConfig.ScoreboardEnabled.Value && PublicFeatureConfig.ScoreboardBroadcastEnabled.Value) { _broadcastCoroutine = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(BroadcastRoutine()); } } public static void Stop() { StopBroadcast(); ResetRoundState(); } public static void RestartBroadcast() { StartBroadcast(); } public static void EndRound() { StopBroadcast(); _windowVisible = false; } public static void ScheduleMvpAnnouncement() { ManualLogSource logger = PublicFeatureRuntime.Logger; if (logger != null) { logger.LogInfo((object)"[MVP] Game-over hook triggered."); } if (_gameOverMvpAnnounced || _gameOverMvpPending) { ManualLogSource logger2 = PublicFeatureRuntime.Logger; if (logger2 != null) { logger2.LogInfo((object)"[MVP] Skipped because an announcement is already pending or sent."); } return; } if (!IsServer() || !PublicFeatureConfig.ScoreboardEnabled.Value || !PublicFeatureConfig.MvpEnabled.Value) { ManualLogSource logger3 = PublicFeatureRuntime.Logger; if (logger3 != null) { logger3.LogInfo((object)"[MVP] Skipped because this instance is not the server or MVP is disabled."); } return; } PlayerScoreSnapshot playerScoreSnapshot = CaptureMvpSnapshot(); if (playerScoreSnapshot == null) { ManualLogSource logger4 = PublicFeatureRuntime.Logger; if (logger4 != null) { logger4.LogWarning((object)$"[MVP] No player score snapshot was available. Stored score entries: {PlayerScores.Count}."); } return; } if ((Object)(object)PublicFeatureRuntime.PluginInstance == (Object)null) { ManualLogSource logger5 = PublicFeatureRuntime.Logger; if (logger5 != null) { logger5.LogWarning((object)"[MVP] Plugin instance is unavailable; cannot start delayed announcement."); } return; } _gameOverMvpPending = true; ManualLogSource logger6 = PublicFeatureRuntime.Logger; if (logger6 != null) { logger6.LogInfo((object)$"[MVP] Snapshot captured for {playerScoreSnapshot.PlayerName}: hits={playerScoreSnapshot.Hits}, score={playerScoreSnapshot.KillScore}, total={playerScoreSnapshot.TotalScore:0.0}."); } _mvpAnnouncementCoroutine = ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(DelayedMvpAnnouncement(playerScoreSnapshot)); } private static PlayerScoreSnapshot CaptureMvpSnapshot() { if (PlayerScores.Count == 0) { return null; } PlayerScoreState playerScoreState = (from state in PlayerScores.Values orderby CalculateTotalScore(state.KillScore, state.Hits) descending, state.KillScore descending, state.Hits descending select state).ThenBy((PlayerScoreState state) => state.PlayerName ?? string.Empty, StringComparer.OrdinalIgnoreCase).FirstOrDefault(); if (playerScoreState == null) { return null; } return new PlayerScoreSnapshot(FindPlayer(playerScoreState.PlayerId), string.IsNullOrEmpty(playerScoreState.PlayerName) ? "Unknown" : playerScoreState.PlayerName, playerScoreState.Hits, playerScoreState.KillScore, CalculateTotalScore(playerScoreState.KillScore, playerScoreState.Hits)); } private static IEnumerator DelayedMvpAnnouncement(PlayerScoreSnapshot mvp) { float deadline = Time.realtimeSinceStartup + 12f; while (Time.realtimeSinceStartup < deadline && (!((Object)(object)StartOfRound.Instance != (Object)null) || !StartOfRound.Instance.inShipPhase || !((Object)(object)HUDManager.Instance != (Object)null))) { yield return (object)new WaitForSecondsRealtime(0.5f); } if ((Object)(object)HUDManager.Instance == (Object)null) { ManualLogSource logger = PublicFeatureRuntime.Logger; if (logger != null) { logger.LogWarning((object)"[MVP] Announcement timed out because HUDManager was unavailable."); } _gameOverMvpPending = false; _mvpAnnouncementCoroutine = null; yield break; } yield return (object)new WaitForSecondsRealtime(1f); HUDManager.Instance.AddTextToChatOnServer("[本局MVP] " + mvp.PlayerName + " " + $"命中:{mvp.Hits} " + $"得分:{mvp.KillScore} " + $"总分:{mvp.TotalScore:0.0}", -1); _gameOverMvpAnnounced = true; _gameOverMvpPending = false; _mvpAnnouncementCoroutine = null; ManualLogSource logger2 = PublicFeatureRuntime.Logger; if (logger2 != null) { logger2.LogInfo((object)("[MVP] Announcement sent for " + mvp.PlayerName + ".")); } } public static void RecordHit(EnemyAI enemy, PlayerControllerB player) { if (IsServer() && PublicFeatureConfig.ScoreboardEnabled.Value && !((Object)(object)enemy == (Object)null) && !((Object)(object)player == (Object)null) && !enemy.isEnemyDead && player.isPlayerControlled && !player.isPlayerDead && !((Object)(object)((NetworkBehaviour)enemy).NetworkObject == (Object)null) && ((NetworkBehaviour)enemy).NetworkObject.IsSpawned && GetEnemyScore(enemy) > 0) { GetOrCreatePlayerState(player).Hits++; LastHitters[GetEnemyTrackingId(enemy)] = player.playerClientId; } } public static void RecordKill(EnemyAI enemy) { if (!IsServer() || !PublicFeatureConfig.ScoreboardEnabled.Value || (Object)(object)enemy == (Object)null || (Object)(object)((NetworkBehaviour)enemy).NetworkObject == (Object)null) { return; } ulong enemyTrackingId = GetEnemyTrackingId(enemy); if (!ProcessedKills.Add(enemyTrackingId)) { return; } try { int enemyScore = GetEnemyScore(enemy); if (enemyScore > 0 && LastHitters.TryGetValue(enemyTrackingId, out var value)) { PlayerControllerB val = FindPlayer(value); if (!((Object)(object)val == (Object)null) && val.isPlayerControlled) { GetOrCreatePlayerState(val).KillScore += enemyScore; } } } finally { LastHitters.Remove(enemyTrackingId); } } public static IReadOnlyList GetRankedPlayers() { List list = new List(); PlayerControllerB[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.allPlayerScripts : null); if (array == null) { return list; } PlayerControllerB[] array2 = array; foreach (PlayerControllerB val in array2) { if (!((Object)(object)val == (Object)null) && val.isPlayerControlled) { PlayerScoreState orCreatePlayerState = GetOrCreatePlayerState(val); list.Add(new PlayerScoreSnapshot(val, string.IsNullOrEmpty(val.playerUsername) ? "Unknown" : val.playerUsername, orCreatePlayerState.Hits, orCreatePlayerState.KillScore, CalculateTotalScore(orCreatePlayerState.KillScore, orCreatePlayerState.Hits))); } } return (from snapshot in list orderby snapshot.TotalScore descending, snapshot.KillScore descending, snapshot.Hits descending select snapshot).ThenBy((PlayerScoreSnapshot snapshot) => snapshot.PlayerName, StringComparer.OrdinalIgnoreCase).ToList(); } internal static void Update() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (IsServer() && PublicFeatureConfig.ScoreboardEnabled.Value && Keyboard.current != null && ((ButtonControl)Keyboard.current[PublicFeatureConfig.ScoreboardKey.Value]).wasPressedThisFrame) { _windowVisible = !_windowVisible; } } internal static void OnGui() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (IsServer() && PublicFeatureConfig.ScoreboardEnabled.Value && _windowVisible) { _windowRect = GUI.Window(41982, _windowRect, new WindowFunction(DrawWindow), string.Empty); } } private static void DrawWindow(int windowId) { IReadOnlyList rankedPlayers = GetRankedPlayers(); float height = Mathf.Clamp(82f + (float)rankedPlayers.Count * 28f, 120f, (float)Screen.height - 20f); ((Rect)(ref _windowRect)).height = height; ((Rect)(ref _windowRect)).x = Mathf.Clamp(((Rect)(ref _windowRect)).x, 10f, Mathf.Max(10f, (float)Screen.width - ((Rect)(ref _windowRect)).width - 10f)); ((Rect)(ref _windowRect)).y = Mathf.Clamp(((Rect)(ref _windowRect)).y, 10f, Mathf.Max(10f, (float)Screen.height - ((Rect)(ref _windowRect)).height - 10f)); GUILayout.BeginVertical(Array.Empty()); GUILayout.Label("战斗记分板", Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("名次", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); GUILayout.Label("玩家", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); GUILayout.Label("命中", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUILayout.Label("得分", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUILayout.Label("总分", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUILayout.Label("救援", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUILayout.EndHorizontal(); for (int i = 0; i < rankedPlayers.Count; i++) { PlayerScoreSnapshot playerScoreSnapshot = rankedPlayers[i]; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label((i + 1).ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); GUILayout.Label(playerScoreSnapshot.PlayerName, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); GUILayout.Label(playerScoreSnapshot.Hits.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUILayout.Label(playerScoreSnapshot.KillScore.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUILayout.Label(playerScoreSnapshot.TotalScore.ToString("0.0"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUI.enabled = PublicFeatureConfig.RescueEnabled.Value && (Object)(object)playerScoreSnapshot.Player != (Object)null && !playerScoreSnapshot.Player.isPlayerDead && !playerScoreSnapshot.Player.isInHangarShipRoom; if (GUILayout.Button("救援", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { CurfewExecutionSystem.TryStartRescue(playerScoreSnapshot.Player); } GUI.enabled = true; GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUI.DragWindow(); } private static IEnumerator BroadcastRoutine() { while (PublicFeatureRuntime.IsEnabled && PublicFeatureConfig.ScoreboardEnabled.Value && PublicFeatureConfig.ScoreboardBroadcastEnabled.Value) { yield return (object)new WaitForSecondsRealtime(Mathf.Max(5f, PublicFeatureConfig.ScoreboardBroadcastInterval.Value)); if (!IsActiveRound() || (Object)(object)HUDManager.Instance == (Object)null) { continue; } IReadOnlyList players = GetRankedPlayers(); int count = Mathf.Min(Mathf.Max(1, PublicFeatureConfig.ScoreboardBroadcastTopCount.Value), players.Count); if (count != 0) { HUDManager.Instance.AddTextToChatOnServer("[战斗记分]", -1); for (int index = 0; index < count; index++) { PlayerScoreSnapshot playerScoreSnapshot = players[index]; HUDManager.Instance.AddTextToChatOnServer($"{index + 1}. {playerScoreSnapshot.PlayerName} - 命中:{playerScoreSnapshot.Hits} 得分:{playerScoreSnapshot.KillScore} 总分:{playerScoreSnapshot.TotalScore:0.0}", -1); yield return (object)new WaitForSecondsRealtime(0.35f); } } } } private static void StopBroadcast() { if (_broadcastCoroutine != null && (Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StopCoroutine(_broadcastCoroutine); } _broadcastCoroutine = null; } private static PlayerScoreState GetOrCreatePlayerState(PlayerControllerB player) { if (!PlayerScores.TryGetValue(player.playerClientId, out var value)) { value = new PlayerScoreState { PlayerId = player.playerClientId }; PlayerScores[player.playerClientId] = value; } value.PlayerName = player.playerUsername; return value; } private static PlayerControllerB FindPlayer(ulong playerId) { return (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.allPlayerScripts : null)?.FirstOrDefault((Func)((PlayerControllerB player) => (Object)(object)player != (Object)null && player.playerClientId == playerId)); } private static ulong GetEnemyTrackingId(EnemyAI enemy) { if ((Object)(object)((NetworkBehaviour)enemy).NetworkObject != (Object)null) { return ((NetworkBehaviour)enemy).NetworkObjectId; } return (ulong)((Object)((Component)enemy).gameObject).GetInstanceID(); } private static int GetEnemyScore(EnemyAI enemy) { if (enemy is FlowermanAI) { return PublicFeatureConfig.ScoreFlowerman.Value; } if (enemy is NutcrackerEnemyAI) { return PublicFeatureConfig.ScoreNutcracker.Value; } if (enemy is CrawlerAI) { return PublicFeatureConfig.ScoreCrawler.Value; } if (enemy is CentipedeAI) { return PublicFeatureConfig.ScoreCentipede.Value; } if (enemy is HoarderBugAI) { return PublicFeatureConfig.ScoreHoarderBug.Value; } if (enemy is MaskedPlayerEnemy) { return PublicFeatureConfig.ScoreMasked.Value; } if (enemy is SandSpiderAI) { return PublicFeatureConfig.ScoreSandSpider.Value; } if (enemy is ButlerEnemyAI) { return PublicFeatureConfig.ScoreButler.Value; } if (enemy is StingrayAI) { return PublicFeatureConfig.ScoreStingray.Value; } return 0; } private static float CalculateTotalScore(int killScore, int hits) { int num = Mathf.Max(0, PublicFeatureConfig.ScoreWeight.Value); int num2 = Mathf.Max(0, PublicFeatureConfig.HitWeight.Value); int num3 = Mathf.Max(1, num + num2); return (float)(killScore * num + hits * num2) / (float)num3; } private static bool IsServer() { if ((Object)(object)RoundManager.Instance != (Object)null) { if (!((NetworkBehaviour)RoundManager.Instance).IsServer) { return ((NetworkBehaviour)RoundManager.Instance).IsHost; } return true; } return false; } private static bool IsActiveRound() { if (IsServer() && (Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.shipHasLanded) { return !StartOfRound.Instance.inShipPhase; } return false; } } public sealed class CombatScoreboardBehaviour : MonoBehaviour { private void Update() { CombatScoreboardSystem.Update(); } private void OnGUI() { CombatScoreboardSystem.OnGui(); } } [HarmonyPatch(typeof(EnemyAI), "HitEnemy")] internal static class CombatScoreboardHitPatch { [HarmonyPrefix] private static void Prefix(EnemyAI __instance, int force, PlayerControllerB playerWhoHit) { if (force > 0) { CombatScoreboardSystem.RecordHit(__instance, playerWhoHit); } } } [HarmonyPatch(typeof(EnemyAI), "KillEnemy")] internal static class CombatScoreboardKillPatch { [HarmonyPostfix] private static void Postfix(EnemyAI __instance) { CombatScoreboardSystem.RecordKill(__instance); } } public static class EnemyScalePatches { private static readonly HashSet ScaledEnemyInstances = new HashSet(); private static readonly HashSet PendingEnemyInstances = new HashSet(); public static void ResetRoundState() { ScaledEnemyInstances.Clear(); PendingEnemyInstances.Clear(); } public static void TryScale(EnemyAI enemy, bool enabled, float multiplier) { if (!enabled || (Object)(object)enemy == (Object)null || multiplier <= 1.001f || (Object)(object)RoundManager.Instance == (Object)null || (!((NetworkBehaviour)RoundManager.Instance).IsServer && !((NetworkBehaviour)RoundManager.Instance).IsHost)) { return; } NetworkObject component = ((Component)enemy).GetComponent(); int instanceID = ((Object)enemy).GetInstanceID(); if ((Object)(object)component == (Object)null || ScaledEnemyInstances.Contains(instanceID)) { return; } if (!component.IsSpawned) { if ((Object)(object)PublicFeatureRuntime.PluginInstance != (Object)null && PendingEnemyInstances.Add(instanceID)) { ((MonoBehaviour)PublicFeatureRuntime.PluginInstance).StartCoroutine(ScaleWhenSpawned(enemy, enabled, multiplier, instanceID)); } } else { ApplyScale(enemy, component, multiplier, instanceID); } } private static IEnumerator ScaleWhenSpawned(EnemyAI enemy, bool enabled, float multiplier, int instanceId) { float timeout = Time.realtimeSinceStartup + 2f; NetworkObject networkObject = (((Object)(object)enemy == (Object)null) ? null : ((Component)enemy).GetComponent()); while ((Object)(object)enemy != (Object)null && (Object)(object)networkObject != (Object)null && !networkObject.IsSpawned && Time.realtimeSinceStartup < timeout) { yield return null; } PendingEnemyInstances.Remove(instanceId); if (enabled && (Object)(object)enemy != (Object)null && (Object)(object)networkObject != (Object)null && networkObject.IsSpawned) { ApplyScale(enemy, networkObject, multiplier, instanceId); } } private static void ApplyScale(EnemyAI enemy, NetworkObject networkObject, float multiplier, int instanceId) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (ScaledEnemyInstances.Add(instanceId)) { Vector3 localScale = ((Component)enemy).transform.localScale; networkObject.Despawn(false); ((Component)enemy).transform.localScale = localScale * multiplier; networkObject.Spawn(false); } } } [HarmonyPatch(typeof(CrawlerAI), "Start")] internal static class PublicCrawlerScalePatch { [HarmonyPostfix] private static void Postfix(CrawlerAI __instance) { EnemyScalePatches.TryScale((EnemyAI)(object)__instance, PublicFeatureConfig.EnableCrawlerScale.Value, PublicFeatureConfig.CrawlerScale.Value); } } [HarmonyPatch(typeof(ButlerEnemyAI), "Start")] internal static class PublicButlerScalePatch { [HarmonyPostfix] private static void Postfix(ButlerEnemyAI __instance) { EnemyScalePatches.TryScale((EnemyAI)(object)__instance, PublicFeatureConfig.EnableButlerScale.Value, PublicFeatureConfig.ButlerScale.Value); } } } namespace PublicCombatMod { [BepInPlugin("com.lengjing.lethalbattletweaks", "LethalBattleTweaks", "1.0.7")] public class Plugin : BaseUnityPlugin { private Harmony _harmony; private void Awake() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _harmony = new Harmony("com.lengjing.lethalbattletweaks"); _harmony.PatchAll(); PublicFeatureRuntime.Initialize((BaseUnityPlugin)(object)this, PublicFeatureMode.PublicStandalone, ((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)"LethalBattleTweaks loaded."); } private void Start() { PublicFeatureRuntime.OnPluginStart(); } private void OnDestroy() { PublicFeatureRuntime.Shutdown(); } } }