Decompiled source of RollOfTheScoutClient v1.0.8
plugins/com.peakmodding.RollOfTheScoutClient.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Peak; using PeakNativeUi; using PeakOverheadCompat; using Photon.Pun; using Photon.Realtime; using RollOfTheScout.Shared; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("com.peakmodding.RollOfTheScoutClient")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.8.0")] [assembly: AssemblyInformationalVersion("1.0.8")] [assembly: AssemblyProduct("com.peakmodding.RollOfTheScoutClient")] [assembly: AssemblyTitle("RollOfTheScoutClient")] [assembly: AssemblyVersion("1.0.8.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RollOfTheScout.Shared { public enum MilesLocalGemUsage { Unknown, SelfUse, FeedNormal, FeedDowned } public sealed class MilesSoulLocal : MonoBehaviour, IOnEventCallback { private sealed class PendingReduce { public int Amount; public float FireAt; } private sealed class PendingZero { public int Target; public float FireAt; public float Until; public float NextWrite; public float Stamina; public bool StaminaApplied; } public const string ModId = "com.peakmodding.RollOfTheScout"; public const byte EventCode = 197; public const byte KindClientHello = 7; public const byte KindGameplayState = 8; public const byte KindUsageReport = 9; public const byte KindClientReady = 10; public const string AmuletHealing = "Amulet_Healing"; public const string AmuletSuperJump = "Amulet_SuperJump"; public const string AmuletClone = "Amulet_Clone"; public const string AmuletInfiniteStamina = "Amulet_InfiniteStamina"; private const float UseDedupWindow = 0.3f; private const float ReduceDelay = 0.35f; private const float ZeroCorrectionInterval = 0.25f; private const float ReadyAckInterval = 8f; private Action<string> _info; private Action<string> _warning; private bool _hasState; private int _classCode; private float _nextHello; private string _lastStateData; private float _nextAckTime; private float _nextSceneCheck; private bool _cachedAirport; private float _healingPetrify; private float _healingHunger; private float _superJumpPetrify; private float _superJumpHunger; private float _superJumpStamina; private float _clonePetrify; private float _cloneHunger; private float _cloneStamina; private float _ambitionPetrify; private float _ambitionHunger; private float _ambitionStamina; private bool _ruleZeroAssist; private float _ruleZeroStamina; private string _guardGem; private float _guardUntil; private int _preUsePetrify = -1; private float _preUseAt; private PendingReduce _reduce; private PendingZero _zero; public static MilesSoulLocal Instance { get; private set; } public void Configure(Action<string> info, Action<string> warning) { _info = info; _warning = warning; } private void Awake() { Instance = this; PhotonNetwork.AddCallbackTarget((object)this); } private void OnDestroy() { PhotonNetwork.RemoveCallbackTarget((object)this); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void Update() { if (Time.time >= _nextSceneCheck) { _nextSceneCheck = Time.time + 0.5f; _cachedAirport = IsInAirport(); } if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient || _cachedAirport) { ResetRun(); return; } bool isInGameplayScene = GameHandler.IsInGameplayScene; if (isInGameplayScene && Time.time >= _nextHello) { _nextHello = Time.time + 2f; SendToMaster(7, "1"); } if (isInGameplayScene && IsActiveFor(Character.localCharacter)) { TickReduce(); TickZero(); } } private static bool IsInAirport() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name.IndexOf("Airport", StringComparison.OrdinalIgnoreCase) >= 0; } private void ResetRun() { _hasState = false; _classCode = 0; _nextHello = 0f; _lastStateData = null; _nextAckTime = 0f; _guardGem = null; _guardUntil = 0f; _preUsePetrify = -1; _reduce = null; _zero = null; } public void OnEvent(EventData photonEvent) { if (photonEvent == null || photonEvent.Code != 197 || PhotonNetwork.IsMasterClient) { return; } Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null || photonEvent.Sender != masterClient.ActorNumber) { return; } try { if (photonEvent.CustomData is object[] array && array.Length >= 3 && array[0] is string text && !(text != "com.peakmodding.RollOfTheScout") && Convert.ToByte(array[1]) == 8) { ApplyGameplayState(array[2] as string); } } catch (Exception ex) { _warning?.Invoke("残魂本机状态同步失败:" + ex.Message); } } private void ApplyGameplayState(string data) { if (string.IsNullOrEmpty(data)) { return; } if (string.Equals(data, _lastStateData, StringComparison.Ordinal)) { if (_classCode == 26 && Time.time >= _nextAckTime) { SendReady(); } return; } string[] array = data.Split('|'); if (array.Length != 14 || !int.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return; } float[] array2 = new float[12]; int[] array3 = new int[12] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13 }; for (int i = 0; i < array2.Length; i++) { if (!float.TryParse(array[array3[i]], NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i])) { return; } } if (int.TryParse(array[12], out var result2)) { if (_classCode != result) { _reduce = null; _zero = null; _guardGem = null; _preUsePetrify = -1; } _classCode = result; _healingPetrify = array2[0]; _healingHunger = array2[1]; _superJumpPetrify = array2[2]; _superJumpHunger = array2[3]; _superJumpStamina = array2[4]; _clonePetrify = array2[5]; _cloneHunger = array2[6]; _cloneStamina = array2[7]; _ambitionPetrify = array2[8]; _ambitionHunger = array2[9]; _ambitionStamina = array2[10]; _ruleZeroAssist = result2 != 0; _ruleZeroStamina = array2[11]; _hasState = true; _lastStateData = data; if (_classCode == 26) { SendReady(); } } } private void SendReady() { _nextAckTime = Time.time + 8f; SendToMaster(10, "26"); } private static void SendToMaster(byte kind, string data) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null) { return; } try { object[] obj = new object[3] { "com.peakmodding.RollOfTheScout", kind, data }; RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { masterClient.ActorNumber }; PhotonNetwork.RaiseEvent((byte)197, (object)obj, val, SendOptions.SendReliable); } catch { } } public static bool TryParseUsageReport(string data, out string gemName, out int usage) { gemName = null; usage = 0; if (string.IsNullOrEmpty(data)) { return false; } int num = data.LastIndexOf('|'); if (num <= 0 || num + 1 >= data.Length) { return false; } if (!int.TryParse(data.Substring(num + 1), out usage)) { return false; } gemName = data.Substring(0, num); if (IsGem(gemName) && usage >= 0) { return usage <= 3; } return false; } public static bool IsGem(string name) { switch (name) { default: return name == "Amulet_InfiniteStamina"; case "Amulet_Healing": case "Amulet_SuperJump": case "Amulet_Clone": return true; } } private bool IsActiveFor(Character character) { if (!_hasState || _classCode != 26 || PhotonNetwork.IsMasterClient) { return false; } if ((Object)(object)character == (Object)null || (Object)(object)character != (Object)(object)Character.localCharacter) { return false; } if ((Object)(object)((MonoBehaviourPun)character).photonView != (Object)null) { return ((MonoBehaviourPun)character).photonView.IsMine; } return false; } public void SnapshotPreUse(Item item) { if (!((Object)(object)item == (Object)null)) { Character holderCharacter = item.holderCharacter; if (IsActiveFor(holderCharacter) && TryGemName(item, out var _) && (_preUsePetrify < 0 || !(Time.time - _preUseAt < 0.5f))) { _preUsePetrify = GetPetrify(holderCharacter); _preUseAt = Time.time; } } } public void NotifyGemUsed(Item item, Character holder, MilesLocalGemUsage usage, bool cloneSuccess = false) { if (!((Object)(object)item == (Object)null) && IsActiveFor(holder) && TryGemName(item, out var gemName)) { OnGemUsed(holder, gemName, usage, cloneSuccess); } } public void NotifyCloneUsed(Character holder, bool success) { if (success && IsActiveFor(holder)) { OnGemUsed(holder, "Amulet_Clone", MilesLocalGemUsage.SelfUse, cloneSuccess: true); } } private static bool TryGemName(Item item, out string gemName) { gemName = null; Item val = default(Item); if ((Object)(object)item == (Object)null || !ItemDatabase.TryGetItem(item.itemID, ref val) || (Object)(object)val == (Object)null) { return false; } gemName = ((Object)((Component)val).gameObject).name; return IsGem(gemName); } private void OnGemUsed(Character character, string gemName, MilesLocalGemUsage usage, bool cloneSuccess) { if ((gemName == "Amulet_Clone" && !cloneSuccess) || (_guardGem == gemName && Time.time < _guardUntil)) { return; } _guardGem = gemName; _guardUntil = Time.time + 0.3f; bool flag = _preUsePetrify >= 0 && Time.time - _preUseAt <= 1.5f; int num = (flag ? _preUsePetrify : GetPetrify(character)); _preUsePetrify = -1; switch (gemName) { case "Amulet_Healing": if (usage == MilesLocalGemUsage.FeedNormal) { _info?.Invoke("童军的毅力:喂给未昏迷队友,无职业效果。"); } else if (usage == MilesLocalGemUsage.FeedDowned && _ruleZeroAssist) { if (!flag) { _warning?.Invoke("零号守则:未记录到使用前的石化值,本次无法等量清除(请检查宝石动作 / 喂食前缀补丁是否生效)。"); break; } _zero = new PendingZero { Target = Mathf.Clamp(num, 0, 100), FireAt = Time.time + 0.35f, Until = Time.time + 3f, Stamina = _ruleZeroStamina }; } else { AdjustStatus(character, (STATUSTYPE)1, 0f - _healingHunger); QueueReduce(_healingPetrify); } break; case "Amulet_SuperJump": AdjustStatus(character, (STATUSTYPE)1, 0f - _superJumpHunger); QueueReduce(_superJumpPetrify); BoostStamina(character, _superJumpStamina); break; case "Amulet_Clone": AdjustStatus(character, (STATUSTYPE)1, 0f - _cloneHunger); QueueReduce(_clonePetrify); BoostStamina(character, _cloneStamina); break; case "Amulet_InfiniteStamina": AdjustStatus(character, (STATUSTYPE)1, 0f - _ambitionHunger); QueueReduce(_ambitionPetrify); BoostStamina(character, _ambitionStamina); break; } int num2 = (int)usage; SendToMaster(9, gemName + "|" + num2); } private void QueueReduce(float amount) { int num = Mathf.RoundToInt(Mathf.Max(0f, amount) * 100f); if (num > 0) { if (_reduce == null) { _reduce = new PendingReduce(); } _reduce.Amount += num; _reduce.FireAt = Time.time + 0.35f; } } private void TickReduce() { if (_reduce == null || Time.time < _reduce.FireAt) { return; } Character localCharacter = Character.localCharacter; if (!IsActiveFor(localCharacter)) { _reduce = null; return; } int petrify = GetPetrify(localCharacter); int num = Mathf.Clamp(petrify - _reduce.Amount, 0, 100); if (num != petrify) { localCharacter.data.SetPetrify(num); } _reduce = null; } private void TickZero() { if (_zero == null || Time.time < _zero.FireAt) { return; } Character localCharacter = Character.localCharacter; if (!IsActiveFor(localCharacter)) { _zero = null; return; } int num = GetPetrify(localCharacter); if (num > _zero.Target && Time.time >= _zero.NextWrite) { localCharacter.data.SetPetrify(_zero.Target); _zero.NextWrite = Time.time + 0.25f; num = _zero.Target; } if (num <= _zero.Target && !_zero.StaminaApplied) { BoostStamina(localCharacter, _zero.Stamina); _zero.StaminaApplied = true; } if (Time.time >= _zero.Until) { if (!_zero.StaminaApplied) { BoostStamina(localCharacter, _zero.Stamina); _zero.StaminaApplied = true; _warning?.Invoke($"零号守则:石化未在 3 秒窗口内回到使用前基准(当前 {num} > 基准 {_zero.Target})," + "已在窗口结束时兜底发放额外体力(可能受石化上限影响)。"); } _zero = null; } } private static int GetPetrify(Character character) { if (!((Object)(object)character == (Object)null) && !((Object)(object)character.data == (Object)null)) { return Mathf.Clamp(character.data.petrifyAmount, 0, 100); } return 0; } private static void AdjustStatus(Character character, STATUSTYPE type, float amount) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)character == (Object)null) && character.refs != null && !((Object)(object)character.refs.afflictions == (Object)null) && !Mathf.Approximately(amount, 0f)) { character.refs.afflictions.AdjustStatus(type, amount, false); } } private static void BoostStamina(Character character, float amount) { if (!((Object)(object)character == (Object)null) && !Mathf.Approximately(amount, 0f)) { character.MoraleBoost(amount, 1); } } private static bool IsDowned(Character character) { if ((Object)(object)character != (Object)null && (Object)(object)character.data != (Object)null && character.data.fullyPassedOut) { return !character.data.dead; } return false; } internal static MilesLocalGemUsage UsageOfFeedTarget(Character target) { if (!IsDowned(target)) { return MilesLocalGemUsage.FeedNormal; } return MilesLocalGemUsage.FeedDowned; } } public static class MilesLocalClonePatch { private static readonly FieldInfo ItemField = AccessTools.Field(typeof(ItemActionBase), "item"); public static void Postfix(Action_CloneSelectedItem __instance, bool success) { try { if (success && !((Object)(object)MilesSoulLocal.Instance == (Object)null)) { object? obj = ItemField?.GetValue(__instance); Item val = (Item)((obj is Item) ? obj : null); if (!((Object)(object)val == (Object)null)) { MilesSoulLocal.Instance.NotifyGemUsed(val, val.holderCharacter, MilesLocalGemUsage.SelfUse, cloneSuccess: true); } } } catch { } } } public static class MilesLocalGemUsePatch { private static readonly FieldInfo ItemField = AccessTools.Field(typeof(ItemActionBase), "item"); public static void ToggleGemPrefix(AmuletBase __instance) { MilesSoulLocal.Instance?.SnapshotPreUse(((ItemComponent)__instance).item); } public static void ToggleGemPostfix(AmuletBase __instance) { MilesSoulLocal.Instance?.NotifyGemUsed(((ItemComponent)__instance).item, ((ItemComponent)__instance).item.holderCharacter, MilesLocalGemUsage.SelfUse); } public static void HealingGemPrefix(Action_HealingGem __instance) { FromActionPrefix((ItemActionBase)(object)__instance); } public static void SuperJumpPrefix(Action_SuperJumpAmulet __instance) { FromActionPrefix((ItemActionBase)(object)__instance); } public static void HealingGemPostfix(Action_HealingGem __instance) { FromAction((ItemActionBase)(object)__instance); } public static void SuperJumpPostfix(Action_SuperJumpAmulet __instance) { FromAction((ItemActionBase)(object)__instance); } private static void FromActionPrefix(ItemActionBase action) { object? obj = ItemField?.GetValue(action); Item item = (Item)((obj is Item) ? obj : null); MilesSoulLocal.Instance?.SnapshotPreUse(item); } private static void FromAction(ItemActionBase action) { object? obj = ItemField?.GetValue(action); Item val = (Item)((obj is Item) ? obj : null); if (!((Object)(object)val == (Object)null)) { MilesSoulLocal.Instance?.NotifyGemUsed(val, val.holderCharacter, MilesLocalGemUsage.SelfUse); } } } public static class MilesLocalFeedPatch { private static readonly FieldInfo BestCharacterField = AccessTools.Field(typeof(Interaction), "bestCharacter"); private static Character FeedTarget() { if ((Object)(object)Interaction.instance == (Object)null || BestCharacterField == null) { return null; } object? value = BestCharacterField.GetValue(Interaction.instance); CharacterInteractible val = (CharacterInteractible)((value is CharacterInteractible) ? value : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.character; } public static void Prefix(Item __instance, out MilesLocalGemUsage __state) { __state = MilesLocalGemUsage.Unknown; if (!((Object)(object)__instance == (Object)null) && __instance.canUseOnFriend) { Character val = FeedTarget(); if (!((Object)(object)val == (Object)null)) { __state = MilesSoulLocal.UsageOfFeedTarget(val); MilesSoulLocal.Instance?.SnapshotPreUse(__instance); } } } public static void Postfix(Item __instance, MilesLocalGemUsage __state) { if (__state != MilesLocalGemUsage.Unknown && !((Object)(object)__instance == (Object)null)) { MilesSoulLocal.Instance?.NotifyGemUsed(__instance, __instance.holderCharacter, __state); } } } } namespace PeakNativeUi { public static class NativeFont { private static TMP_FontAsset _resolved; public static TMP_FontAsset Resolve() { if ((Object)(object)_resolved != (Object)null) { return _resolved; } TMP_FontAsset val = null; TMP_FontAsset val2 = null; TMP_FontAsset val3 = null; try { FontFallbackSwapper instance = FontFallbackSwapper.instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.mainBaseFont != (Object)null) { val = instance.mainBaseFont; } } catch { } try { TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); foreach (TMP_FontAsset val4 in array) { if (!((Object)(object)val4 == (Object)null) && HasCjk(val4)) { if ((Object)(object)val == (Object)null && ((Object)val4).name != null && ((Object)val4).name.IndexOf("Daruma", StringComparison.OrdinalIgnoreCase) >= 0) { val = val4; } if ((Object)(object)val2 == (Object)null && HasChar(val4, 'A')) { val2 = val4; } if ((Object)(object)val3 == (Object)null && HasChar(val4, 'A') && LooksThin(val4)) { val3 = val4; } } } } catch { } if ((Object)(object)val3 == (Object)null && (Object)(object)val2 == (Object)null) { try { PlayerName[] array2 = Resources.FindObjectsOfTypeAll<PlayerName>(); foreach (PlayerName val5 in array2) { if (!((Object)(object)val5 == (Object)null) && !((Object)(object)val5.text == (Object)null) && !((Object)(object)((TMP_Text)val5.text).font == (Object)null) && HasCjk(((TMP_Text)val5.text).font) && HasChar(((TMP_Text)val5.text).font, 'A')) { val2 = ((TMP_Text)val5.text).font; if ((Object)(object)val3 == (Object)null && LooksThin(((TMP_Text)val5.text).font)) { val3 = ((TMP_Text)val5.text).font; } break; } } } catch { } } if ((Object)(object)val3 != (Object)null) { return _resolved = val3; } if ((Object)(object)val2 != (Object)null) { return _resolved = val2; } if ((Object)(object)val != (Object)null && HasCjk(val) && HasChar(val, 'A')) { return _resolved = val; } try { TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset; if ((Object)(object)defaultFontAsset != (Object)null && HasCjk(defaultFontAsset)) { return _resolved = defaultFontAsset; } } catch { } return null; } private static bool LooksThin(TMP_FontAsset f) { if ((Object)(object)f == (Object)null || string.IsNullOrEmpty(((Object)f).name)) { return false; } string name = ((Object)f).name; if (name.IndexOf("Daruma", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } if (name.IndexOf("Noto", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Sans", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("SourceHan", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Source Han", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("CJK", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Hei", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("Gothic", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static bool HasChar(TMP_FontAsset font, char c) { try { return (Object)(object)font != (Object)null && font.HasCharacter(c, true, false); } catch { return false; } } private static bool HasCjk(TMP_FontAsset font) { try { return (Object)(object)font != (Object)null && font.HasCharacter('职', true, false); } catch { return false; } } } public static class NativeOverlay { private sealed class Block { public RectTransform Root; public Image Bg; public TextMeshProUGUI Text; public float WidthFraction; public bool Bold; public string Last; public float LastSize; public int LastScreenHeight; public int LastVersion = -1; } private static bool _ready; private static float _nextTry; private static GameObject _root; private static Block _toast; private static Block _panel; private static Block _intro; public static bool Ready { get { if (_ready && (Object)(object)_root == (Object)null) { _ready = false; } return _ready; } } public static bool Ensure() { if (Ready) { return true; } if (Time.time < _nextTry) { return false; } _nextTry = Time.time + 1f; try { TMP_FontAsset val = NativeFont.Resolve(); if ((Object)(object)val == (Object)null) { return false; } Build(val); _ready = true; return true; } catch (Exception ex) { Debug.LogWarning((object)("[PeakNativeUi] 创建原生字体叠加层失败(将回退旧绘制):" + ex.Message)); return false; } } public static void SetToast(IList<string> lines) { if (Ready) { SetBlock(_toast, Join(lines, null), _toast.Bold); PositionToast(); } } public static void SetToast(IList<string> lines, int version) { if (Ready) { Block toast = _toast; if (toast != null && (version < 0 || version != toast.LastVersion)) { toast.LastVersion = version; SetBlock(toast, Join(lines, null), toast.Bold); PositionToast(); } } } public static void SetPanel(string title, IList<string> lines, bool visible) { SetPanel(title, lines, visible, -1); } public static void SetPanel(string title, IList<string> lines, bool visible, int version) { if (Ready) { Block panel = _panel; if (!visible) { SetBlock(panel, null, panel.Bold); panel.LastVersion = int.MinValue; SetBlock(_intro, null, _intro.Bold); _intro.LastVersion = int.MinValue; PositionToast(); } else if (version < 0 || version != panel.LastVersion) { panel.LastVersion = version; SetBlock(panel, Join(lines, title), panel.Bold); } } } public static void SetPanelHidden() { if (Ready) { SetBlock(_panel, null, _panel.Bold); _panel.LastVersion = int.MinValue; SetBlock(_intro, null, _intro.Bold); _intro.LastVersion = int.MinValue; PositionToast(); } } public static void SetClassIntro(string title, IList<string> lines, bool visible) { SetClassIntro(title, lines, visible, -1); } public static void SetClassIntro(string title, IList<string> lines, bool visible, int version) { if (!Ready) { return; } Block intro = _intro; if (!visible) { SetBlock(intro, null, intro.Bold); intro.LastVersion = int.MinValue; PositionToast(); return; } if (version < 0 || version != intro.LastVersion) { intro.LastVersion = version; SetBlock(intro, Join(lines, title), intro.Bold); } PositionToast(); } public static void SetClassIntroHidden() { if (Ready) { SetBlock(_intro, null, _intro.Bold); _intro.LastVersion = int.MinValue; PositionToast(); } } private static void PositionToast() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) Block toast = _toast; if (toast != null && !((Object)(object)toast.Root == (Object)null)) { float num = 12f; if (_intro != null && (Object)(object)_intro.Root != (Object)null && ((Component)_intro.Root).gameObject.activeSelf) { num += _intro.Root.sizeDelta.y + 8f; } toast.Root.anchoredPosition = new Vector2(12f, 0f - num); } } public static void Dispose() { try { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } } catch { } _root = null; _toast = null; _panel = null; _intro = null; _ready = false; _nextTry = 0f; } private static void Build(TMP_FontAsset font) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown _root = new GameObject("RollOfTheScout.NativeOverlay", new Type[1] { typeof(RectTransform) }); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32000; _toast = CreateBlock("Toast", font, rightSide: false, 0.45f, bold: false); _panel = CreateBlock("ClassPanel", font, rightSide: true, 0.32f, bold: false); _intro = CreateBlock("ClassIntro", font, rightSide: false, 0.4f, bold: false); } private static Block CreateBlock(string name, TMP_FontAsset font, bool rightSide, float widthFraction, bool bold) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: 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_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_root.transform, false); RectTransform component = val.GetComponent<RectTransform>(); float num = (rightSide ? 1f : 0f); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(num, 1f); component.anchorMax = val2; component.anchorMin = val2; component.pivot = new Vector2(num, 1f); component.anchoredPosition = (rightSide ? new Vector2(-12f, -12f) : new Vector2(12f, -12f)); component.sizeDelta = new Vector2(400f, 40f); Image val3 = val.AddComponent<Image>(); ((Graphic)val3).color = new Color(0f, 0f, 0f, 0.6f); ((Graphic)val3).raycastTarget = false; GameObject val4 = new GameObject("Text", new Type[1] { typeof(RectTransform) }); val4.transform.SetParent(val.transform, false); RectTransform component2 = val4.GetComponent<RectTransform>(); ((Vector2)(ref val2))..ctor(0f, 1f); component2.anchorMax = val2; component2.anchorMin = val2; component2.pivot = new Vector2(0f, 1f); component2.anchoredPosition = new Vector2(10f, -6f); TextMeshProUGUI val5 = val4.AddComponent<TextMeshProUGUI>(); ((TMP_Text)val5).font = font; if ((Object)(object)((TMP_Asset)font).material != (Object)null) { ((TMP_Text)val5).fontSharedMaterial = ((TMP_Asset)font).material; } ((Graphic)val5).raycastTarget = false; ((TMP_Text)val5).alignment = (TextAlignmentOptions)257; ((TMP_Text)val5).textWrappingMode = (TextWrappingModes)1; ((TMP_Text)val5).overflowMode = (TextOverflowModes)0; ((Graphic)val5).color = Color.white; ((TMP_Text)val5).fontStyle = (FontStyles)(bold ? 1 : 0); return new Block { Root = component, Bg = val3, Text = val5, WidthFraction = widthFraction, Bold = bold }; } private static void SetBlock(Block b, string content, bool bold) { //IL_00f4: 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_012d: Unknown result type (might be due to invalid IL or missing references) if (b == null || (Object)(object)b.Root == (Object)null) { return; } if (string.IsNullOrEmpty(content)) { if (((Component)b.Root).gameObject.activeSelf) { ((Component)b.Root).gameObject.SetActive(false); } b.Last = null; return; } if (!((Component)b.Root).gameObject.activeSelf) { ((Component)b.Root).gameObject.SetActive(true); } float num = CurrentFontSize(); if (!(content == b.Last) || !Mathf.Approximately(num, b.LastSize) || Screen.height != b.LastScreenHeight) { b.Last = content; b.LastSize = num; b.LastScreenHeight = Screen.height; float num2 = (float)Screen.width * b.WidthFraction; TextMeshProUGUI text = b.Text; ((TMP_Text)text).fontSize = num; ((TMP_Text)text).fontStyle = (FontStyles)(bold ? 1 : 0); ((TMP_Text)text).text = content; RectTransform rectTransform = ((TMP_Text)text).rectTransform; rectTransform.sizeDelta = new Vector2(num2 - 20f, 4000f); ((TMP_Text)text).ForceMeshUpdate(false, false); float preferredHeight = ((TMP_Text)text).preferredHeight; rectTransform.sizeDelta = new Vector2(num2 - 20f, preferredHeight); b.Root.sizeDelta = new Vector2(num2, preferredHeight + 12f); } } private static float CurrentFontSize() { return Mathf.Max(16, Mathf.RoundToInt((float)Screen.height / 48f)); } private static string Join(IList<string> lines, string title) { if ((lines == null || lines.Count == 0) && string.IsNullOrEmpty(title)) { return null; } StringBuilder stringBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(title)) { stringBuilder.Append("<color=#FFE666>").Append(title).Append("</color>"); } if (lines != null) { for (int i = 0; i < lines.Count; i++) { if (!string.IsNullOrEmpty(lines[i])) { if (stringBuilder.Length > 0) { stringBuilder.Append('\n'); } stringBuilder.Append(lines[i]); } } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return null; } } } namespace PeakOverheadCompat { public static class ThirdPartyOverheadNameCompat { internal static class BpdExtractNamePatch { public static void Postfix(string objName, ref string __result) { try { if (Active && !string.IsNullOrEmpty(__result) && TryActorFromObjName(objName, out var actor) && !string.IsNullOrEmpty(ClassNameOf(actor))) { __result = Rebuild(__result, actor); } } catch { } } } internal static class BpdLabelPatch { private sealed class LabelState { public string Original; public string Desired; } private static readonly ConditionalWeakTable<object, LabelState> States = new ConditionalWeakTable<object, LabelState>(); public static void Postfix(object __instance) { try { if (__instance == null || _bpdDisplayNameField == null || _bpdCharacterField == null || _bpdLastContentField == null) { return; } object? value = _bpdCharacterField.GetValue(__instance); Character val = (Character)((value is Character) ? value : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)((MonoBehaviourPun)val).photonView == (Object)null) && ((MonoBehaviourPun)val).photonView.Owner != null) { int actorNumber = ((MonoBehaviourPun)val).photonView.Owner.ActorNumber; LabelState orCreateValue = States.GetOrCreateValue(__instance); if (orCreateValue.Original == null) { orCreateValue.Original = Strip(_bpdDisplayNameField.GetValue(__instance) as string); } string text = (string.IsNullOrEmpty(orCreateValue.Original) ? orCreateValue.Original : Rebuild(orCreateValue.Original, actorNumber)); if (!(orCreateValue.Desired == text)) { orCreateValue.Desired = text; _bpdDisplayNameField.SetValue(__instance, text); _bpdLastContentField.SetValue(__instance, null); } } } catch { } } } internal static class SodRefreshPatch { public static void Prefix(object __instance, ref string name) { try { if (__instance == null || string.IsNullOrEmpty(name) || !Active || _sodEntriesField == null || _sodEntryLabelField == null || (_sodInstanceProp == null && _sodInstanceField == null)) { return; } object obj = ((_sodInstanceProp != null) ? _sodInstanceProp.GetValue(null) : _sodInstanceField.GetValue(null)); if (obj == null || !(_sodEntriesField.GetValue(obj) is IDictionary dictionary)) { return; } foreach (DictionaryEntry item in dictionary) { if (_sodEntryLabelField.GetValue(item.Value) == __instance) { object key = item.Key; Character val = (Character)((key is Character) ? key : null); if (val != null && !((Object)(object)((MonoBehaviourPun)val).photonView == (Object)null) && ((MonoBehaviourPun)val).photonView.Owner != null) { name = Rebuild(name, ((MonoBehaviourPun)val).photonView.Owner.ActorNumber); } break; } } } catch { } } } private const string BpdDll = "BetterPlayerDistance.dll"; private const string SodDll = "SenseOfDirection.dll"; private static Func<int, string> _classNameByActor; private static Action<string> _warn; private static bool _initialized; private static readonly Dictionary<int, string> _colorByViewId = new Dictionary<int, string>(); private static readonly Dictionary<int, Character> _characterByActor = new Dictionary<int, Character>(); private static float _nextTry; private static bool _bpdDone; private static bool _sodDone; private static bool _bpdApplied; private static bool _sodApplied; private static bool _loggedApplied; private static FieldInfo _bpdCharacterField; private static FieldInfo _bpdDisplayNameField; private static FieldInfo _bpdLastContentField; private static FieldInfo _sodEntriesField; private static FieldInfo _sodEntryLabelField; private static PropertyInfo _sodInstanceProp; private static FieldInfo _sodInstanceField; private const string ColorFallback = "FFFFFF"; private const float ColorRetrySeconds = 0.5f; private static readonly Dictionary<int, float> _colorRetryAt = new Dictionary<int, float>(); internal static bool Active { get { if (_initialized) { return _classNameByActor != null; } return false; } } public static void Initialize(Func<int, string> classNameByActor, Action<string> warn) { _classNameByActor = classNameByActor; _warn = warn; _initialized = true; } public static void Tick(Harmony harmony) { if (_initialized && harmony != null && (!_bpdDone || !_sodDone) && !(Time.time < _nextTry)) { _nextTry = Time.time + 1f; if (!_bpdDone) { _bpdDone = !Installed("BetterPlayerDistance.dll") || ApplyBpd(harmony); } if (!_sodDone) { _sodDone = !Installed("SenseOfDirection.dll") || ApplySod(harmony); } if (_bpdApplied && _sodApplied && !_loggedApplied) { _loggedApplied = true; Warn("[兼容] 已挂载第三方头顶id显示模组兼容补丁(BetterPlayerDistance / Sense of Direction)。"); } } } private static bool Installed(string dllFileName) { try { string pluginPath = Paths.PluginPath; if (!string.IsNullOrEmpty(pluginPath) && Directory.Exists(pluginPath)) { return Directory.GetFiles(pluginPath, dllFileName, SearchOption.AllDirectories).Length != 0; } } catch { } return false; } private static void Warn(string msg) { try { _warn?.Invoke(msg); } catch { } } private static Type FindType(string assemblyName, string simpleName) { try { Assembly assembly = FindAssembly(assemblyName); if (assembly != null) { Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (type != null && type.Name == simpleName) { return type; } } } } catch { } return null; } private static Assembly FindAssembly(string simpleName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly != null && assembly.GetName().Name == simpleName) { return assembly; } } } catch { } return null; } private static bool ApplyBpd(Harmony harmony) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown try { Type type = FindType("BetterPlayerDistance", "Tracking"); Type type2 = FindType("BetterPlayerDistance", "PlayerLabel"); if (type == null || type2 == null) { return false; } MethodInfo method = type.GetMethod("ExtractDisplayName", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method2 = type2.GetMethod("UpdateState", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _bpdCharacterField = type2.GetField("_character", BindingFlags.Instance | BindingFlags.NonPublic); _bpdDisplayNameField = type2.GetField("_displayName", BindingFlags.Instance | BindingFlags.NonPublic); _bpdLastContentField = type2.GetField("_lastContent", BindingFlags.Instance | BindingFlags.NonPublic); if (method == null || method2 == null || _bpdCharacterField == null || _bpdDisplayNameField == null || _bpdLastContentField == null) { return false; } harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(BpdExtractNamePatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(BpdLabelPatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _bpdApplied = true; return true; } catch (Exception ex) { Warn("[兼容] BetterPlayerDistance 补丁失败:" + ex.Message); return true; } } private static bool ApplySod(Harmony harmony) { //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Expected O, but got Unknown try { Type type = FindType("SenseOfDirection", "PlayerLabelController"); Type type2 = FindType("SenseOfDirection", "PlayerLabel"); if (type == null || type2 == null) { return false; } _sodEntriesField = type.GetField("_entries", BindingFlags.Instance | BindingFlags.NonPublic); _sodInstanceProp = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _sodInstanceField = ((_sodInstanceProp == null) ? type.GetField("_instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : null); _sodEntryLabelField = type.GetNestedType("Entry", BindingFlags.NonPublic)?.GetField("Label", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo methodInfo = null; MethodInfo[] methods = type2.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "Refresh")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length >= 1 && parameters[0].ParameterType == typeof(string)) { methodInfo = methodInfo2; break; } } } if (methodInfo == null || _sodEntriesField == null || (_sodInstanceProp == null && _sodInstanceField == null) || _sodEntryLabelField == null) { return false; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(SodRefreshPatch), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _sodApplied = true; return true; } catch (Exception ex) { Warn("[兼容] Sense of Direction 补丁失败:" + ex.Message); return true; } } internal static string ClassNameOf(int actor) { if (_classNameByActor == null) { return null; } try { return _classNameByActor(actor); } catch { return null; } } internal static Character CharacterOf(int actor) { if (_characterByActor.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null) { return value; } List<Character> allCharacters = Character.AllCharacters; if (allCharacters != null) { for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)((MonoBehaviourPun)val).photonView == (Object)null) && ((MonoBehaviourPun)val).photonView.Owner != null && ((MonoBehaviourPun)val).photonView.Owner.ActorNumber == actor) { _characterByActor[actor] = val; return val; } } } return null; } internal static string ColorHexOf(int actor) { Character val = CharacterOf(actor); int num = (((Object)(object)val != (Object)null && (Object)(object)((MonoBehaviourPun)val).photonView != (Object)null) ? ((MonoBehaviourPun)val).photonView.ViewID : 0); if (num != 0 && _colorByViewId.TryGetValue(num, out var value)) { return value; } if (_colorRetryAt.TryGetValue(actor, out var value2) && Time.time < value2) { return "FFFFFF"; } if (TryReadColorOf(val, out var hex)) { if (num != 0) { _colorByViewId[num] = hex; } return hex; } _colorRetryAt[actor] = Time.time + 0.5f; return "FFFFFF"; } private static bool TryReadColorOf(Character ch, out string hex) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) hex = "FFFFFF"; try { if ((Object)(object)ch == (Object)null || ch.refs == null || (Object)(object)ch.refs.customization == (Object)null) { return false; } hex = ColorUtility.ToHtmlStringRGB(ch.refs.customization.PlayerColor); return true; } catch { return false; } } internal static string BuildPrefix(int actor) { if (!Active) { return null; } string text = ClassNameOf(actor); if (string.IsNullOrEmpty(text)) { return null; } return "<color=#" + ColorHexOf(actor) + ">【" + text + "】</color>"; } internal static string Strip(string text) { if (string.IsNullOrEmpty(text)) { return text; } if (!text.StartsWith("<color=#", StringComparison.Ordinal)) { return text; } int num = text.IndexOf("</color>", StringComparison.Ordinal); if (num < 0) { return text; } int num2 = text.IndexOf('】'); if (num2 < 0 || num2 > num) { return text; } return text.Substring(num + "</color>".Length); } internal static string Rebuild(string text, int actor) { string text2 = Strip(text); string text3 = BuildPrefix(actor); if (text3 != null) { return text3 + text2; } return text2; } internal static bool TryActorFromObjName(string objName, out int actor) { actor = 0; if (string.IsNullOrEmpty(objName)) { return false; } int num = objName.IndexOf('['); int num2 = objName.LastIndexOf(':'); if (num == -1 || num2 == -1 || num2 <= num) { return false; } return int.TryParse(objName.Substring(num2 + 1).Trim().TrimEnd(']') .Trim(), out actor); } } } namespace RollOfTheScoutClient { public sealed class ClassDisplayReceiver : MonoBehaviour, IOnEventCallback { private const string ModId = "com.peakmodding.RollOfTheScout"; private const byte EventCode = 197; private const byte KindClassSnapshot = 1; private const byte KindToast = 2; private const byte KindClassNames = 3; private const byte KindClassKeywords = 4; private const byte KindClassIntro = 5; private const byte KindTargetedToast = 6; private const byte KindModeSnapshot = 11; private const byte KindModeOff = 12; private readonly List<KeyValuePair<int, int>> _remoteClasses = new List<KeyValuePair<int, int>>(); private readonly Dictionary<int, int> _remoteClassMap = new Dictionary<int, int>(); private bool _hasRemoteSnapshot; private readonly List<KeyValuePair<int, int>> _remoteModeStatuses = new List<KeyValuePair<int, int>>(); private bool _hasRemoteMode; private readonly Dictionary<int, string> _classNameByCode = new Dictionary<int, string>(); private readonly Dictionary<int, string> _remoteKeywords = new Dictionary<int, string>(); private readonly Dictionary<int, string> _remoteIntro = new Dictionary<int, string>(); private readonly List<string> _toasts = new List<string>(); private readonly List<float> _toastExpiries = new List<float>(); private const float ToastDuration = 8f; private const int ToastMaxCount = 5; private bool _classListHeld; private bool _classListToggled; private float _classListPressUntil; private const float ClassListPressDuration = 5f; private GUIStyle _toastStyle; private readonly List<float> _toastHeights = new List<float>(); private readonly GUIContent _toastMeasure = new GUIContent(); private GUIStyle _classListStyle; private GUIStyle _classListTitleStyle; private GUIStyle _classIntroStyle; private GUIStyle _classIntroTitleStyle; private float _imguiIntroBottom; private int _classIntroCacheCode = -1; private string _classIntroCacheTitle; private List<string> _classIntroCacheLines; private int _introVersion = 1; private int _introCacheVersion; private int _panelSignature = int.MinValue; private List<string> _panelLinesCache; public static ClassDisplayReceiver Instance { get; private set; } private void Awake() { Instance = this; PhotonNetwork.AddCallbackTarget((object)this); } private void OnDestroy() { PhotonNetwork.RemoveCallbackTarget((object)this); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private static bool IsHostNow() { if (PhotonNetwork.InRoom) { return PhotonNetwork.IsMasterClient; } return false; } private void Update() { ThirdPartyOverheadNameCompat.Tick(Plugin.HarmonyInstance); if (IsHostNow()) { ClearReceivedData(); _toasts.Clear(); _toastExpiries.Clear(); PushNativeOverlay(); return; } ExpireToasts(); UpdateClassListInput(); if (!IsInGameplayScene() && (_remoteClasses.Count > 0 || _toasts.Count > 0)) { ClearReceivedData(); _toasts.Clear(); _toastExpiries.Clear(); } PushNativeOverlay(); } private void ClearReceivedData() { _remoteClasses.Clear(); _remoteClassMap.Clear(); _remoteModeStatuses.Clear(); _remoteKeywords.Clear(); _remoteIntro.Clear(); _hasRemoteSnapshot = false; _hasRemoteMode = false; _panelSignature = int.MinValue; _panelLinesCache = null; InvalidateIntroCache(); } private void PushNativeOverlay() { if (NativeOverlay.Ensure()) { NativeOverlay.SetToast(_toasts); bool flag = ClassListVisible() && (_hasRemoteMode || (_hasRemoteSnapshot && _remoteClasses.Count > 0)); List<string> list = (flag ? BuildPanelLines() : null); if (list != null && list.Count == 0) { list = null; flag = false; } string text = "[职业分配 / Class List] " + ClientConfig.ClassListKey.Value; NativeOverlay.SetPanel(text, list, flag, _panelSignature * 31 + text.GetHashCode()); if (flag && TryBuildClassIntro(LocalClassCode(), out var title, out var lines)) { NativeOverlay.SetClassIntro(title, lines, visible: true, _introVersion); } else { NativeOverlay.SetClassIntroHidden(); } } } private int LocalClassCode() { Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null) { return 0; } return ClassCodeOf(localCharacter); } private bool TryBuildClassIntro(int code, out string title, out List<string> lines) { title = null; lines = null; if (code <= 0) { return false; } if (code == _classIntroCacheCode && _introCacheVersion == _introVersion && _classIntroCacheLines != null) { title = _classIntroCacheTitle; lines = _classIntroCacheLines; return true; } string text = ClassIntroText.KeywordsLine(code); string text2 = ClassIntroText.IntroLine(code); if (string.IsNullOrEmpty(text) && _remoteKeywords.TryGetValue(code, out var value)) { text = value; } if (string.IsNullOrEmpty(text2) && _remoteIntro.TryGetValue(code, out var value2)) { text2 = value2; } if (string.IsNullOrEmpty(text) && string.IsNullOrEmpty(text2)) { return false; } List<string> list = new List<string>(2); if (!string.IsNullOrEmpty(text)) { list.Add(text); } if (!string.IsNullOrEmpty(text2)) { list.Add(text2); } _classIntroCacheCode = code; _introCacheVersion = _introVersion; _classIntroCacheTitle = ClassIntroText.TitlePrefix + NameOf(code); _classIntroCacheLines = list; title = _classIntroCacheTitle; lines = list; return true; } private void InvalidateIntroCache() { _introVersion++; _classIntroCacheCode = -1; _classIntroCacheLines = null; } private List<string> BuildPanelLines() { int num = ComputePanelSignature(); if (num == _panelSignature && _panelLinesCache != null) { return _panelLinesCache; } List<KeyValuePair<int, int>> list = (_hasRemoteMode ? _remoteModeStatuses : _remoteClasses); List<string> list2 = new List<string>(list.Count); Room currentRoom = PhotonNetwork.CurrentRoom; for (int i = 0; i < list.Count; i++) { KeyValuePair<int, int> keyValuePair = list[i]; Player val = ((currentRoom != null) ? currentRoom.GetPlayer(keyValuePair.Key, false) : null); string text = ((val != null && !string.IsNullOrEmpty(val.NickName)) ? val.NickName : $"#{keyValuePair.Key}"); list2.Add(text + ":" + (_hasRemoteMode ? ModeStatusName(keyValuePair.Value) : NameOf(keyValuePair.Value))); } _panelSignature = num; _panelLinesCache = list2; return list2; } private int ComputePanelSignature() { int num = 17; num = num * 31 + (_hasRemoteMode ? 1 : 0); Room currentRoom = PhotonNetwork.CurrentRoom; List<KeyValuePair<int, int>> list = (_hasRemoteMode ? _remoteModeStatuses : _remoteClasses); for (int i = 0; i < list.Count; i++) { KeyValuePair<int, int> keyValuePair = list[i]; num = num * 31 + keyValuePair.Key; num = num * 31 + keyValuePair.Value; Player val = ((currentRoom != null) ? currentRoom.GetPlayer(keyValuePair.Key, false) : null); if (val != null && !string.IsNullOrEmpty(val.NickName)) { num = num * 31 + val.NickName.GetHashCode(); } } return num; } public void OnEvent(EventData photonEvent) { if (photonEvent == null || photonEvent.Code != 197 || IsHostNow()) { return; } Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null || photonEvent.Sender != masterClient.ActorNumber) { return; } try { if (!(photonEvent.CustomData is object[] array) || array.Length < 3 || !(array[0] is string text) || text != "com.peakmodding.RollOfTheScout") { return; } byte b = Convert.ToByte(array[1]); string text2 = array[2] as string; if (!string.IsNullOrEmpty(text2)) { switch (b) { case 1: SetSnapshot(text2); break; case 11: SetModeSnapshot(text2); break; case 12: ClearModeSnapshot(); break; case 3: SetClassNames(text2); break; case 4: case 5: SetClassText(b, text2); break; case 6: AddTargetedToast(text2); break; case 2: AddToast(text2); break; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[RollOfTheScoutClient] 处理职业显示事件时异常:" + ex.Message)); } } private void SetSnapshot(string data) { _remoteClasses.Clear(); _remoteClassMap.Clear(); string[] array = data.Split(';'); foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { int num = text.IndexOf('='); if (num > 0 && int.TryParse(text.Substring(0, num), out var result) && int.TryParse(text.Substring(num + 1), out var result2)) { _remoteClasses.Add(new KeyValuePair<int, int>(result, result2)); _remoteClassMap[result] = result2; } } } _hasRemoteSnapshot = _remoteClasses.Count > 0; } private void SetModeSnapshot(string data) { _remoteModeStatuses.Clear(); string[] array = data.Split(';'); foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { int num = text.IndexOf('='); if (num > 0 && int.TryParse(text.Substring(0, num), out var result) && int.TryParse(text.Substring(num + 1), out var result2)) { _remoteModeStatuses.Add(new KeyValuePair<int, int>(result, result2)); } } } _hasRemoteMode = _remoteModeStatuses.Count > 0; _panelSignature = int.MinValue; _panelLinesCache = null; } private void ClearModeSnapshot() { _remoteModeStatuses.Clear(); _hasRemoteMode = false; _panelSignature = int.MinValue; _panelLinesCache = null; } private static string ModeStatusName(int status) { return status switch { 0 => "Alive · 存活", 1 => "Dead · 死亡", _ => "Error · 异常", }; } private void SetClassNames(string data) { string[] array = data.Split(';'); foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { int num = text.IndexOf('='); if (num > 0 && num + 1 < text.Length && int.TryParse(text.Substring(0, num), out var result)) { _classNameByCode[result] = text.Substring(num + 1); } } } InvalidateIntroCache(); } private void SetClassText(byte kind, string data) { Dictionary<int, string> dictionary = ((kind == 4) ? _remoteKeywords : _remoteIntro); string[] array = data.Split(';'); foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { int num = text.IndexOf('='); if (num > 0 && num + 1 < text.Length && int.TryParse(text.Substring(0, num), out var result)) { dictionary[result] = text.Substring(num + 1); } } } InvalidateIntroCache(); } public string NameOf(int code) { if (_classNameByCode.TryGetValue(code, out var value) && !string.IsNullOrEmpty(value)) { return value; } return ClassNames.Describe(code); } public string ClassNameOrEmptyOfActor(int actor) { int num = ClassCodeOfActor(actor); if (num != 0) { return NameOf(num); } return string.Empty; } public int ClassCodeOf(Character character) { Player val = (((Object)(object)character != (Object)null && (Object)(object)((MonoBehaviourPun)character).photonView != (Object)null) ? ((MonoBehaviourPun)character).photonView.Owner : null); if (val != null && _remoteClassMap.TryGetValue(val.ActorNumber, out var value)) { return value; } return 0; } public int ClassCodeOfActor(int actor) { if (!_remoteClassMap.TryGetValue(actor, out var value)) { return 0; } return value; } public void OnOverheadNameTick(UIPlayerNames ui, int index) { if (!ClientConfig.NamePrefix.Value || (Object)(object)ui == (Object)null) { return; } PlayerName[] playerNameText = ui.playerNameText; if (playerNameText == null || index < 0 || index >= playerNameText.Length) { return; } PlayerName val = playerNameText[index]; if (!((Object)(object)val == (Object)null)) { CharacterInteractible characterInteractable = val.characterInteractable; Character val2 = (((Object)(object)characterInteractable != (Object)null) ? characterInteractable.character : null); if (!((Object)(object)val2 == (Object)null)) { OverheadNamePrefix.Apply(val, val2, OverheadClassNameOf); } } } private string OverheadClassNameOf(Character character) { int num = ClassCodeOf(character); if (num != 0) { return NameOf(num); } return string.Empty; } private void AddTargetedToast(string data) { int num = data.IndexOf('\t'); if (num > 0 && int.TryParse(data.Substring(0, num), out var result) && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == result) { AddToast(data.Substring(num + 1)); } } private void AddToast(string text) { if (!string.IsNullOrEmpty(text)) { if (_toasts.Count >= 5) { _toasts.RemoveAt(0); _toastExpiries.RemoveAt(0); } _toasts.Add(text); _toastExpiries.Add(Time.time + 8f); } } private void ExpireToasts() { if (_toasts.Count == 0) { return; } float time = Time.time; for (int num = _toasts.Count - 1; num >= 0; num--) { if (time >= _toastExpiries[num]) { _toasts.RemoveAt(num); _toastExpiries.RemoveAt(num); } } } private static bool IsInGameplayScene() { try { return GameHandler.IsInGameplayScene; } catch { return false; } } private void UpdateClassListInput() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) KeyCode val = ClientConfig.ParseKey(); int num = ClientConfig.ClassListModeKind(); bool keyDown = Input.GetKeyDown(val); bool key = Input.GetKey(val); switch (num) { case 1: _classListHeld = key; break; case 3: if (keyDown) { _classListToggled = !_classListToggled; } break; default: if (keyDown) { _classListPressUntil = Time.time + 5f; } break; } } private bool ClassListVisible() { return ClientConfig.ClassListModeKind() switch { 1 => _classListHeld, 3 => _classListToggled, _ => Time.time < _classListPressUntil, }; } private void OnGUI() { if (!NativeOverlay.Ready) { _imguiIntroBottom = 0f; DrawClassList(); DrawToasts(); } } private void DrawClassList() { //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) if (!IsHostNow() && ClassListVisible() && _hasRemoteSnapshot) { List<string> list = new List<string>(_remoteClasses.Count + 1); list.Add("[职业分配 / Class List] " + ClientConfig.ClassListKey.Value); list.AddRange(BuildPanelLines()); if (_classListStyle == null) { int fontSize = Mathf.Max(16, Mathf.RoundToInt((float)Screen.height / 48f)); _classListTitleStyle = new GUIStyle(GUI.skin.label) { fontSize = fontSize, fontStyle = (FontStyle)0, alignment = (TextAnchor)3 }; _classListTitleStyle.normal.textColor = new Color(1f, 0.9f, 0.4f); _classListStyle = new GUIStyle(GUI.skin.label) { fontSize = fontSize, alignment = (TextAnchor)3 }; _classListStyle.normal.textColor = Color.white; } float num = (float)Screen.width * 0.32f; float num2 = (float)_classListStyle.fontSize * 1.6f; float num3 = num2 * (float)list.Count + 12f; Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width - num - 12f, 12f, num, num3); GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; for (int i = 0; i < list.Count; i++) { GUI.Label(new Rect(((Rect)(ref val)).x + 10f, ((Rect)(ref val)).y + 6f + num2 * (float)i, ((Rect)(ref val)).width - 20f, num2), list[i], (i == 0) ? _classListTitleStyle : _classListStyle); } DrawClassIntro(); } } private void DrawClassIntro() { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (TryBuildClassIntro(LocalClassCode(), out var title, out var lines)) { if (_classIntroStyle == null) { int fontSize = Mathf.Max(16, Mathf.RoundToInt((float)Screen.height / 48f)); _classIntroTitleStyle = new GUIStyle(GUI.skin.label) { fontSize = fontSize, fontStyle = (FontStyle)0, alignment = (TextAnchor)3 }; _classIntroTitleStyle.normal.textColor = new Color(1f, 0.9f, 0.4f); _classIntroStyle = new GUIStyle(GUI.skin.label) { fontSize = fontSize, alignment = (TextAnchor)3, wordWrap = true }; _classIntroStyle.normal.textColor = Color.white; } float num = (float)Screen.width * 0.4f; float num2 = (float)_classIntroStyle.fontSize * 1.6f; GUIContent val = new GUIContent(string.Join("\n", lines)); float num3 = _classIntroStyle.CalcHeight(val, num - 20f); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(12f, 12f, num, num2 + num3 + 16f); GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.DrawTexture(val2, (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 6f, ((Rect)(ref val2)).width - 20f, num2), title, _classIntroTitleStyle); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 6f + num2, ((Rect)(ref val2)).width - 20f, num3), val, _classIntroStyle); _imguiIntroBottom = ((Rect)(ref val2)).yMax; } } private void DrawToasts() { //IL_0021: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) if (_toasts.Count != 0) { if (_toastStyle == null) { _toastStyle = new GUIStyle(GUI.skin.label) { fontSize = Mathf.Max(16, Mathf.RoundToInt((float)Screen.height / 48f)), fontStyle = (FontStyle)0, alignment = (TextAnchor)3, wordWrap = true }; _toastStyle.normal.textColor = Color.white; } float num = (float)Screen.width * 0.45f; float num2 = num - 20f; float num3 = (float)_toastStyle.fontSize * 1.6f; _toastHeights.Clear(); float num4 = 0f; for (int i = 0; i < _toasts.Count; i++) { _toastMeasure.text = _toasts[i]; float num5 = Mathf.Max(num3, _toastStyle.CalcHeight(_toastMeasure, num2) + 2f); _toastHeights.Add(num5); num4 += num5; } float num6 = ((_imguiIntroBottom > 0f) ? (_imguiIntroBottom + 8f) : 12f); Rect val = default(Rect); ((Rect)(ref val))..ctor(12f, num6, num, num4 + 12f); GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; float num7 = ((Rect)(ref val)).y + 6f; for (int j = 0; j < _toasts.Count; j++) { float num8 = _toastHeights[j]; GUI.Label(new Rect(((Rect)(ref val)).x + 10f, num7, num2, num8), _toasts[j], _toastStyle); num7 += num8; } } } } internal static class ClassIntroText { public static string TitlePrefix => "[职业介绍 / Class Intro] "; public static string Keywords(int code) { string text = KeywordsEn(code); string text2 = KeywordsZh(code); if (string.IsNullOrEmpty(text) && string.IsNullOrEmpty(text2)) { return string.Empty; } if (string.IsNullOrEmpty(text2)) { return "Keywords: " + text; } if (string.IsNullOrEmpty(text)) { return "关键词:" + text2; } return "Keywords: " + text + "\n关键词:" + text2; } public static string Intro(int code) { string text = IntroEn(code); string text2 = IntroZh(code); if (string.IsNullOrEmpty(text)) { return text2; } if (string.IsNullOrEmpty(text2)) { return text; } return text + "\n" + text2; } public static string KeywordsLine(int code) { return Keywords(code); } public static string IntroLine(int code) { return Intro(code); } private static string KeywordsZh(int code) { return code switch { 1 => "每关随机道具、一次性昏迷复活、高饥饿消耗、无限周期加速", 2 => "范围增益、开局负债、绑定全员生死、亡语激励、额外神秘物品", 3 => "周期随机补给、开局背包、初始高石化", 4 => "每关医疗物资、范围减伤解毒、队友死亡自伤惩罚", 5 => "初始武器、匕首吹箭、拾取转化", 6 => "范围潘多拉、拾取转化、概率消失、持续自增诅咒", 7 => "骸骨之书、死亡增益、骷髅强化、初始高诅咒、诅咒与增益运营", 8 => "负面状态转化、无负面周期惩罚、周期负面耐受", 9 => "毒孢抗性、自然食物、周期粉蕉、禁用工具", 10 => "开局随机物资腰包、独行流血、集体行动、同伴增益", 11 => "检查点旗标、插旗范围清负、自身插旗石化、全员随机增益、团队爆发", 12 => "跳舞回体、强制共舞、周期后空翻、摔倒风险", 13 => "周期产蛋、传染转职、同类大范围回血、周期后空翻", 14 => "禁用物品、拾取消失、周期重置、受伤放大、职业免疫", 15 => "周期卷轴、双形态、随机效果、负面代价", 16 => "初始高负面、自带黄金抓钩、抓人高惩罚、潘多拉免疫", 17 => "便携烤炉、烤物解饿、烤多中毒、非食物奖励、持炉炎热", 18 => "便携烤炉、生食烹饪、群体增益、熟食增益、生食惩罚、饥饿昏迷", 19 => "踢击替代拉人、踢中回体、踢空困倦、初始诅咒", 20 => "开局背包、物品囤积、负重奖励、周期加速、额外体力", 21 => "昏迷救援、传送无敌、救起加速、背人治疗、死亡惩罚", 22 => "防晒喷雾、每关刷新、强制覆盖、防晒回体、用完额外效果", 23 => "独行奖励、结伴惩罚、半径检测", 24 => "童子军大炮、每关与周期获得、受伤炸药、主手风险", 25 => "喇叭乐器、演奏范围增益、拾取转化、库存奖励、初始诅咒、特殊转职", 26 => "童军宝石、诅咒锁定、拾取消失、特殊增益、零号守则、天底解放", 27 => "早起虫儿、攀爬高频脱手、分解垃圾奖励、感染转职、同类增益、免疫孢子、食虫惩罚", 28 => "周期绑气球、拾取转化、只吃包装熟食、冷热抵抗、额外负面", 29 => "周期荔莓、食用奖励、非莓惩罚、进食损伤、特殊转职", 30 => "周期获得昆虫、额外体力溢出取模、夜间加速、独行清负、职业效果复制", 31 => "传送、曲迁菇、负面状态、周期检测", 32 => "石化、石头、动态增益、拾取转化、随机效果、范围守护", 33 => "工具转化、随机负面、食物炼化、周期强力炼制、背人炼成", 34 => "周期范围发糖、满物品栏捣蛋、拾取转化、吃糖惩罚", _ => string.Empty, }; } private static string IntroZh(int code) { return code switch { 1 => "【开路先锋】每关获得随机道具背包;首次昏迷将完全清除负面复活;*高代谢*迫使你不断管理饥饿与冲刺。", 2 => "【团队核心】周期强力范围增益,使玩家获得额外体力、治疗损伤、补充饥饿、减少困倦;自身诅咒石化随全员生死波动;自身死亡触发全员激励;全员存活时,自己开启雕像可获额外物品。", 3 => "【移动补给】开局自带随机背包;定时获得随机物资;初始诅咒与较高石化需谨慎管理;与【炊事员】有特殊羁绊。", 4 => "【随队医护】每关获得随机医疗物资背包;近身小范围持续减伤解毒;队友阵亡会触发自伤惩罚。", 5 => "【生死问题】开局自带仪式匕首与吹箭筒;拾取物品概率变为武器;杀人还是救人,这是一个问题。", 6 => "【混沌之子】持续向周围散播潘多拉效果;拾取物品半变魔盒半消失;自身诅咒持续叠加。", 7 => "【骷髅之王】致敬本模组最不小团体的职业;通过玩家死亡、与骷髅配合降低自身诅咒;持续为周围的人类玩家施加中毒、骷髅玩家获得额外体力与无敌。", 8 => "【负面转化】受到绝大部分负面状态时,等量转化额外体力;身上无负面状态则周期损失额外体力;对特定负面状态有耐受。", 9 => "【自然力量】自身对中毒与孢子有较强抗性;只能食用生的自然食物,否则获得损伤;定时获得粉莓蕉;自身无法主动使用攀爬道具,拾取会直接变为木棍。", 10 => "【结伴而行】开局自带随机物资腰包;落单独行持续流血与饥饿;需和玩家结伴同行缓解,与同职业【童军】或【领袖】有加强羁绊。", 11 => "【团队旗手】每关获得检查点旗标;自身插旗立即范围清除负面,三秒后全员获得随机增益效果;自身每次插旗增加石化。", 12 => "【一起跳舞】自身范围内玩家跳舞获得额外体力;自身跳舞强制周围玩家共舞;定时强制后空翻且必定摔倒。", 13 => "【模因传播】语音与产蛋周期获得额外体力;传染式转职,可主动通过喂食煎蛋强制同化他人;较大范围内,同类周期回血减伤,但定时强制后空翻且必定摔倒。", 14 => "【请输入文本】禁用食物及绝大部分道具,拾取物品直接消失;每关饥饿值清零重置;周期回满额外体力;受伤放大即倒;免疫【潘多拉之子】【死灵法师】【舞王】【区】职业效果。", 15 => "【随机施法】定时获得卷轴,打开随机触发效果并增加自身石化/诅咒;人类形态效果偏辅助,骷髅形态效果高收益高代价。", 16 => "【立体机动】初始高诅咒高石化需谨慎管理;开局自带黄金抓钩与滑翔翼;使用抓钩勾中玩家会立即触发高损伤惩罚;免疫【潘多拉之子】且拾取的潘多拉餐盒直接消失。", 17 => "【烤炉大师】定时获得便携烤炉;烤任意物品都能解饿;烤多会中毒;周期烤非食物(道具)可获额外体力;手持便携烤炉时有高频炎热。", 18 => "【饮食保障】定时获得便携烤炉;烤熟生食可为周围回饥饿与体力;食用熟食有额外收益,食用生食有较大惩罚;附近有人饥饿会致困昏迷;与【后勤】有特殊羁绊。", 19 => "【并非和平】右键拉人变为踢人;每踢中一名玩家+5额外体力,踢空则-5额外体力并+20困倦;开局自带诅咒。", 20 => "【负重前行】开局自带背包;负重过半达标后,周期获得加速与额外体力。", 21 => "【火线救援】队友昏迷将自动传送并获得一次无敌,背起再获得一次加速与大量额外体力;背起玩家时治疗双方;有玩家死亡会获得石化惩罚。", 22 => "【精致防晒】每关获得三瓶防晒喷雾,强制覆盖物品栏;获得防晒奖励额外体力,每次用完防晒喷雾会额外奖励额外体力并积累石化。", 23 => "【单人模式】离人够远时周期奖励额外体力;靠近他人持续困倦。", 24 => "【重火力者】每关获得童子军大炮;定时获得童子军大炮;受伤就会获得炸药,且物品栏满时会直接塞入主手。", 25 => "【演奏乐队】拾取物品概率变乐器,库存与演奏可范围奖励额外体力;库存集齐三种乐器有额外加速奖励,且给予他人有低概率将其转职。", 26 => "【领队残魂】初始诅咒锁定,仅能通过获取童军宝石道具降低;使用童军宝石道具有专属特殊增益;禁用童军宝石道具以外物品,拾取物品直接消失;即时传送救援,不可放弃他人;集齐所有童军宝石道具有额外奖励;抵达天底解放灵魂全员奖励。", 27 => "【蠕动之区】攀爬时高频强制脱手;靠拾取分解垃圾物品、获取积攒早起虫儿积累收益;感染式转职,周期对附近施加孢子,过半会使他人强制同化,并获得早起虫儿;同类聚集有加速与无限体力效果奖励;自身免疫孢子;食用早期虫儿有诅咒惩罚。", 28 => "【太空漂流】身上定时绑上气球;无法拾取气球/一束气球;拾取物品概率变为火箭背包/喷气背包;只吃包装食品或熟食,否则中毒;对冷热有一定抗性,但部分负面状态会额外加重。", 29 => "【大果成瘾】定时获得青/紫荔莓;食用青荔莓获得冲刺效果、紫荔莓则给无限体力;食用其他食物加困倦;每次进食固定损伤;他人多次连吃荔莓会被强制同化。", 30 => "【代码之力】定时获得随机虫类,获得时大概率中毒;额外体力溢出会取模重置;夜晚周期获得加速;独行会周期清负面;无负面且无虫时会复制在场他人职业效果,并持续生效一段时间。", 31 => "【混乱传送】周期检测自身状态,若检测的负面状态相比上一次增加,则与随机一名其他玩家互换位置,否则获得曲迁菇;拾取物品有较高概率直接消失并触发潘多拉;饮用运动饮料或万灵药、接触水体都会带来额外惩罚;积攒过多曲迁菇会受到惩罚。", 32 => "【活体石像】站立不动会持续积蓄石化,移动则缓慢消退;自身石化越高,增益效果越强;若石化偏低,白天会畏光叠加困倦;周期内石化达到峰值时会强制破壳,触发一次性强力增益,且能影响周围;拾取食物会变石头,而石头可能带来随机增益。", 33 => "【等价炼成】拾取工具时获得随机负面并转化为随机物品;拾取可食用物品会转化为负面清除与额外体力,每12次会有特殊奖励;每炼制出12石头将触发一次特殊强力炼制;背起玩家会将其变为骷髅,或将其炼化为尸。", 34 => "【万圣敲门】定时给范围内的玩家发糖,成功使自身获得额外体力;若对方物品栏已满则捣蛋:随机抢走一件物品或连施潘多拉;拾取食物会变糖,但吃糖有蛀牙的损伤惩罚。", _ => string.Empty, }; } private static string KeywordsEn(int code) { return code switch { 1 => "Random items each level · One-time faint revive · High hunger drain · Periodic infinite sprint", 2 => "Area buffs · Opening debt · Bound to everyone's life · Death-triggered rally · Extra mystery item", 3 => "Periodic random supply · Starting backpack · High starting petrify", 4 => "Medical supplies each level · Area injury/poison relief · Self-injury on teammate death", 5 => "Starting weapons · Dagger and blowgun · Pickup conversion", 6 => "Area Pandora · Pickup conversion · Chance to vanish · Steadily rising curse", 7 => "Book of Bones · Death buffs · Skeleton empowerment · High starting curse · Curse/gain management", 8 => "Debuff conversion · Penalty without debuffs · Periodic debuff tolerance", 9 => "Poison/spore resistance · Natural food only · Periodic pink banana · Tools disabled", 10 => "Starting random fanny pack · Solo bleeding · Group action · Companion buffs", 11 => "Checkpoint flag · Area debuff purge on plant · Self petrify on plant · Random team buffs · Team burst", 12 => "Dance for stamina · Forced group dance · Periodic backflip · Fall risk", 13 => "Periodic eggs · Contagious reclass · Same-class area healing · Periodic backflip", 14 => "Items disabled · Pickups vanish · Periodic reset · Amplified injury · Class immunity", 15 => "Periodic scrolls · Two forms · Random effects · Negative cost", 16 => "High starting debuffs · Golden grapnel · Heavy grab penalty · Pandora immunity", 17 => "Portable stove · Cooking sates hunger · Cooking builds poison · Non-food rewards · Heat while holding", 18 => "Portable stove · Cooking raw food · Group buffs · Cooked-food bonus · Raw-food penalty · Hunger-induced drowsiness", 19 => "Kick replaces grab · Hits restore stamina · Misses cause drowsiness · Starting curse", 20 => "Starting backpack · Item hoarding · Load reward · Periodic sprint · Extra stamina", 21 => "Faint rescue · Teleport invincibility · Post-rescue sprint · Carrying heals both · Death penalty", 22 => "Sunscreen · Refreshed each level · Forced overwrite · Sunscreen stamina · Per-bottle bonus", 23 => "Solo reward · Company penalty · Radius check", 24 => "Scout cannon · Each level and periodic · Injury dynamite · Main-hand risk", 25 => "Horns and instruments · Performance area buff · Pickup conversion · Inventory bonus · Starting curse · Special reclass", 26 => "Scout gems · Locked curse · Pickups vanish · Special bonuses · Rule Zero · Nadir liberation", 27 => "Early bug · Frequent climbing slips · Decomposing junk rewards · Contagious reclass · Same-class buffs · Spore immunity · Bug-eating penalty", 28 => "Periodic balloons · Pickup conversion · Packaged/cooked food only · Heat/cold resistance · Extra debuffs", 29 => "Periodic kingberries · Eating rewards · Non-berry penalty · Injury per meal · Special reclass", 30 => "Periodic insects · Extra stamina overflow modulo · Night sprint · Solo debuff cleanse · Class-effect copy", 31 => "Teleport · Warp Fungus · Negative statuses · Periodic checks", 32 => "Petrify · Stones · Dynamic buffs · Pickup conversion · Random effects · Area guard", 33 => "Tool transmutation · Random debuff · Food conversion · Forced periodic refining · Carrying taboo", 34 => "Periodic area candy · Trick on full inventories · Pickup conversion · Sugar penalty", _ => string.Empty, }; } private static string IntroEn(int code) { return code switch { 1 => "【Pioneer】 a random-item backpack every level; your first faint fully clears debuffs as you revive; *Hyper-metabolism* forces you to keep managing hunger and sprints.", 2 => "【Team core】 periodic strong area buffs that grant players extra stamina, heal injury, restore hunger and reduce drowsiness; your curse and petrify swing with everyone's life and death; your death rallies everyone; while all are alive, opening an Ancient Idol yourself yields an extra item.", 3 => "【Mobile supply】 starts with a random backpack; receives random supplies on a timer; your starting curse and high petrify need careful management; special bond with the Cook.", 4 => "【Field medic】 a random medical backpack every level; sustained close-range injury and poison relief; a teammate's death triggers a self-injury penalty.", 5 => "【A matter of life and death】 starts with a ritual dagger and a blowgun; picked-up items may become weapons; to kill or to save, that is the question.", 6 => "【Child of chaos】 keeps spreading the Pandora effect around you; half of the items you pick up become boxes and half vanish; your curse keeps stacking.", 7 => "【King of skeletons】 a tribute to the mod's least small group; lower your curse through player deaths and skeleton allies; keep poisoning nearby humans while skeletons gain extra stamina and invincibility.", 8 => "【Negative conversion】 most debuffs you take convert into an equal amount of extra stamina; with no debuffs you periodically lose extra stamina; tolerant against specific debuffs.", 9 => "【Power of nature】 strong resistance to poison and spores; you can only eat raw natural food or take injury; gains a pink banana on a timer; climbing tools are unusable — picked-up ones turn into wooden sticks.", 10 => "【Better together】 starts with a random-item fanny pack; alone you bleed and starve; you need company to ease it; strengthened bond with fellow Scouts or Leaders.", 11 => "【Team standard-bearer】 a checkpoint flag every level; planting it immediately clears debuffs in range and 3 s later grants everyone random buffs; each plant adds petrify to yourself.", 12 => "【Dance together】 players dancing in your range grant extra stamina; your own dance forces nearby players to dance; periodic forced backflips that always end in a fall.", 13 => "【Meme spread】 voice and egg cycles grant extra stamina; contagious reclassing — you can force-merge others by feeding fried eggs; same-class players heal and take less injury over a larger range, but periodic backflips always end in a fall.", 14 => "【Input text here】 food and most items are disabled and picked-up items vanish; hunger resets to zero every level; extra stamina refills periodically; any injury is amplified into a knockdown; immune to the Pandora, Necromancer, Dancer and Qu class effects.", 15 => "【Random casting】 scrolls on a timer; opening one triggers a random effect and adds petrify/curse; the human form leans support, the skeleton form trades a high cost for high gain.", 16 => "【Aerial mobility】 high starting curse and petrify need careful management; starts with a golden grapnel and a glider; hooking a player with the grapnel immediately triggers a heavy injury penalty; immune to Pandora effects and picked-up Pandora lunchboxes vanish.", 17 => "【Grill master】 portable stoves on a timer; cooking anything sates hunger; cooking too much poisons you; periodically cooking non-food (tools) grants extra stamina; holding a portable stove causes frequent heat.", 18 => "【Food security】 portable stoves on a timer; cooking raw food restores hunger and stamina around you; cooked food pays extra, raw food punishes hard; nearby hungry players make you drowsy; special bond with the Logistician.", 19 => "【Not so peaceful】 the right-click grab becomes a kick; each hit grants +5 extra stamina, while a miss costs 5 extra stamina and adds 20 drowsiness; starts with curse.", 20 => "【Carrying the weight】 starts with a backpack; once your load passes the threshold you periodically gain a sprint and extra stamina.", 21 => "【Fireline rescue】 a teammate's faint auto-teleports you with invincibility; picking them up grants a sprint and a large amount of extra stamina; carrying a player heals both; a player's death gives you petrify.", 22 => "【Exquisite sunscreen】 three sunscreens every level, force-overwriting your inventory; gaining sunscreen grants extra stamina, and finishing each bottle grants more extra stamina plus petrify.", 23 => "【Single-player mode】 periodically rewarded with extra stamina when far from others; constant drowsiness when someone is close.", 24 => "【Heavy gunner】 a scout cannon every level; scout cannons on a timer; taking damage grants dynamite, which is forced into your main hand when the inventory is full.", 25 => "【The playing band】 picked-up items may become instruments; inventory and performance grant area extra stamina; holding all three instruments grants an extra sprint, and giving them to others has a low chance to reclass them.", 26 => "【Scoutmaster's soul】 starting curse is locked and only Scout gem items can lower it; Scout gems grant exclusive bonuses; all non-gem items are disabled and picked-up items vanish; instant teleport rescue, never abandon others; collecting every Scout gem grants an extra reward; freeing the soul at the Nadir rewards everyone.", 27 => "【Wriggling Qu】 frequent forced let-go while climbing; profit from decomposing junk items and accumulating early bugs; contagious reclassing — periodically applies spores nearby, and past the threshold forces others to merge and grants early bugs; same-class clusters get sprint and infinite stamina rewards; immune to spores; eating early bugs costs curse.", 28 => "【Space drift】 balloons are tied on periodically; balloons and bunches of balloons cannot be picked up; picked-up items may become rocket packs or jetpacks; only packaged or cooked food, otherwise poison; some heat/cold resistance but certain debuffs hit harder.", 29 => "【Big-berry addiction】 green/purple kingberries on a timer; green grants a sprint, purple grants infinite stamina; other food adds drowsiness; every meal costs injury; others who chain-eat kingberries are forced to merge.", 30 => "【Power of code】 random insects on a timer, usually with poison; extra stamina overflow wraps around; periodic sprint at night; solo play periodically cleanses debuffs; with no debuffs and no insects you copy another present class's effect for a while.", 31 => "【Chaos Teleport】periodically checks your own status; if the selected negative status increased, swap positions with a random other player; otherwise gain a Warp Fungus; picked-up items may vanish and trigger Pandora; sports drinks, Cure-All and touching water add extra punishment; hoarding too many Warp Fungi is punished.", 32 => "【Living Statue】Standing still builds petrify while moving slowly sheds it; the higher your petrify, the stronger your buffs; when petrify is low, daylight adds drowsiness; when petrify peaks within a cycle you are forced to break the shell, triggering a one-time powerful buff that also affects those around you; food you pick up turns to stone, and stones may bring random buffs.", 33 => "【Equivalent alchemy】picked-up tools gain a random debuff and are turned into random items; picked-up edible items are converted into debuff relief and extra stamina, with a special reward every 12 triggers; every 12 stones refined triggers one special powerful transmutation; carrying a player turns them into a skeleton, or refines them into a corpse.", 34 => "【Halloween knocker】candy to players in range on a timer, granting you extra stamina when it lands; a full inventory gets tricked — one item is snatched or Pandora strikes repeatedly; edible pickups turn into candy, but eating candy costs injury from cavities.", _ => string.Empty, }; } } public static class ClassNames { public static string Describe(int code) { return code switch { 1 => "Explorer · 先驱", 2 => "Leader · 领袖", 3 => "Logistician · 后勤", 4 => "Doctor · 医生", 5 => "Assassin · 刺客", 6 => "Pandora's Child · 潘多拉之子", 7 => "Necromancer · 死灵法师", 8 => "M · M", 9 => "Savage · 野人", 10 => "Scout · 童军", 11 => "StandardBearer · 旗手", 12 => "Dancer · 舞王", 13 => "Daihao · 待皓", 14 => "Saint · 圣人", 15 => "Mage · 法师", 16 => "Challenger · 挑战者", 17 => "MasterChef · 特级厨师", 18 => "Cook · 炊事员", 19 => "Pacifist · 和平主义者", 20 => "Hamster · 仓鼠", 21 => "Firefighter · 救火队员", 22 => "Narcissist · 自恋狂", 23 => "LoneWolf · 独狼", 24 => "Gunner · 炮手", 25 => "Band · 乐队成员", 26 => "MilesSoul · 迈尔斯残魂", 27 => "Qu · 区", 28 => "Astronaut · 太空人", 29 => "Langwang · 榔王", 30 => "Programmer · 程序员", 31 => "ChaosEnderman · 混乱末影人", 32 => "Gargoyle · 石像鬼", 33 => "Alchemist · 炼金术士", 34 => "Trickster · 捣蛋鬼", _ => "None · 无职业", }; } } public static class ClientConfig { private static string _cachedKeyRaw; private static KeyCode _cachedKey = (KeyCode)9; private static string _cachedModeRaw; private static int _cachedModeKind; public static ConfigEntry<string> ClassListKey { get; private set; } public static ConfigEntry<string> ClassListMode { get; private set; } public static ConfigEntry<bool> NamePrefix { get; private set; } public static void Initialize(ConfigFile config) { ConfigLocalization.DetectLanguage(); ClassListKey = BindL(config, "通用", "职业显示-按键", "Tab", "按哪个键在【客机自己的屏幕】上显示「当前职业分配情况」(由主机广播而来)。\n填写 Unity 的 KeyCode 名,例如 Tab、F1、LeftAlt、BackQuote、Mouse2。"); ClassListMode = BindL(config, "通用", "职业显示-模式", "1", "职业分配面板的显示方式(1 / 2 / 3):\n1 = 按住该键时显示,松开立即隐藏;\n2 = 按一下显示 5 秒后自动隐藏;\n3 = 每按一次在「显示 / 隐藏」之间切换。\n默认 1。旧版文字值仍兼容。"); NamePrefix = BindL(config, "通用", "头顶职业前缀", defaultValue: false, "是否把「头顶玩家名称」改写成「【职业】昵称」,例如「张三」→「【先驱】张三」。\n「【职业】」用该玩家进入游戏时的皮肤色着色;「昵称」维持原版颜色不变。\n职业数据由主机广播而来;本开关只影响【本机屏幕】上看到的名字。\n⚠ **默认关闭**:该功能每帧、每个玩家都会执行;若同时装有其它的头顶名称模组\n(例如 AlwaysDisplayPlayerName),双方会每帧互相改写文本,导致明显掉帧。\n默认 false(关闭)。"); config.Save(); } private static ConfigEntry<T> BindL<T>(ConfigFile config, string zhSection, string zhKey, T defaultValue, string zhDescription) { return config.Bind<T>(ConfigLocalization.Section(zhSection), ConfigLocalization.Key(zhSection, zhKey), defaultValue, ConfigLocalization.Desc(zhSection, zhKey, zhDescription)); } public static KeyCode ParseKey() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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) string text = ((ClassListKey != null) ? ClassListKey.Value : null); if (string.Equals(text, _cachedKeyRaw)) { return _cachedKey; } _cachedKeyRaw = text; _cachedKey = (KeyCode)((string.IsNullOrEmpty(text) || !Enum.TryParse<KeyCode>(text.Trim(), ignoreCase: true, out KeyCode result)) ? 9 : ((int)result)); return _cachedKey; } public static int ClassListModeKind() { string text = ((ClassListMode != null) ? (ClassListMode.Value ?? string.Empty) : string.Empty); if (string.Equals(text, _cachedModeRaw)) { return _cachedModeKind; } _cachedModeRaw = text; string text2 = text.ToLowerInvariant(); _cachedModeKind = ((text.Trim() == "1" || text.Contains("按住") || text2.Contains("hold")) ? 1 : ((text.Trim() == "3" || text.Contains("切换") || text2.Contains("toggle")) ? 3 : 2)); return _cachedModeKind; } } internal static class ConfigLocalization { private static readonly Dictionary<string, string> Sections = new Dictionary<string, string> { { "通用", "General" } }; private static readonly Dictionary<string, (string Key, string Desc)> Entries = new Dictionary<string, (string, string)> { { "通用|职业显示-按键", ("classDisplayKey", "Key that shows the current class assignment (broadcast by the host) on the client's own screen. Enter a Unity KeyCode name, e.g. Tab, F1, LeftAlt, BackQuote, Mouse2.") }, { "通用|职业显示-模式", ("classDisplayMode", "Display mode of the class panel (values 1 / 2 / 3): 1 = show while the key is held and hide immediately on release; 2 = show for 5 seconds after a single press, then auto-hide; 3 = each press toggles between show and hide. Default 1. Legacy text values are still accepted.") }, { "通用|头顶职业前缀", ("overheadClassPrefix", "Whether to rewrite the overhead player name as '[Class] Nickname', e.g. Alice -> [Explorer] Alice. '[Class]' is colored with that player's skin color; the nickname keeps its vanilla color. Class data comes from the host's broadcast; this switch only affects names seen on the local screen. Warning: this runs per frame and per player; if another overhead-name mod is installed (e.g. AlwaysDisplayPlayerName), both rewrite the text every frame and cause noticeable frame drops. Default false (off).") } }; internal static bool UseEnglish { get; private set; } internal static void DetectLanguage() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) bool flag; try { Language cURRENT_LANGUAGE = LocalizedText.CURRENT_LANGUAGE; flag = (int)cURRENT_LANGUAGE == 9 || (int)cURRENT_LANGUAGE == 10 || ((int)cURRENT_LANGUAGE == 0 && IsSystemChinese()); } catch { flag = IsSystemChinese(); } UseEnglish = !flag; } private static bool IsSystemChinese() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 SystemLanguage systemLanguage = Application.systemLanguage; if ((int)systemLanguage != 40) { return (int)systemLanguage == 41; } return true; } internal static string Section(string zhSection) { if (Sections.TryGetValue(zhSection, out var value) && !string.IsNullOrEmpty(value) && !string.Equals(value, zhSection, StringComparison.Ordinal)) { return value + zhSection; } return zhSection; } internal static string Key(string zhSection, string zhKey) { if (!Entries.TryGetValue(Index(zhSection, zhKey), out (string, string) value) || string.IsNullOrEmpty(value.Item1)) { return zhKey; } if (string.Equals(value.Item1, zhKey, StringComparison.OrdinalIgnoreCase)) { return zhKey; } return BuildBilingualKey(zhKey, value.Item1) + zhKey; } private static string BuildBilingualKey(string zhKey, string enKey) { int num = zhKey.Split('-').Length; List<string> list = SplitCamelWord