using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using FishNet.Object; using HarmonyLib; using HtF.Shared; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("HtF.HostRules")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+30e45fdf35baf9e07db484a878d9c720a617ee42")] [assembly: AssemblyProduct("HtF.HostRules")] [assembly: AssemblyTitle("HtF.HostRules")] [assembly: AssemblyVersion("1.1.0.0")] namespace HtF.Shared { public enum Language { Auto, Chinese, English } public static class Loc { private sealed class Entry { internal string NameZh; internal string NameEn; internal string DescZh; internal string DescEn; } private const string OwnerGuid = "htf.configmenu"; private const string OwnerSection = "介面"; private const string OwnerKey = "語言"; private static readonly Dictionary Entries = new Dictionary(StringComparer.Ordinal); public static Func LocalChoice; private static ConfigEntryBase _remote; private static string _remoteChoice; private static float _nextRemoteRead; public static bool IsEnglish { get; private set; } public static ConfigEntry Bind(ConfigFile file, string section, string key, T defaultValue, string nameEn, string descZh, string descEn = null, AcceptableValueBase range = null) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown Add(key, null, nameEn, descZh, descEn); return file.Bind(section, key, defaultValue, new ConfigDescription(CfgDesc(key), range, Array.Empty())); } public static void Section(string section, string nameEn) { Add(section, null, nameEn, null, null); } public static void EnumValue(object value, string nameZh, string nameEn) { if (value != null) { Add("enum." + value, nameZh, nameEn, null, null); } } public static void Add(string key, string nameZh, string nameEn, string descZh, string descEn) { if (!string.IsNullOrEmpty(key)) { Entries[key] = new Entry { NameZh = nameZh, NameEn = nameEn, DescZh = descZh, DescEn = descEn }; } } public static string Term(string key, bool english, bool wantDesc) { if (key == null || !Entries.TryGetValue(key, out var value)) { return null; } if (wantDesc) { if (!english) { return value.DescZh; } return value.DescEn; } if (!english) { return value.NameZh; } return value.NameEn; } public static string CfgDesc(string key) { if (key == null || !Entries.TryGetValue(key, out var value) || value.DescZh == null) { return ""; } if (value.DescEn != null) { return value.DescZh + "\n" + value.DescEn; } return value.DescZh; } public static void Resolve() { string text = ((LocalChoice != null) ? LocalChoice() : RemoteChoice()); if (text == "English") { IsEnglish = true; } else if (text == "Chinese") { IsEnglish = false; } else { IsEnglish = !GameSpeaksChinese(); } } private static string RemoteChoice() { if (Time.unscaledTime < _nextRemoteRead) { return _remoteChoice; } _nextRemoteRead = Time.unscaledTime + 0.5f; try { if (_remote == null) { if (!Chainloader.PluginInfos.TryGetValue("htf.configmenu", out var value)) { return _remoteChoice = null; } if (value == null || (Object)(object)value.Instance == (Object)null || value.Instance.Config == null) { return _remoteChoice = null; } ConfigEntryBase[] configEntries = value.Instance.Config.GetConfigEntries(); foreach (ConfigEntryBase val in configEntries) { if (val.Definition.Section == "介面" && val.Definition.Key == "語言") { _remote = val; break; } } if (_remote == null) { return _remoteChoice = null; } } _remoteChoice = _remote.BoxedValue?.ToString(); } catch (Exception) { _remoteChoice = null; } return _remoteChoice; } private static bool GameSpeaksChinese() { try { int curLanguage = LocalizationManager.CurLanguage; return curLanguage == 2 || curLanguage == 3; } catch (Exception) { return false; } } public static string P(string zh, string en) { if (!IsEnglish) { return zh; } return en; } } } namespace HtF.HostRules { internal static class BaitTuner { private static FieldInfo _field; private static bool _resolved; private static bool _applied; private static readonly Dictionary Originals = new Dictionary(); private static bool Enabled { get { if (!Plugin.FishingDisabledByConflict && Plugin.FishingEnabled != null) { return Plugin.FishingEnabled.Value; } return false; } } internal static void TickTryApply() { if (_applied || !Enabled) { return; } try { if (GameInfo.AllBaits == null || GameInfo.AllBaits.Count == 0) { return; } } catch (Exception) { return; } Apply(); } internal static void Apply() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) Resolve(); if (_field == null) { return; } if (!Enabled) { Restore(); return; } IReadOnlyList allBaits; try { allBaits = GameInfo.AllBaits; if (allBaits == null || allBaits.Count == 0) { return; } } catch (Exception) { return; } float value = Plugin.CatchTimeMultiplier.Value; for (int i = 0; i < allBaits.Count; i++) { BaitInfo val = allBaits[i]; if (!Object.op_Implicit((Object)(object)val)) { continue; } if (!Originals.TryGetValue(val, out var value2)) { try { value2 = (Vector2)_field.GetValue(val); } catch (Exception) { continue; } Originals[val] = value2; } try { _field.SetValue(val, value2 * value); } catch (Exception) { } } _applied = true; if (Math.Abs(value - 1f) > 0.0001f) { Plugin.Log.LogInfo((object)("咬鉤時間 ×" + value + " 已套用到 " + Originals.Count + " 種魚餌。")); } } internal static void Restore() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (_field == null || Originals.Count == 0) { return; } foreach (KeyValuePair original in Originals) { if (Object.op_Implicit((Object)(object)original.Key)) { try { _field.SetValue(original.Key, original.Value); } catch (Exception) { } } } Originals.Clear(); _applied = false; } private static void Resolve() { if (!_resolved) { _resolved = true; _field = AccessTools.Field(typeof(BaitInfo), "_catchTimeMinMax"); if (_field == null) { Plugin.Log.LogWarning((object)"找不到 BaitInfo._catchTimeMinMax(遊戲可能又更新了),咬鉤時間不會生效。"); } } } } [HarmonyPatch] internal static class FishingPatches { [HarmonyPatch(typeof(CreatureManager), "GetRandomItem")] [HarmonyPrefix] private static void GetRandomItem_Prefix(ref List weights) { if (Plugin.FishingEnabled.Value) { List list = WeightTable.Build(weights); if (list != null) { weights = list; } } } [HarmonyPatch(typeof(CreatureManager), "GetRandomItem")] [HarmonyPostfix] private static void GetRandomItem_Postfix(Fishable __result) { if (Plugin.FishingEnabled.Value && WeightTable.LastBuildValid) { bool flag = (Object)(object)__result != (Object)null && WeightTable.LastRareSet.Contains(__result); if (flag) { WeightTable.MissStreak = 0; } else { WeightTable.MissStreak++; } if (Plugin.LogRolls.Value) { string text = (((Object)(object)__result != (Object)null && Object.op_Implicit((Object)(object)__result.ItemToSpawn)) ? ((Object)__result.ItemToSpawn).name : "(null)"); Plugin.Log.LogInfo((object)("抽到 " + text + (flag ? "(稀有)" : "") + "\u3000連續非稀有 " + WeightTable.MissStreak)); } } } } [HarmonyPatch] internal static class Patches { internal const string OnDifficultyChangeName = "OnDifficultyChange"; [HarmonyPatch(typeof(ServerSettings), "OnStartServer")] [HarmonyPostfix] private static void ServerSettings_OnStartServer_Postfix() { RuleApplier.ApplyMultipliers(); RuleApplier.ApplyToggles(); } [HarmonyPatch(typeof(ServerSettings), "OnDifficultyChange")] [HarmonyPostfix] private static void OnDifficultyChange_Postfix() { RuleApplier.ApplyMultipliers(); } [HarmonyPatch(typeof(PlayerVitals), "OnStartServer")] [HarmonyPostfix] private static void PlayerVitals_OnStartServer_Postfix(PlayerVitals __instance) { VitalsTuner.Apply(__instance); } internal static void VerifyTargets() { if (AccessTools.Method(typeof(ServerSettings), "OnDifficultyChange", (Type[])null, (Type[])null) == null) { Plugin.Log.LogWarning((object)"ServerSettings.OnDifficultyChange 不存在了(遊戲更新?)——房主在遊戲中改難度時,覆寫的乘數會被遊戲蓋回去。"); } } } internal enum Force { 不變, 強制開啟, 強制關閉 } [BepInPlugin("htf.hostrules", "HtF Host Rules", "1.1.0")] public class Plugin : BaseUnityPlugin { public const string Guid = "htf.hostrules"; private static readonly string[] OldFishingGuids = new string[2] { "htf.economy", "htf.fishingecology" }; internal static ManualLogSource Log; internal static Plugin Instance; internal static ConfigEntry OverrideDifficulty; internal static ConfigEntry CreatureHealthMul; internal static ConfigEntry PlayerDamageTakenMul; internal static ConfigEntry FriendlyFire; internal static ConfigEntry OneShot; internal static ConfigEntry HungerSpeedMul; internal static ConfigEntry HungerDamageMul; internal static ConfigEntry RegenSpeedMul; internal static ConfigEntry RegenAmountMul; internal static ConfigEntry PoisonDamageMul; internal static ConfigEntry FireDamageMul; internal static ConfigEntry PvpDamageMul; internal static ConfigEntry HealthOnRes; internal static ConfigEntry FullnessOnRes; internal static ConfigEntry InvulnAfterDamage; internal static ConfigEntry FishingEnabled; internal static ConfigEntry RareMultiplier; internal static ConfigEntry CommonMultiplier; internal static ConfigEntry BossMultiplier; internal static ConfigEntry RareThreshold; internal static ConfigEntry PerItem; internal static ConfigEntry PityAfter; internal static ConfigEntry CatchTimeMultiplier; internal static ConfigEntry LogRolls; internal static bool FishingDisabledByConflict; private Harmony _harmony; private Harmony _fishingHarmony; private bool _checkedForOldMods; private void Awake() { //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_0422: Expected O, but got Unknown //IL_0442: Unknown result type (might be due to invalid IL or missing references) //IL_044c: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Loc.Section("難度乘數", "Difficulty Multipliers"); Loc.Section("規則", "Rules"); Loc.Section("玩家數值", "Player Stats"); Loc.Section("釣魚", "Fishing"); Loc.Section("除錯", "Debug"); Loc.EnumValue(Force.不變, "不變", "Unchanged"); Loc.EnumValue(Force.強制開啟, "強制開啟", "Force On"); Loc.EnumValue(Force.強制關閉, "強制關閉", "Force Off"); OverrideDifficulty = Loc.Bind(((BaseUnityPlugin)this).Config, "難度乘數", "覆寫難度乘數", defaultValue: false, "Override Difficulty Multipliers", "打開後,下面兩個倍率會取代遊戲的 簡單/普通/困難 三段設定。", "When on, the two multipliers below replace the game's Easy / Default / Hard presets."); CreatureHealthMul = Loc.Bind(((BaseUnityPlugin)this).Config, "難度乘數", "生物血量倍率", 1f, "Creature Health Multiplier", "作用於 Creature.MaxHp。遊戲原本:簡單 0.75、普通 1、困難 1.25。", "Applied to Creature.MaxHp. Vanilla values: Easy 0.75, Default 1, Hard 1.25.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f)); PlayerDamageTakenMul = Loc.Bind(((BaseUnityPlugin)this).Config, "難度乘數", "玩家受傷倍率", 1f, "Player Damage Taken Multiplier", "玩家受到的所有傷害。遊戲原本:簡單 0.5、普通 1、困難 1.25。", "All damage players take. Vanilla values: Easy 0.5, Default 1, Hard 1.25.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); FriendlyFire = Loc.Bind(((BaseUnityPlugin)this).Config, "規則", "友軍傷害", Force.不變, "Friendly Fire", "強制覆寫房間設定裡的友傷開關。", "Force the lobby's friendly fire toggle one way or the other."); OneShot = Loc.Bind(((BaseUnityPlugin)this).Config, "規則", "一擊必殺", Force.不變, "One-Shot Kills", "強制覆寫一擊必殺。開啟時近戰/拳頭/子彈傷害固定 99999。", "Force the one-shot setting. When on, melee / fists / bullets all deal a flat 99999."); HungerSpeedMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "飢餓速度倍率", 1f, "Hunger Speed Multiplier", "大於 1 餓得更快。原本每 300 tick 掉 1 點飽食。", "Above 1 = you get hungry faster. Vanilla loses 1 fullness every 300 ticks.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f)); HungerDamageMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "飢餓扣血倍率", 1f, "Starvation Damage Multiplier", "飽食歸零後的扣血量。原本每 150 tick 扣 5。", "Damage taken once fullness hits zero. Vanilla is 5 every 150 ticks.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); RegenSpeedMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "回血速度倍率", 1f, "Regen Speed Multiplier", "大於 1 回得更快。原本每 100 tick 回一次。", "Above 1 = you heal more often. Vanilla heals once every 100 ticks.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f)); RegenAmountMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "回血量倍率", 1f, "Regen Amount Multiplier", "每次回復的血量。原本 5。", "Health restored per heal. Vanilla is 5.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); PoisonDamageMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "中毒傷害倍率", 1f, "Poison Damage Multiplier", "原本每 100 tick 扣 5。", "Vanilla is 5 every 100 ticks.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); FireDamageMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "著火傷害倍率", 1f, "Fire Damage Multiplier", "原本每 50 tick 扣 10。", "Vanilla is 10 every 50 ticks.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f)); PvpDamageMul = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "玩家對玩家傷害倍率", -1f, "Player vs Player Damage Multiplier", "絕對值覆寫,−1 = 不改。遊戲預設 0.25(玩家互打只有四分之一傷害)。", "Absolute override, −1 = leave alone. The game default is 0.25 (players deal a quarter damage to each other).", (AcceptableValueBase)(object)new AcceptableValueRange(-1f, 10f)); HealthOnRes = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "復活後生命", -1, "Health On Revive", "−1 = 不改。遊戲預設 25。", "−1 = leave alone. The game default is 25.", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 100)); FullnessOnRes = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "復活後飽食", -1, "Fullness On Revive", "−1 = 不改。遊戲預設 10。", "−1 = leave alone. The game default is 10.", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 100)); InvulnAfterDamage = Loc.Bind(((BaseUnityPlugin)this).Config, "玩家數值", "受傷後無敵秒數", -1f, "Invulnerability After Damage", "−1 = 不改。遊戲預設 0.25。", "Seconds. −1 = leave alone. The game default is 0.25.", (AcceptableValueBase)(object)new AcceptableValueRange(-1f, 5f)); BindFishing(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate(object s, SettingChangedEventArgs e) { if ((object)e.ChangedSetting == CatchTimeMultiplier || (object)e.ChangedSetting == FishingEnabled) { BaitTuner.Apply(); } else { RuleApplier.ApplyAll(); } }; _harmony = new Harmony("htf.hostrules"); _harmony.PatchAll(typeof(Patches)); Patches.VerifyTargets(); _fishingHarmony = new Harmony("htf.hostrules.fishing"); _fishingHarmony.PatchAll(typeof(FishingPatches)); Log.LogInfo((object)("Host Rules 已載入(釣魚生態 " + (FishingEnabled.Value ? "開" : "關") + ")。")); } private void BindFishing() { FishingEnabled = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "啟用釣魚生態", defaultValue: true, "Enable Fishing Ecology", "關掉就完全走遊戲原本的抽魚權重。不影響上面的難度與玩家數值。", "Turn this off to fall back to the vanilla catch weights entirely. Does not affect the difficulty and player stats above."); RareThreshold = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "稀有判定門檻", 0.25f, "Rare Threshold", "權重小於等於「該魚餌表中最大權重 × 此值」的項目算稀有。\n遊戲沒有魚的稀有度欄位(Rarity 只用在外觀),所以用權重本身來判定:\n在那張表裡越難抽到的就越稀有。", "Anything whose weight is at or below (the largest weight in that bait's table × this value) counts as rare.\nThe game has no rarity field for fish (Rarity is only used for skins), so rarity is derived from the weights themselves:\nthe harder it is to roll in that table, the rarer it is.", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 1f)); RareMultiplier = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "稀有魚倍率", 1f, "Rare Multiplier", "大於 1 = 稀有魚更常出現。", "Above 1 = rare fish show up more often.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f)); CommonMultiplier = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "常見魚倍率", 1f, "Common Multiplier", "調低它等於相對拉高稀有魚。", "Lowering this raises rare fish relative to everything else.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f)); BossMultiplier = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "Boss 倍率", 1f, "Boss Multiplier", "Boss 類(Creature.BossType 不是 None)的權重。\n場上已經有 Boss 時遊戲本來就會擋掉,這裡不影響那個規則。", "Weight of boss creatures (Creature.BossType other than None).\nThe game already blocks a second boss while one is alive; this does not change that rule.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f)); PerItem = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "個別倍率", "", "Per-Item Multipliers", "逗號分隔的「名稱=倍率」,名稱用去空格全小寫的形式(跟 /spawn 一樣)。\n例:tuna=5, giantpiranha=0.2, flyingfish=3\n個別倍率會覆蓋上面的稀有/常見/Boss 倍率。", "Comma-separated name=multiplier pairs. Names are lowercase with spaces removed (same form /spawn takes).\nExample: tuna=5, giantpiranha=0.2, flyingfish=3\nA per-item multiplier overrides the rare / common / boss multipliers above."); PityAfter = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "連續幾次沒稀有就保底", 0, "Pity After N Non-Rare Rolls", "0 = 關閉。設 N 表示連續 N 次抽到非稀有後,下一次只從稀有項目裡抽。\n計數是全房共用的,不是每個玩家各自計算;換存檔、重開房間都不會歸零,要關掉遊戲才會。", "0 = off. Set N so that after N non-rare rolls in a row, the next roll draws only from the rare items.\nThe counter is shared by the whole lobby rather than tracked per player, and it is not reset when you load another save or start a new lobby - only when you quit the game.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100)); CatchTimeMultiplier = Loc.Bind(((BaseUnityPlugin)this).Config, "釣魚", "咬鉤時間倍率", 1f, "Bite Time Multiplier", "小於 1 = 魚咬鉤更快。這一項是改 BaitInfo 資產,關掉上面的「啟用釣魚生態」或離開遊戲時都會還原。", "Below 1 = fish bite sooner. This one edits the BaitInfo asset; it is restored both when you turn off Enable Fishing Ecology above and when you quit.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 10f)); LogRolls = Loc.Bind(((BaseUnityPlugin)this).Config, "除錯", "記錄每次抽取", defaultValue: false, "Log Every Roll", "把每次抽到什麼寫進 BepInEx log,調倍率時很有用。", "Write every roll to the BepInEx log. Handy while tuning multipliers."); } private void Update() { if (!_checkedForOldMods) { CheckForOldMods(); } BaitTuner.TickTryApply(); } private void CheckForOldMods() { _checkedForOldMods = true; string text = null; for (int i = 0; i < OldFishingGuids.Length; i++) { if (Chainloader.PluginInfos.ContainsKey(OldFishingGuids[i])) { text = ((text == null) ? OldFishingGuids[i] : (text + "、" + OldFishingGuids[i])); } } if (text != null) { FishingDisabledByConflict = true; BaitTuner.Restore(); if (_fishingHarmony != null) { _fishingHarmony.UnpatchSelf(); _fishingHarmony = null; } Log.LogError((object)("偵測到舊的 " + text + " 還裝著。釣魚生態已經併進這個 mod,兩份同時載入會讓抽魚權重與咬鉤時間被套用兩次(倍率疊乘)。為了不弄壞你的存檔,這個 mod 的釣魚功能**已自動停用**(難度、規則、玩家數值不受影響)。請把舊的 BepInEx/plugins 資料夾整個刪掉再重開遊戲。")); } } private void OnDestroy() { BaitTuner.Restore(); if (_fishingHarmony != null) { _fishingHarmony.UnpatchSelf(); } if (_harmony != null) { _harmony.UnpatchSelf(); } } } internal static class RuleApplier { private static MethodInfo _setHealth; private static MethodInfo _setDamage; private static bool _resolved; internal static bool IsHost { get { ServerSettings instance = ServerSettings.Instance; if (Object.op_Implicit((Object)(object)instance)) { return ((NetworkBehaviour)instance).IsServerInitialized; } return false; } } private static void GameDefaults(Difficulty d, out float health, out float damage) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 if ((int)d != 0) { if ((int)d == 2) { health = 1.25f; damage = 1.25f; } else { health = 1f; damage = 1f; } } else { health = 0.75f; damage = 0.5f; } } internal static void ApplyAll() { if (IsHost) { ApplyMultipliers(); ApplyToggles(); VitalsTuner.ReapplyAll(); } } internal static void ApplyMultipliers() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!IsHost) { return; } Resolve(); if (_setHealth == null || _setDamage == null) { return; } float health; float damage; if (Plugin.OverrideDifficulty.Value) { health = Plugin.CreatureHealthMul.Value; damage = Plugin.PlayerDamageTakenMul.Value; } else { GameDefaults(ServerSettings.Difficulty, out health, out damage); } try { _setHealth.Invoke(null, new object[1] { health }); _setDamage.Invoke(null, new object[1] { damage }); } catch (Exception ex) { Plugin.Log.LogError((object)("寫入難度乘數失敗:" + ex)); } } internal static void ApplyToggles() { ServerSettings instance = ServerSettings.Instance; if (!Object.op_Implicit((Object)(object)instance) || !((NetworkBehaviour)instance).IsServerInitialized) { return; } try { if (Plugin.FriendlyFire.Value != Force.不變) { bool flag = Plugin.FriendlyFire.Value == Force.強制開啟; if (ServerSettings.UseFriendlyFire != flag) { instance.ToggleFriendlyFire(flag); } } if (Plugin.OneShot.Value != Force.不變) { bool flag2 = Plugin.OneShot.Value == Force.強制開啟; if (ServerSettings.OneShotEnabled != flag2) { instance.ToggleOneShot(); } } } catch (Exception ex) { Plugin.Log.LogError((object)("套用規則開關失敗:" + ex)); } } private static void Resolve() { if (!_resolved) { _resolved = true; _setHealth = AccessTools.PropertySetter(typeof(ServerSettings), "HealthMultiplier"); _setDamage = AccessTools.PropertySetter(typeof(ServerSettings), "DamageMultiplier"); if (_setHealth == null || _setDamage == null) { Plugin.Log.LogWarning((object)"找不到 ServerSettings 的乘數 setter(遊戲可能又更新了),難度覆寫不會生效。"); } } } } internal static class VitalsTuner { private static readonly Dictionary Fields = new Dictionary(); private static readonly Dictionary Defaults = new Dictionary(); private static bool _defaultsCaptured; private const string LoseFullnessInterval = "_loseFullnessTickInterval"; private const string FullnessLostPerTick = "_fullnessLostPerTickInterval"; private const string LoseHealthHungerInterval = "_loseHealthHungerTickInterval"; private const string HealthLostPerHungerTick = "_healthLostPerHungerTick"; private const string GainHealthInterval = "_gainHealthTickInterval"; private const string HealthGainedPerTick = "_healthGainedPerTickInterval"; private const string PoisonInterval = "_poisonTickInterval"; private const string PoisonDamage = "_poisonDamagePerTickInterval"; private const string FireInterval = "_fireTickInterval"; private const string FireDamage = "_fireDamagePerTickInterval"; private const string PvpDamage = "_playerDamageMultiplier"; private const string HealthOnRes = "_healthOnRes"; private const string FullnessOnRes = "_fullnessOnRes"; private const string InvulnAfterDamage = "_invulnerabilityAfterDamage"; private static readonly string[] All = new string[14] { "_loseFullnessTickInterval", "_fullnessLostPerTickInterval", "_loseHealthHungerTickInterval", "_healthLostPerHungerTick", "_gainHealthTickInterval", "_healthGainedPerTickInterval", "_poisonTickInterval", "_poisonDamagePerTickInterval", "_fireTickInterval", "_fireDamagePerTickInterval", "_playerDamageMultiplier", "_healthOnRes", "_fullnessOnRes", "_invulnerabilityAfterDamage" }; internal static void Apply(PlayerVitals v) { if (Object.op_Implicit((Object)(object)v)) { CaptureDefaults(v); SetUInt(v, "_loseFullnessTickInterval", DivInterval("_loseFullnessTickInterval", Plugin.HungerSpeedMul.Value)); SetUInt(v, "_loseHealthHungerTickInterval", DivInterval("_loseHealthHungerTickInterval", Plugin.HungerSpeedMul.Value)); SetInt(v, "_healthLostPerHungerTick", MulInt("_healthLostPerHungerTick", Plugin.HungerDamageMul.Value)); SetUInt(v, "_gainHealthTickInterval", DivInterval("_gainHealthTickInterval", Plugin.RegenSpeedMul.Value)); SetInt(v, "_healthGainedPerTickInterval", MulInt("_healthGainedPerTickInterval", Plugin.RegenAmountMul.Value)); SetInt(v, "_poisonDamagePerTickInterval", MulInt("_poisonDamagePerTickInterval", Plugin.PoisonDamageMul.Value)); SetInt(v, "_fireDamagePerTickInterval", MulInt("_fireDamagePerTickInterval", Plugin.FireDamageMul.Value)); if (Plugin.PvpDamageMul.Value >= 0f) { SetFloat(v, "_playerDamageMultiplier", Plugin.PvpDamageMul.Value); } else { RestoreFloat(v, "_playerDamageMultiplier"); } if (Plugin.HealthOnRes.Value >= 0) { SetInt(v, "_healthOnRes", Plugin.HealthOnRes.Value); } else { RestoreInt(v, "_healthOnRes"); } if (Plugin.FullnessOnRes.Value >= 0) { SetInt(v, "_fullnessOnRes", Plugin.FullnessOnRes.Value); } else { RestoreInt(v, "_fullnessOnRes"); } if (Plugin.InvulnAfterDamage.Value >= 0f) { SetFloat(v, "_invulnerabilityAfterDamage", Plugin.InvulnAfterDamage.Value); } else { RestoreFloat(v, "_invulnerabilityAfterDamage"); } } } internal static void ReapplyAll() { try { if (PlayerManager.Players == null) { return; } for (int i = 0; i < PlayerManager.Players.Count; i++) { Player val = PlayerManager.Players[i]; if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.Vitals)) { Apply(val.Vitals); } } } catch (Exception ex) { Plugin.Log.LogError((object)("重新套用失敗:" + ex)); } } private static void CaptureDefaults(PlayerVitals v) { if (_defaultsCaptured) { return; } _defaultsCaptured = true; for (int i = 0; i < All.Length; i++) { FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerVitals), All[i]); if (fieldInfo == null) { Plugin.Log.LogWarning((object)("PlayerVitals 找不到欄位 " + All[i] + "(遊戲可能又更新了),該項會被略過。")); continue; } Fields[All[i]] = fieldInfo; try { Defaults[All[i]] = fieldInfo.GetValue(v); } catch (Exception ex) { Plugin.Log.LogWarning((object)("讀取 " + All[i] + " 失敗:" + ex.Message)); } } Plugin.Log.LogInfo((object)("已擷取 PlayerVitals 預設值 " + Defaults.Count + " 項。")); } private static uint DivInterval(string key, float speedMul) { if (!Defaults.TryGetValue(key, out var value)) { return 0u; } float num = Convert.ToSingle(value); if (speedMul <= 0.0001f) { speedMul = 0.0001f; } return (uint)Mathf.Max(1f, Mathf.Round(num / speedMul)); } private static int MulInt(string key, float mul) { if (!Defaults.TryGetValue(key, out var value)) { return 0; } return Mathf.Max(0, Mathf.RoundToInt(Convert.ToSingle(value) * mul)); } private static void SetUInt(PlayerVitals v, string key, uint value) { if (!Fields.TryGetValue(key, out var value2) || !Defaults.ContainsKey(key)) { return; } try { value2.SetValue(v, value); } catch (Exception) { } } private static void SetInt(PlayerVitals v, string key, int value) { if (!Fields.TryGetValue(key, out var value2) || !Defaults.ContainsKey(key)) { return; } try { value2.SetValue(v, value); } catch (Exception) { } } private static void SetFloat(PlayerVitals v, string key, float value) { if (!Fields.TryGetValue(key, out var value2) || !Defaults.ContainsKey(key)) { return; } try { value2.SetValue(v, value); } catch (Exception) { } } private static void RestoreInt(PlayerVitals v, string key) { Restore(v, key); } private static void RestoreFloat(PlayerVitals v, string key) { Restore(v, key); } private static void Restore(PlayerVitals v, string key) { if (!Fields.TryGetValue(key, out var value) || !Defaults.TryGetValue(key, out var value2)) { return; } try { value.SetValue(v, value2); } catch (Exception) { } } } internal static class WeightTable { private static FieldInfo _fFishable; private static FieldInfo _fWeight; private static bool _resolved; private static string _parsedFrom; private static Dictionary _perItem = new Dictionary(); internal static readonly HashSet LastRareSet = new HashSet(); internal static int MissStreak; internal static bool LastBuildValid; internal static bool Ready { get { Resolve(); if (_fFishable != null) { return _fWeight != null; } return false; } } internal static List Build(List original) { LastBuildValid = false; if (original == null || original.Count == 0) { return null; } if (!Ready) { return null; } ParsePerItem(); float num = 0f; for (int i = 0; i < original.Count; i++) { if (original[i] != null && original[i].Weight > num) { num = original[i].Weight; } } if (num <= 0f) { return null; } float num2 = num * Plugin.RareThreshold.Value; LastRareSet.Clear(); List list = new List(original.Count); List list2 = new List(); float num3 = 0f; float num4 = 0f; for (int j = 0; j < original.Count; j++) { ItemInfoWeight val = original[j]; if (val != null && !((Object)(object)val.Fishable == (Object)null)) { bool flag = IsBoss(val.Fishable); bool flag2 = flag || val.Weight <= num2; if (flag2) { LastRareSet.Add(val.Fishable); } float value; float num5 = (_perItem.TryGetValue(KeyOf(val.Fishable), out value) ? value : (flag ? Plugin.BossMultiplier.Value : ((!flag2) ? Plugin.CommonMultiplier.Value : Plugin.RareMultiplier.Value))); float num6 = Mathf.Max(0f, val.Weight * num5); ItemInfoWeight item = Make(val.Fishable, num6); list.Add(item); num3 += num6; if (flag2) { list2.Add(item); num4 += num6; } } } LastBuildValid = true; int value2 = Plugin.PityAfter.Value; if (value2 > 0 && MissStreak >= value2 && list2.Count > 0 && num4 > 0f) { if (Plugin.LogRolls.Value) { Plugin.Log.LogInfo((object)("保底觸發(連續 " + MissStreak + " 次非稀有),本次只抽稀有項。")); } return list2; } if (num3 <= 0f || list.Count == 0) { return null; } return list; } internal static bool IsBoss(Fishable f) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 if ((Object)(object)f == (Object)null || !Object.op_Implicit((Object)(object)f.ItemToSpawn)) { return false; } Item itemToSpawn = f.ItemToSpawn; Creature val = (Creature)(object)((itemToSpawn is Creature) ? itemToSpawn : null); if (Object.op_Implicit((Object)(object)val)) { return (int)val.BossType > 0; } return false; } internal static string KeyOf(Fishable f) { if ((Object)(object)f == (Object)null || !Object.op_Implicit((Object)(object)f.ItemToSpawn)) { return ""; } return ((Object)f.ItemToSpawn).name.Replace(" ", "").ToLowerInvariant(); } private static ItemInfoWeight Make(Fishable f, float weight) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ItemInfoWeight val = new ItemInfoWeight(); _fFishable.SetValue(val, f); _fWeight.SetValue(val, weight); return val; } private static void Resolve() { if (!_resolved) { _resolved = true; _fFishable = AccessTools.Field(typeof(ItemInfoWeight), "fishable"); _fWeight = AccessTools.Field(typeof(ItemInfoWeight), "_weight"); if (_fFishable == null || _fWeight == null) { Plugin.Log.LogWarning((object)"ItemInfoWeight 的欄位名對不上(遊戲可能又更新了),權重調整不會生效。"); } } } private static void ParsePerItem() { string text = Plugin.PerItem.Value ?? ""; if (text == _parsedFrom) { return; } _parsedFrom = text; Dictionary dictionary = new Dictionary(); string[] array = text.Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('='); if (array2.Length != 2) { continue; } string text2 = array2[0].Trim().Replace(" ", "").ToLowerInvariant(); if (text2.Length != 0) { if (!float.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { Plugin.Log.LogWarning((object)("個別倍率解析不了:" + array[i].Trim() + "(小數點請用 .)")); } else { dictionary[text2] = Mathf.Max(0f, result); } } } _perItem = dictionary; if (dictionary.Count > 0) { Plugin.Log.LogInfo((object)("個別倍率已載入 " + dictionary.Count + " 項。")); } } } internal static class ModInfo { public const string Name = "HtF Host Rules"; public const string Version = "1.1.0"; } }