using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using Photon.Voice.Unity; using TMPro; using UnityEngine; using UnityEngine.Audio; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; using Werewolf.Core; using Werewolf.Debugging; using Werewolf.Game; using Werewolf.Game.Patches; using Werewolf.Net; using Werewolf.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("Minorunara_Werewolf")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d25146b9bff09860509a3818fa26fb257548c4a3")] [assembly: AssemblyProduct("Minorunara_Werewolf")] [assembly: AssemblyTitle("Minorunara_Werewolf")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } internal static class IsExternalInit { } } namespace Werewolf { internal sealed class ConfigBindings { private const string SecGeneral = "01. General"; private const string SecMeeting = "02. Meeting"; private const string SecRoleAssignment = "03. Role Assignment"; private const string SecShaman = "04. Shaman"; private const string SecWerewolf = "05. Werewolf"; private const string SecBlackCat = "06. Black Cat"; private const string SecBomber = "07. Bomber"; internal const string SecStage = "08. Stage"; private const string SecPlayerUpgrades = "09. Player Upgrades"; internal const string SecLoadoutItems = "10. Loadout Items"; private const string SecClientKeybinds = "Client Keybinds"; private const string SecClientUi = "Client UI"; private const string SecTutorial = "Tutorial"; private const string SecVoice = "Voice"; private const string SecDebug = "Debug"; internal readonly ConfigEntry WerewolfCount; internal readonly ConfigEntry BlackCatChancePercent; internal readonly ConfigEntry BomberChancePercent; internal readonly ConfigEntry RoundSeconds; internal readonly ConfigEntry BlackCatRevealDelaySec; internal readonly ConfigEntry BlackCatCurseEnabled; internal readonly ConfigEntry DebugMode; internal readonly ConfigEntry ShamanChancePercent; internal readonly ConfigEntry ShamanGazeFullSec; internal readonly ConfigEntry ShamanGhostCooldownSec; internal readonly ConfigEntry ShamanStormWeakMeters; internal readonly ConfigEntry ShamanStormMediumMeters; internal readonly ConfigEntry ShamanStormStrongMeters; internal readonly ConfigEntry BomberProximityMeters; internal readonly ConfigEntry BomberGaugeFullSec; internal readonly ConfigEntry BomberInitialCooldownSec; internal readonly ConfigEntry BomberCooldownSec; internal readonly ConfigEntry BomberBlastRadiusMeters; internal readonly ConfigEntry BomberBlastPlayerDamage; internal readonly ConfigEntry BomberBlastEnemyDamage; internal readonly ConfigEntry BomberAmmoRefillPct; internal readonly ConfigEntry BomberPlantKey; internal readonly ConfigEntry BomberDetonateKey; internal readonly ConfigEntry Language; internal readonly ConfigEntry MeetingRightsPerPlayer; internal readonly ConfigEntry ConveneSuppressStartSec; internal readonly ConfigEntry ConveneSuppressAfterSec; internal readonly ConfigEntry MeetingCountdownSec; internal readonly ConfigEntry MeetingDurationSec; internal readonly ConfigEntry VoteTimeCutEnabled; internal readonly ConfigEntry ResultDisplaySec; internal readonly ConfigEntry ButtonOffsetX; internal readonly ConfigEntry ButtonOffsetY; internal readonly ConfigEntry ButtonOffsetZ; internal readonly ConfigEntry ButtonYaw; internal readonly ConfigEntry ButtonPitch; internal readonly ConfigEntry StaminaUnlockPct; internal readonly ConfigEntry JumpUnlockPct; internal readonly ConfigEntry EnemyIgnoreUnlockPct; internal readonly ConfigEntry HealUnlockPct; internal readonly ConfigEntry HealIntervalSec; internal readonly ConfigEntry BeaconChargePct; internal readonly ConfigEntry InformantThresholdPct; internal readonly ConfigEntry ExtraJumpCount; internal readonly ConfigEntry BeaconCooldownSec; internal readonly ConfigEntry BeaconSuppressStartSec; internal readonly ConfigEntry BeaconSuppressAfterMeetingSec; internal readonly ConfigEntry CatGaugeSyncIntervalSec; internal readonly ConfigEntry OrbGaugeEnabled; internal readonly ConfigEntry WolfModeKey; internal readonly ConfigEntry BeaconKey; internal readonly ConfigEntry WerewolfModeEnabled; internal readonly ConfigEntry MinimapHideEnabled; internal readonly ConfigEntry ValuableMapMode; internal readonly ConfigEntry GameOverAutoReturnSec; internal readonly ConfigEntry ToastDurationSec; internal readonly ConfigEntry LobbySettingsPanelKey; internal readonly ConfigEntry ManualKey; internal readonly ConfigEntry CorpseReportKey; internal readonly ConfigEntry ResultReturnKey; internal readonly ConfigEntry MeetingMapKey; internal readonly ConfigEntry MeetingChatLogKey; internal readonly ConfigEntry MeetingMapOrthoSize; internal readonly ConfigEntry MeetingMapResolution; internal readonly ConfigEntry MeetingMapGrid; internal readonly ConfigEntry MeetingChatLog; internal readonly ConfigEntry NecroVoiceMode; internal readonly ConfigEntry NecroVoiceVolume; internal readonly ConfigEntry NecroVoiceLowPassCutoffHz; internal readonly ConfigEntry NecroVoiceEchoDelayMs; internal readonly ConfigEntry NecroVoiceEchoDecay; internal readonly ConfigEntry NecroVoiceReverbRoom; internal readonly ConfigEntry NecroVoiceReverbRoomHF; internal readonly ConfigEntry NecroVoiceReverbDecayTime; internal readonly ConfigEntry NecroVoiceReverbDecayHFRatio; internal readonly ConfigEntry NecroVoiceReverbReflections; internal readonly ConfigEntry NecroVoiceReverbReflectionsDelay; internal readonly ConfigEntry NecroVoiceReverbLevel; internal readonly ConfigEntry NecroVoiceReverbDelay; internal readonly ConfigEntry NecroVoiceReverbDiffusion; internal readonly ConfigEntry NecroVoiceReverbDensity; internal readonly ConfigEntry NecroVoiceReverbHFReference; private readonly Dictionary> _tutorialSeen; internal readonly ConfigEntry ResetTutorials; internal readonly ConfigEntry TutorialFontScale; internal readonly ConfigEntry CursorMirrorScale; internal readonly ConfigEntry HudOffsetX; internal readonly ConfigEntry HudOffsetY; internal readonly ConfigEntry StartLevelNumber; internal ConfigEntry StartMapName; internal readonly ConfigEntry StartEnergyPct; internal readonly ConfigEntry OrbDropMax; private readonly Dictionary> _startUpgrades; private readonly ValuableMapMode _valuableMapModeDefault; private readonly NecroVoiceMode _necroVoiceModeDefault; internal Func ItemsSpecProvider { get; set; } internal ConfigBindings(ConfigFile config) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Expected O, but got Unknown //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected O, but got Unknown //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Expected O, but got Unknown //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Expected O, but got Unknown //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Expected O, but got Unknown //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Expected O, but got Unknown //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Expected O, but got Unknown //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0358: Expected O, but got Unknown //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Expected O, but got Unknown //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Expected O, but got Unknown //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_03f1: Expected O, but got Unknown //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0424: Expected O, but got Unknown //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Expected O, but got Unknown //IL_0480: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Expected O, but got Unknown //IL_04b3: Unknown result type (might be due to invalid IL or missing references) //IL_04bd: Expected O, but got Unknown //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_04f0: Expected O, but got Unknown //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_0523: Expected O, but got Unknown //IL_054c: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Expected O, but got Unknown //IL_057f: Unknown result type (might be due to invalid IL or missing references) //IL_0589: Expected O, but got Unknown //IL_05b2: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Expected O, but got Unknown //IL_05e5: Unknown result type (might be due to invalid IL or missing references) //IL_05ef: Expected O, but got Unknown //IL_0618: Unknown result type (might be due to invalid IL or missing references) //IL_0622: Expected O, but got Unknown //IL_064e: Unknown result type (might be due to invalid IL or missing references) //IL_0658: Expected O, but got Unknown //IL_0684: Unknown result type (might be due to invalid IL or missing references) //IL_068e: Expected O, but got Unknown //IL_06ba: Unknown result type (might be due to invalid IL or missing references) //IL_06c4: Expected O, but got Unknown //IL_06f0: Unknown result type (might be due to invalid IL or missing references) //IL_06fa: Expected O, but got Unknown //IL_0744: Unknown result type (might be due to invalid IL or missing references) //IL_074e: Expected O, but got Unknown //IL_077a: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Expected O, but got Unknown //IL_07ad: Unknown result type (might be due to invalid IL or missing references) //IL_07b7: Expected O, but got Unknown //IL_07e0: Unknown result type (might be due to invalid IL or missing references) //IL_07ea: Expected O, but got Unknown //IL_0816: Unknown result type (might be due to invalid IL or missing references) //IL_0820: Expected O, but got Unknown //IL_0849: Unknown result type (might be due to invalid IL or missing references) //IL_0853: Expected O, but got Unknown //IL_087c: Unknown result type (might be due to invalid IL or missing references) //IL_0886: Expected O, but got Unknown //IL_08b4: Unknown result type (might be due to invalid IL or missing references) //IL_08be: Expected O, but got Unknown //IL_08ea: Unknown result type (might be due to invalid IL or missing references) //IL_08f4: Expected O, but got Unknown //IL_0920: Unknown result type (might be due to invalid IL or missing references) //IL_092a: Expected O, but got Unknown //IL_0953: Unknown result type (might be due to invalid IL or missing references) //IL_095d: Expected O, but got Unknown //IL_09a6: Unknown result type (might be due to invalid IL or missing references) //IL_09b0: Expected O, but got Unknown //IL_09d9: Unknown result type (might be due to invalid IL or missing references) //IL_09e3: Expected O, but got Unknown //IL_0adb: Unknown result type (might be due to invalid IL or missing references) //IL_0ae5: Expected O, but got Unknown //IL_0b0c: Unknown result type (might be due to invalid IL or missing references) //IL_0b16: Expected O, but got Unknown //IL_0b3d: Unknown result type (might be due to invalid IL or missing references) //IL_0b47: Expected O, but got Unknown //IL_0b6e: Unknown result type (might be due to invalid IL or missing references) //IL_0b78: Expected O, but got Unknown //IL_0b9f: Unknown result type (might be due to invalid IL or missing references) //IL_0ba9: Expected O, but got Unknown //IL_0bd0: Unknown result type (might be due to invalid IL or missing references) //IL_0bda: Expected O, but got Unknown //IL_0c01: Unknown result type (might be due to invalid IL or missing references) //IL_0c0b: Expected O, but got Unknown //IL_0c35: Unknown result type (might be due to invalid IL or missing references) //IL_0c3f: Expected O, but got Unknown //IL_0c69: Unknown result type (might be due to invalid IL or missing references) //IL_0c73: Expected O, but got Unknown //IL_0c9d: Unknown result type (might be due to invalid IL or missing references) //IL_0ca7: Expected O, but got Unknown //IL_0ce0: Unknown result type (might be due to invalid IL or missing references) //IL_0cea: Expected O, but got Unknown //IL_0d23: Unknown result type (might be due to invalid IL or missing references) //IL_0d2d: Expected O, but got Unknown //IL_0d66: Unknown result type (might be due to invalid IL or missing references) //IL_0d70: Expected O, but got Unknown //IL_0da9: Unknown result type (might be due to invalid IL or missing references) //IL_0db3: Expected O, but got Unknown //IL_0dec: Unknown result type (might be due to invalid IL or missing references) //IL_0df6: Expected O, but got Unknown //IL_0e28: Unknown result type (might be due to invalid IL or missing references) //IL_0e32: Expected O, but got Unknown //IL_0e6a: Unknown result type (might be due to invalid IL or missing references) //IL_0e74: Expected O, but got Unknown //IL_0ea0: Unknown result type (might be due to invalid IL or missing references) //IL_0eaa: Expected O, but got Unknown //IL_0ec7: Unknown result type (might be due to invalid IL or missing references) //IL_0ed1: Expected O, but got Unknown //IL_0eee: Unknown result type (might be due to invalid IL or missing references) //IL_0ef8: Expected O, but got Unknown //IL_0f27: Unknown result type (might be due to invalid IL or missing references) //IL_0f31: Expected O, but got Unknown //IL_0f5c: Unknown result type (might be due to invalid IL or missing references) //IL_0f66: Expected O, but got Unknown //IL_0f91: Unknown result type (might be due to invalid IL or missing references) //IL_0f9b: Expected O, but got Unknown //IL_0fe6: Unknown result type (might be due to invalid IL or missing references) //IL_0ff0: Expected O, but got Unknown //IL_0a93: Unknown result type (might be due to invalid IL or missing references) //IL_0a9d: Expected O, but got Unknown //IL_1073: Unknown result type (might be due to invalid IL or missing references) //IL_107d: Expected O, but got Unknown //IL_10d6: Unknown result type (might be due to invalid IL or missing references) //IL_10e0: Expected O, but got Unknown //IL_1119: Unknown result type (might be due to invalid IL or missing references) //IL_1123: Expected O, but got Unknown //IL_115c: Unknown result type (might be due to invalid IL or missing references) //IL_1166: Expected O, but got Unknown //IL_119f: Unknown result type (might be due to invalid IL or missing references) //IL_11a9: Expected O, but got Unknown //IL_11e2: Unknown result type (might be due to invalid IL or missing references) //IL_11ec: Expected O, but got Unknown //IL_1225: Unknown result type (might be due to invalid IL or missing references) //IL_122f: Expected O, but got Unknown //IL_1268: Unknown result type (might be due to invalid IL or missing references) //IL_1272: Expected O, but got Unknown //IL_12ab: Unknown result type (might be due to invalid IL or missing references) //IL_12b5: Expected O, but got Unknown //IL_12ee: Unknown result type (might be due to invalid IL or missing references) //IL_12f8: Expected O, but got Unknown //IL_1331: Unknown result type (might be due to invalid IL or missing references) //IL_133b: Expected O, but got Unknown //IL_1374: Unknown result type (might be due to invalid IL or missing references) //IL_137e: Expected O, but got Unknown //IL_13b7: Unknown result type (might be due to invalid IL or missing references) //IL_13c1: Expected O, but got Unknown //IL_13fa: Unknown result type (might be due to invalid IL or missing references) //IL_1404: Expected O, but got Unknown //IL_143d: Unknown result type (might be due to invalid IL or missing references) //IL_1447: Expected O, but got Unknown //IL_1480: Unknown result type (might be due to invalid IL or missing references) //IL_148a: Expected O, but got Unknown //IL_14b5: Unknown result type (might be due to invalid IL or missing references) //IL_14bf: Expected O, but got Unknown GameConfig gameConfig = new GameConfig(); _valuableMapModeDefault = gameConfig.ValuableMapMode; _necroVoiceModeDefault = gameConfig.NecroVoiceMode; WerewolfModeEnabled = config.Bind("01. General", "WerewolfModeEnabled", true, "Werewolf mode ON/OFF. While true, rounds are played as werewolf sessions. Host authoritative: only the host's value applies to the room / 人狼モードのON/OFF。true の間はラウンドが人狼セッションとして進行する。ホスト権威(部屋に効くのはホストの値のみ。参加者側の値は自分がホストのときにだけ効く)"); Language = config.Bind("01. General", "Language (restart required)", "English", new ConfigDescription("Display language (restart required). 日本語 = built-in Japanese, English = built-in English, custom = load Lang/custom.txt next to the DLL. A matching Lang file (Lang/en.txt for English, Lang/custom.txt for custom), if present, overrides the built-in text per key (missing keys fall back to the built-in table) / 表示言語(反映には再起動が必要)。日本語=埋め込み日本語, English=埋め込み英語, custom=DLL同階層の Lang/custom.txt。対応する Lang ファイル(English は Lang/en.txt)があればキー単位で埋め込み文言を上書きする(欠落キーは埋め込みへフォールバック)", (AcceptableValueBase)(object)new AcceptableValueList(new string[3] { "日本語", "English", "custom" }), Array.Empty())); RoundSeconds = config.Bind("01. General", "RoundSeconds", gameConfig.RoundSeconds, new ConfigDescription("Round time limit (seconds) / ラウンド制限時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(180, 3600), Array.Empty())); MinimapHideEnabled = config.Bind("01. General", "MinimapHideEnabled", gameConfig.MinimapHideEnabled, "Hide valuables and corpses from the minimap / 貴重品・死体のミニマップ非表示設定"); ValuableMapMode = config.Bind("01. General", "ValuableMapMode", (int)gameConfig.ValuableMapMode, new ConfigDescription("Valuable map mode (0 = realtime, 1 = meeting sync (recommended; positions freeze at discovery and snapshot at meeting start / extraction complete), 2 = hidden) / 貴重品マップ表示モード(0=リアルタイム, 1=会議同期(推奨・発見時の位置で静止表示し会議開始/抽出完了時にスナップショット更新), 2=非表示)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); OrbGaugeEnabled = config.Bind("01. General", "OrbGaugeEnabled", gameConfig.OrbGaugeEnabled, "Whether value lost as enemy-dropped orbs counts toward the perk gauge / 敵ドロップ品(オーブ)の減額を特典ゲージへ算入するか"); NecroVoiceMode = config.Bind("01. General", "NecroVoiceMode", (int)_necroVoiceModeDefault, new ConfigDescription("Netherworld voice eavesdrop mode (0 = OFF, 1 = non-werewolf dead, 2 = all dead). Host setting, synced to all clients / 冥界の声の傍聴モード(0=OFF, 1=人狼以外の死者, 2=全死者)。ホスト設定で全クライアントに同期される", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); GameOverAutoReturnSec = config.Bind("01. General", "GameOverAutoReturnSec", gameConfig.GameOverAutoReturnSec, new ConfigDescription("Fallback: seconds until the result screen auto-returns to the lobby when the host does not press the return key (0 = never) / ホストが帰還キーを押さない場合に結果画面から自動でロビーへ戻るまでの保険秒数(0=自動で戻らない)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 60), Array.Empty())); MeetingRightsPerPlayer = config.Bind("02. Meeting", "MeetingRightsPerPlayer", gameConfig.MeetingRightsPerPlayer, new ConfigDescription("Emergency meeting calls per player. Consumed only when a call is accepted / 1人あたりの会議開催権の回数。召集が受理された時点でのみ消費する", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); MeetingCountdownSec = config.Bind("02. Meeting", "MeetingCountdownSec", gameConfig.MeetingCountdownSec, new ConfigDescription("Lead time from the meeting announcement until everyone warps (seconds) / 会議開始通知から全員ワープまでの予告時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 30), Array.Empty())); MeetingDurationSec = config.Bind("02. Meeting", "MeetingDurationSec", gameConfig.MeetingDurationSec, new ConfigDescription("Meeting time limit (seconds). End time = warp time + this value / 会議の制限時間(秒)。終了時刻 = ワープ時刻 + この秒数", (AcceptableValueBase)(object)new AcceptableValueRange(10, 600), Array.Empty())); VoteTimeCutEnabled = config.Bind("02. Meeting", "VoteTimeCutEnabled", gameConfig.VoteTimeCutEnabled, "Whether each accepted vote shortens the remaining meeting time by a share based on the number of living players / 投票が受理されるたびに会議残り時間を生存人数に応じた割合で短縮するか"); ResultDisplaySec = config.Bind("02. Meeting", "ResultDisplaySec", gameConfig.ResultDisplaySec, new ConfigDescription("How long the vote results stay on screen before play resumes (seconds) / 開票結果の表示保持時間(秒)。この経過後に通常プレイへ復帰する", (AcceptableValueBase)(object)new AcceptableValueRange(0, 60), Array.Empty())); ConveneSuppressStartSec = config.Bind("02. Meeting", "ConveneSuppressStartSec", gameConfig.ConveneSuppressStartSec, new ConfigDescription("Time right after the game starts during which meetings cannot be called (seconds) / ゲーム開始直後に会議召集を抑止する時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 60), Array.Empty())); ConveneSuppressAfterSec = config.Bind("02. Meeting", "ConveneSuppressAfterSec", gameConfig.ConveneSuppressAfterSec, new ConfigDescription("Time right after a meeting ends during which the next meeting cannot be called (seconds) / 会議終了直後に次の会議召集を抑止する時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 60), Array.Empty())); ShamanChancePercent = config.Bind("03. Role Assignment", "ShamanChancePercent", gameConfig.ShamanChancePercent, new ConfigDescription("Shaman appearance chance P_shaman (0..100%). Villager-side variant: one villager is converted, werewolf count N is unaffected / 祈祷師の出現確率 P_shaman(0..100%)。村人側変種=当選時に村人1名が変換され、人狼の人数 N には影響しない", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); WerewolfCount = config.Bind("03. Role Assignment", "WerewolfCount", gameConfig.WerewolfCount, new ConfigDescription("Number of werewolves N (Werewolf slots; a winning Bomber roll converts one slot, always leaving at least one pure Werewolf). The Black Cat mutates from the villager side and is not counted here / 人狼の人数 N(人狼枠。爆弾魔当選時はこの枠の1人が爆弾魔へ変異する。純人狼は最低1人残る)。黒猫は村人陣営から変異するためこの人数には含まれない", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); BlackCatChancePercent = config.Bind("03. Role Assignment", "BlackCatChancePercent", gameConfig.BlackCatChancePercent, new ConfigDescription("Black Cat appearance chance P_cat (0..100%). The Black Cat mutates from one villager and joins the werewolf team; rolls happen only while at least one villager would remain / 黒猫の出現確率 P_cat(0..100%)。黒猫は村人陣営から変異して人狼陣営に加わる(変異後も村人が最低1人残る構成でのみ抽選)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); BomberChancePercent = config.Bind("03. Role Assignment", "BomberChancePercent", gameConfig.BomberChancePercent, new ConfigDescription("Bomber appearance chance P_bomber (0..100%). The Bomber mutates from one werewolf slot (needs werewolves >= 2) / 爆弾魔の出現確率 P_bomber(0..100%)。爆弾魔は人狼枠から変異する(人狼の人数2以上で抽選)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); ShamanGazeFullSec = config.Bind("04. Shaman", "ShamanGazeFullSec", gameConfig.ShamanGazeFullSec, new ConfigDescription("Seconds of keeping the nearest unannounced corpse in view to trigger the spirit vision (decays at double speed while out of view) / 霊表示に必要な注視秒数(最寄りの未周知死体が視界内で蓄積・視界外は倍速減衰)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 30), Array.Empty())); ShamanGhostCooldownSec = config.Bind("04. Shaman", "ShamanGhostCooldownSec", gameConfig.ShamanGhostCooldownSec, new ConfigDescription("Cooldown after the spirit vision ends before it can charge again (seconds) / 霊表示終了から再蓄積可能になるまでのクールダウン(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 120), Array.Empty())); ShamanStormWeakMeters = config.Bind("04. Shaman", "ShamanStormWeakMeters", gameConfig.ShamanStormWeakMeters, new ConfigDescription("Radius of the weak haunting ring around an unannounced corpse (m) / 霊障(弱)の発動半径(m)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); ShamanStormMediumMeters = config.Bind("04. Shaman", "ShamanStormMediumMeters", gameConfig.ShamanStormMediumMeters, new ConfigDescription("Radius of the medium haunting ring (m) / 霊障(中)の発動半径(m)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); ShamanStormStrongMeters = config.Bind("04. Shaman", "ShamanStormStrongMeters", gameConfig.ShamanStormStrongMeters, new ConfigDescription("Radius of the strong haunting ring (m) / 霊障(強)の発動半径(m)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); StaminaUnlockPct = config.Bind("05. Werewolf", "StaminaUnlockPct", gameConfig.StaminaUnlockPct, new ConfigDescription("Unlock threshold for infinite stamina (% of the map's total valuable value destroyed) / 無限スタミナの解禁閾値(マップ総貴重品額に対する減額%)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); JumpUnlockPct = config.Bind("05. Werewolf", "JumpUnlockPct", gameConfig.JumpUnlockPct, new ConfigDescription("Unlock threshold for extra jumps (%) / 追加ジャンプの解禁閾値(%)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); ExtraJumpCount = config.Bind("05. Werewolf", "ExtraJumpCount", gameConfig.ExtraJumpCount, new ConfigDescription("Extra mid-air jumps (-1 = effectively unlimited) / 滞空中の追加ジャンプ回数(-1=実質無限)", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 20), Array.Empty())); EnemyIgnoreUnlockPct = config.Bind("05. Werewolf", "EnemyIgnoreUnlockPct", gameConfig.EnemyIgnoreUnlockPct, new ConfigDescription("Unlock threshold for enemy ignore (%) / 敵認識無効の解禁閾値(%)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); HealUnlockPct = config.Bind("05. Werewolf", "HealUnlockPct", gameConfig.HealUnlockPct, new ConfigDescription("Unlock threshold for regeneration (%) / 自然治癒の解禁閾値(%)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); HealIntervalSec = config.Bind("05. Werewolf", "HealIntervalSec", gameConfig.HealIntervalSec, new ConfigDescription("Regeneration interval while Wolf Mode is on (seconds per 1 HP) / 狼化中の自然治癒の回復間隔(1HPあたりの秒数)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 60), Array.Empty())); BeaconChargePct = config.Bind("05. Werewolf", "BeaconChargePct", gameConfig.BeaconChargePct, new ConfigDescription("Threshold per beacon charge (%; each multiple reached grants every werewolf one use) / ビーコン1チャージあたりの閾値(%。この倍数の到達ごとに各人狼へ1回分付与)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); BeaconCooldownSec = config.Bind("05. Werewolf", "BeaconCooldownSec", gameConfig.BeaconCooldownSec, new ConfigDescription("Beacon cooldown (seconds) / ビーコンのクールダウン時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 600), Array.Empty())); BeaconSuppressStartSec = config.Bind("05. Werewolf", "BeaconSuppressStartSec", gameConfig.BeaconSuppressStartSec, new ConfigDescription("Time right after the game starts during which the beacon cannot be used (seconds) / ゲーム開始直後のビーコン使用抑止時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 180), Array.Empty())); BeaconSuppressAfterMeetingSec = config.Bind("05. Werewolf", "BeaconSuppressAfterMeetingSec", gameConfig.BeaconSuppressAfterMeetingSec, new ConfigDescription("Time right after a meeting ends during which the beacon cannot be used (seconds) / 会議終了直後のビーコン使用抑止時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 180), Array.Empty())); BlackCatRevealDelaySec = config.Bind("06. Black Cat", "BlackCatRevealDelaySec", gameConfig.BlackCatRevealDelaySec, new ConfigDescription("Delay from game start until the Black Cat is privately notified of its role (seconds) / ゲーム開始から黒猫本人へ自覚通知するまでの遅延(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 300), Array.Empty())); BlackCatCurseEnabled = config.Bind("06. Black Cat", "BlackCatCurseEnabled", gameConfig.BlackCatCurseEnabled, "Enable the Black Cat's drag-down ability when executed / 黒猫が処刑されたときの道連れ能力を有効にする"); InformantThresholdPct = config.Bind("06. Black Cat", "InformantThresholdPct", gameConfig.InformantThresholdPct, new ConfigDescription("Informant threshold: reveals the werewolf list to the Black Cat (%; a high value not reached early is recommended) / 内通(黒猫への人狼一覧開示)の閾値(%。序盤に到達しない高めを推奨)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); CatGaugeSyncIntervalSec = config.Bind("06. Black Cat", "CatGaugeSyncIntervalSec", gameConfig.CatGaugeSyncIntervalSec, new ConfigDescription("Gauge sync interval to the Black Cat (seconds; 0 = realtime. Werewolves always get realtime. Meeting start/end always refreshes regardless of the interval) / 黒猫へのゲージ配信間隔(秒。0=リアルタイム配信。人狼へは常にリアルタイム。会議開始・終了時は間隔に関係なく更新される)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 600), Array.Empty())); BomberProximityMeters = config.Bind("07. Bomber", "BomberProximityMeters", gameConfig.BomberProximityMeters, new ConfigDescription("Distance within which the Bomber counts a plant target as close contact (m) / 爆弾魔がプラント対象を至近と見なす距離(m)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); BomberGaugeFullSec = config.Bind("07. Bomber", "BomberGaugeFullSec", gameConfig.BomberGaugeFullSec, new ConfigDescription("Total seconds of close contact needed to fill the proximity gauge / 近接ゲージが満タンになるまでの累計滞在秒", (AcceptableValueBase)(object)new AcceptableValueRange(1, 60), Array.Empty())); BomberInitialCooldownSec = config.Bind("07. Bomber", "BomberInitialCooldownSec", gameConfig.BomberInitialCooldownSec, new ConfigDescription("Initial cooldown after the match starts; proximity gauges are paused during it (seconds) / 試合開始直後の専用クールダウン。この間は近接ゲージも停止(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 180), Array.Empty())); BomberCooldownSec = config.Bind("07. Bomber", "BomberCooldownSec", gameConfig.BomberCooldownSec, new ConfigDescription("Cooldown after meetings, planting, re-planting, and detonation (seconds) / 会議後・プラント・付替・爆破後クールダウン(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 60), Array.Empty())); BomberAmmoRefillPct = config.Bind("07. Bomber", "BomberAmmoRefillPct", gameConfig.BomberAmmoRefillPct, new ConfigDescription("Perk-gauge share required to refill one bomb (%) / 弾1発の補充に要する特典ゲージ割合%", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); BomberBlastRadiusMeters = config.Bind("07. Bomber", "BomberBlastRadiusMeters", Mathf.RoundToInt(gameConfig.BomberBlastRadiusMeters), new ConfigDescription("Blast radius / warning-sound audible radius (m, integer) / 爆風半径・警告音可聴半径(m・整数)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); BomberBlastPlayerDamage = config.Bind("07. Bomber", "BomberBlastPlayerDamage", gameConfig.BomberBlastPlayerDamage, new ConfigDescription("Damage to nearby players / 周囲プレイヤーへのダメージ", (AcceptableValueBase)(object)new AcceptableValueRange(1, 999), Array.Empty())); BomberBlastEnemyDamage = config.Bind("07. Bomber", "BomberBlastEnemyDamage", gameConfig.BomberBlastEnemyDamage, new ConfigDescription("Damage to enemies / 敵ダメージ", (AcceptableValueBase)(object)new AcceptableValueRange(1, 999), Array.Empty())); StartLevelNumber = config.Bind("08. Stage", "StartLevelNumber", gameConfig.StartLevelNumber, new ConfigDescription("Level of the werewolf session (the N in the on-screen \"Level N\") / 人狼セッションのレベル(画面表示「Level N」の N)", (AcceptableValueBase)(object)new AcceptableValueRange(1, 30), Array.Empty())); StartMapName = config.Bind("08. Stage", "StartMapName", "", "Map type name for the werewolf session. Empty = random (vanilla default selection). Unknown names also mean random / 人狼セッションの開始マップ種別名。空 = ランダム(バニラ既定の選択)。実在しない名前もランダム扱い"); StartEnergyPct = config.Bind("08. Stage", "StartEnergyPct", gameConfig.StartEnergyPct, new ConfigDescription("Truck charger level at session start (%; 0 = empty, 100 = full) / トラック充電器の開始時充電量(%。0 = 空, 100 = 満充電)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); OrbDropMax = config.Bind("08. Stage", "OrbDropMax", gameConfig.OrbDropMax, new ConfigDescription("Number of valuable orbs dropped when an enemy dies (vanilla = 3) / 敵撃破時にドロップする貴重品オーブの数(バニラ = 3)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); Dictionary dictionary = new Dictionary { ["Health"] = 1, ["Strength"] = 2 }; _startUpgrades = new Dictionary>(WorldgenUpgrades.Names.Count); foreach (string name in WorldgenUpgrades.Names) { int value; int num = (dictionary.TryGetValue(name, out value) ? value : 0); _startUpgrades[name] = config.Bind("09. Player Upgrades", "StartUpgrade" + name, num, new ConfigDescription("Starting upgrade level shared by all players (" + name + "). 0 = no upgrade / 全プレイヤー共通の開始時強化段階(" + name + ")。0 = 強化なし", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); } WolfModeKey = config.Bind("Client Keybinds", "WolfModeKey", (KeyCode)102, new ConfigDescription("Key binding for the Wolf Mode toggle (bulk ON/OFF of unlocked perks) / 狼化トグル(解禁済み特典の一括ON/OFF)のキーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); BeaconKey = config.Bind("Client Keybinds", "BeaconKey", (KeyCode)103, new ConfigDescription("Key binding for using the beacon / ビーコン使用のキーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); BomberPlantKey = config.Bind("Client Keybinds", "BomberPlantKey", (KeyCode)102, new ConfigDescription("Key binding for the Bomber's plant (turn a player into a bomb) / 爆弾魔のプラント(爆弾化)キーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); BomberDetonateKey = config.Bind("Client Keybinds", "BomberDetonateKey", (KeyCode)103, new ConfigDescription("Key binding for the Bomber's detonation / 爆弾魔の起爆キーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); CorpseReportKey = config.Bind("Client Keybinds", "CorpseReportKey", (KeyCode)114, new ConfigDescription("Key binding to report an emergency meeting near an undiscovered body (dead player's head) / 未発見の死体(死者頭部)の近くで押すと緊急会議を通報するキーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); MeetingMapKey = config.Bind("Client Keybinds", "MeetingMapKey", (KeyCode)109, new ConfigDescription("Key binding to toggle the full-map overlay during meetings / 会議中の全体マップオーバーレイのトグルキー", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); MeetingChatLogKey = config.Bind("Client Keybinds", "MeetingChatLogKey", (KeyCode)108, new ConfigDescription("Key binding to toggle the chat log panel during meetings / 会議チャットログのトグルキー", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); LobbySettingsPanelKey = config.Bind("Client Keybinds", "LobbySettingsPanelKey", (KeyCode)288, new ConfigDescription("Key binding to toggle the lobby settings panel / ロビー設定確認パネルの表示/非表示トグルのキーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); ManualKey = config.Bind("Client Keybinds", "ManualKey", (KeyCode)282, new ConfigDescription("Key binding to toggle the in-game manual / ゲーム内説明書の表示/非表示トグルのキーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); ResultReturnKey = config.Bind("Client Keybinds", "ResultReturnKey", (KeyCode)286, new ConfigDescription("Host only: key binding to return to the lobby from the match result screen / ホスト専用: 試合結果画面からロビーへ戻るキーバインド", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); ButtonOffsetX = config.Bind("Client UI", "ButtonOffsetX", gameConfig.ButtonOffsetX, new ConfigDescription("Meeting button offset X relative to the truck (fine-tuned on Stage 1) / 会議召集ボタンのトラック基準相対オフセットX", (AcceptableValueBase)(object)new AcceptableValueRange(-10f, 10f), new object[1] { "HideFromREPOConfig" })); ButtonOffsetY = config.Bind("Client UI", "ButtonOffsetY", gameConfig.ButtonOffsetY, new ConfigDescription("Meeting button offset Y relative to the truck (fine-tuned on Stage 1) / 会議召集ボタンのトラック基準相対オフセットY", (AcceptableValueBase)(object)new AcceptableValueRange(-10f, 10f), new object[1] { "HideFromREPOConfig" })); ButtonOffsetZ = config.Bind("Client UI", "ButtonOffsetZ", gameConfig.ButtonOffsetZ, new ConfigDescription("Meeting button offset Z relative to the truck (fine-tuned on Stage 1) / 会議召集ボタンのトラック基準相対オフセットZ", (AcceptableValueBase)(object)new AcceptableValueRange(-10f, 10f), new object[1] { "HideFromREPOConfig" })); ButtonYaw = config.Bind("Client UI", "ButtonYaw", gameConfig.ButtonYaw, new ConfigDescription("Meeting button yaw fine-tune (degrees; tuned on Stage 1) / 会議召集ボタンの向き(Yaw, 度)の微調整", (AcceptableValueBase)(object)new AcceptableValueRange(-180f, 180f), new object[1] { "HideFromREPOConfig" })); ButtonPitch = config.Bind("Client UI", "ButtonPitch", gameConfig.ButtonPitch, new ConfigDescription("Meeting button pitch (degrees). 0 = flat facing up, 90 = wall mount (vertical, facing forward) / 会議召集ボタンのピッチ(度)。0=水平面上向き、90=壁面マウント(垂直面前向き)", (AcceptableValueBase)(object)new AcceptableValueRange(-180f, 180f), new object[1] { "HideFromREPOConfig" })); ToastDurationSec = config.Bind("Client UI", "ToastDurationSec", gameConfig.ToastDurationSec, new ConfigDescription("Toast notification duration (seconds) / トースト通知の表示秒数", (AcceptableValueBase)(object)new AcceptableValueRange(0, 60), new object[1] { "HideFromREPOConfig" })); MeetingMapOrthoSize = config.Bind("Client UI", "MeetingMapOrthoSize", 60f, new ConfigDescription("Camera orthographicSize while the full-map overlay is open (larger = wider view). MiniMap MOD defaults to 2.25 / max 10; a larger default is used here to show the whole level. Needs vary per level, so tune in-game (recommended range 5-50) / 全体マップオーバーレイ表示時のカメラ orthographicSize(大きいほど広範囲)。レベル毎に必要な広さが異なるため実機で調整可(推奨レンジ 5〜50)", (AcceptableValueBase)(object)new AcceptableValueRange(1.5f, 200f), new object[1] { "HideFromREPOConfig" })); MeetingMapResolution = config.Bind("Client UI", "MeetingMapResolution", 1, new ConfigDescription("Full-map overlay render resolution preset. 0=1280x768 / 1=1600x960 / 2=1920x1152 (all 5:3, same aspect as the 1200x720 panel). The vanilla handheld-map RT is low-res and blurry when magnified, so a dedicated RT replaces it while the overlay is open / 全体マップオーバーレイのレンダリング解像度プリセット(0=1280x768 / 1=1600x960 / 2=1920x1152)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), new object[1] { "HideFromREPOConfig" })); MeetingMapGrid = config.Bind("Client UI", "MeetingMapGrid", true, new ConfigDescription("Show an Excel-style coordinate grid on the meeting full-map overlay (columns A,B,C... left to right / rows 1,2,3... top to bottom; one cell = one room module), so locations can be called out like \"the room at C5\" / 会議全体マップにエクセル風の座標グリッド(列=左からA,B,C…・行=上から1,2,3…・1マス=1部屋モジュール)を重ねて表示する(「C5の部屋」のような位置伝達用)", (AcceptableValueBase)null, Array.Empty())); MeetingChatLog = config.Bind("Client UI", "MeetingChatLog", true, new ConfigDescription("Show the chat log panel on the right side of the screen during meetings (speaker avatar and message bubbles, plus a recap of deaths, destroyed valuables, delivery progress and beacon uses). Messages from the dead are shown only to the dead / 会議中に画面右端へ会議チャットログを表示する(発言者アバターと吹き出し+死亡者・破壊された貴重品・納品状況・ビーコン使用の要約)。死者の発言は死者にだけ表示される", (AcceptableValueBase)null, Array.Empty())); CursorMirrorScale = config.Bind("Client UI", "CursorMirrorScale", 1f, new ConfigDescription("Scale of the mirror cursor drawn over meeting panels (1.0 = vanilla cursor's measured size) / 会議パネル等の上に重ねるミラーカーソルのサイズ倍率(1.0=バニラカーソルの実測換算そのまま)", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 3f), Array.Empty())); HudOffsetX = config.Bind("Client UI", "HudOffsetX (+right)", 0, new ConfigDescription("Horizontal offset of the top-left role badge in 1920x1080-reference px (+ = right). Adjust if it overlaps the vanilla health/stamina display on your resolution / 左上役職バッジの横オフセット(1920x1080基準px・+で右)。バニラの体力/スタミナ表示と重なる環境で調整する", (AcceptableValueBase)(object)new AcceptableValueRange(-1000, 1000), Array.Empty())); HudOffsetY = config.Bind("Client UI", "HudOffsetY (+down)", 0, new ConfigDescription("Vertical offset of the top-left role badge in 1920x1080-reference px (+ = down). Adjust if it overlaps the vanilla health/stamina display on your resolution / 左上役職バッジの縦オフセット(1920x1080基準px・+で下)。バニラの体力/スタミナ表示と重なる環境で調整する", (AcceptableValueBase)(object)new AcceptableValueRange(-1000, 1000), Array.Empty())); ResetTutorials = config.Bind("Tutorial", "ResetTutorials", false, "Set to true to reset all tutorial seen-flags (flips back to false automatically on the next poll; acts as a reset button to show the tutorials again) / true にするとチュートリアルの既読状態を全てリセットする(次のポーリングで自動的にfalseへ戻る。チュートリアルをもう一度表示させたい時のリセットボタン代わり)"); TutorialFontScale = config.Bind("Tutorial", "FontScale", 0.7f, new ConfigDescription("Tutorial text font scale (ratio against the vanilla tutorial notification's maximum size) / チュートリアル文言のフォントサイズ倍率(バニラのチュートリアル通知の上限サイズに対する比率)", (AcceptableValueBase)(object)new AcceptableValueRange(0.4f, 1.5f), Array.Empty())); _tutorialSeen = new Dictionary>(); foreach (TutorialId value2 in Enum.GetValues(typeof(TutorialId))) { _tutorialSeen[value2] = config.Bind("Tutorial", "Seen" + value2, false, new ConfigDescription($"Whether tutorial \"{value2}\" has been shown (internal bookkeeping) / チュートリアル「{value2}」を表示済みか(内部管理用)", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); } NecroVoiceVolume = config.Bind("Voice", "NecroVoiceVolume", gameConfig.NecroVoiceVolume, new ConfigDescription("Eavesdrop voice volume (0..1; vanilla normal voice is roughly 0.5) / 傍聴音声の再生音量(0..1。バニラ通常声≒0.5相当)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), new object[1] { "HideFromREPOConfig" })); NecroVoiceLowPassCutoffHz = config.Bind("Voice", "NecroVoiceLowPassCutoffHz", gameConfig.NecroVoiceLowPassCutoffHz, new ConfigDescription("Eavesdrop voice muffling (LowPass cutoff frequency, Hz) / 傍聴音声のくすみ(LowPass カットオフ周波数 Hz)", (AcceptableValueBase)(object)new AcceptableValueRange(100f, 20000f), new object[1] { "HideFromREPOConfig" })); NecroVoiceEchoDelayMs = config.Bind("Voice", "NecroVoiceEchoDelayMs", gameConfig.NecroVoiceEchoDelayMs, new ConfigDescription("Eavesdrop voice echo interval (ms) / 傍聴音声の反響1回の間隔(ms)", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 2000f), new object[1] { "HideFromREPOConfig" })); NecroVoiceEchoDecay = config.Bind("Voice", "NecroVoiceEchoDecay", gameConfig.NecroVoiceEchoDecay, new ConfigDescription("Eavesdrop voice echo decay (0..1) / 傍聴音声の反響の減衰率(0..1)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbRoom = config.Bind("Voice", "NecroVoiceReverbRoom", gameConfig.NecroVoiceReverbRoom, new ConfigDescription("Eavesdrop voice reverb room level (1/100 dB) / 傍聴音声の Reverb ルーム全体レベル(100分の1dB)", (AcceptableValueBase)(object)new AcceptableValueRange(-10000f, 0f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbRoomHF = config.Bind("Voice", "NecroVoiceReverbRoomHF", gameConfig.NecroVoiceReverbRoomHF, new ConfigDescription("Eavesdrop voice reverb room HF level (1/100 dB) / 傍聴音声の Reverb ルーム高域レベル(100分の1dB)", (AcceptableValueBase)(object)new AcceptableValueRange(-10000f, 0f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbDecayTime = config.Bind("Voice", "NecroVoiceReverbDecayTime", gameConfig.NecroVoiceReverbDecayTime, new ConfigDescription("Eavesdrop voice reverb decay time (seconds) / 傍聴音声の Reverb 残響減衰時間(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 20f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbDecayHFRatio = config.Bind("Voice", "NecroVoiceReverbDecayHFRatio", gameConfig.NecroVoiceReverbDecayHFRatio, new ConfigDescription("Eavesdrop voice reverb HF decay ratio / 傍聴音声の Reverb 高域減衰比", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 2f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbReflections = config.Bind("Voice", "NecroVoiceReverbReflections", gameConfig.NecroVoiceReverbReflections, new ConfigDescription("Eavesdrop voice reverb early reflection level (1/100 dB) / 傍聴音声の Reverb 初期反射レベル(100分の1dB)", (AcceptableValueBase)(object)new AcceptableValueRange(-10000f, 1000f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbReflectionsDelay = config.Bind("Voice", "NecroVoiceReverbReflectionsDelay", gameConfig.NecroVoiceReverbReflectionsDelay, new ConfigDescription("Eavesdrop voice reverb early reflection delay (seconds) / 傍聴音声の Reverb 初期反射遅延(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.3f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbLevel = config.Bind("Voice", "NecroVoiceReverbLevel", gameConfig.NecroVoiceReverbLevel, new ConfigDescription("Eavesdrop voice reverb late reflection level (1/100 dB) / 傍聴音声の Reverb 後期反射レベル(100分の1dB)", (AcceptableValueBase)(object)new AcceptableValueRange(-10000f, 2000f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbDelay = config.Bind("Voice", "NecroVoiceReverbDelay", gameConfig.NecroVoiceReverbDelay, new ConfigDescription("Eavesdrop voice reverb late reflection delay (seconds) / 傍聴音声の Reverb 後期反射遅延(秒)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.1f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbDiffusion = config.Bind("Voice", "NecroVoiceReverbDiffusion", gameConfig.NecroVoiceReverbDiffusion, new ConfigDescription("Eavesdrop voice reverb diffusion (0..100) / 傍聴音声の Reverb 拡散度(0..100)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbDensity = config.Bind("Voice", "NecroVoiceReverbDensity", gameConfig.NecroVoiceReverbDensity, new ConfigDescription("Eavesdrop voice reverb density (0..100) / 傍聴音声の Reverb 密度(0..100)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), new object[1] { "HideFromREPOConfig" })); NecroVoiceReverbHFReference = config.Bind("Voice", "NecroVoiceReverbHFReference", gameConfig.NecroVoiceReverbHFReference, new ConfigDescription("Eavesdrop voice reverb HF reference frequency (Hz) / 傍聴音声の Reverb 高域基準周波数(Hz)", (AcceptableValueBase)(object)new AcceptableValueRange(20f, 20000f), new object[1] { "HideFromREPOConfig" })); DebugMode = config.Bind("Debug", "DebugMode", gameConfig.DebugMode, new ConfigDescription("Debug mode. true allows cheat commands (/ww ...), writes secret log lines immediately (false batches them until the match ends; ADR-0030), and shows a persistent TEST PLAY banner to every participant when the host enables it (ADR-0058) / デバッグモード。true でチートコマンド(/ww ...)を許可し、secret ログ行を即時出力する(false は試合終了時に一括出力。ADR-0030)。ホストが有効にすると全参加者にテストプレイ常設バナーが表示される(ADR-0058)", (AcceptableValueBase)null, new object[1] { "HideFromREPOConfig" })); } internal GameConfig Snapshot() { int value = ValuableMapMode.Value; ValuableMapMode valuableMapMode = ((value >= 0 && value <= 2) ? ((ValuableMapMode)value) : _valuableMapModeDefault); int value2 = NecroVoiceMode.Value; NecroVoiceMode necroVoiceMode = ((value2 >= 0 && value2 <= 2) ? ((NecroVoiceMode)value2) : _necroVoiceModeDefault); Dictionary dictionary = new Dictionary(_startUpgrades.Count); foreach (KeyValuePair> startUpgrade in _startUpgrades) { dictionary[startUpgrade.Key] = startUpgrade.Value.Value; } return new GameConfig { WerewolfCount = WerewolfCount.Value, BlackCatChancePercent = BlackCatChancePercent.Value, BomberChancePercent = BomberChancePercent.Value, RoundSeconds = RoundSeconds.Value, BlackCatRevealDelaySec = BlackCatRevealDelaySec.Value, BlackCatCurseEnabled = BlackCatCurseEnabled.Value, DebugMode = DebugMode.Value, MeetingRightsPerPlayer = MeetingRightsPerPlayer.Value, ConveneSuppressStartSec = ConveneSuppressStartSec.Value, ConveneSuppressAfterSec = ConveneSuppressAfterSec.Value, MeetingCountdownSec = MeetingCountdownSec.Value, MeetingDurationSec = MeetingDurationSec.Value, VoteTimeCutEnabled = VoteTimeCutEnabled.Value, ResultDisplaySec = ResultDisplaySec.Value, ButtonOffsetX = ButtonOffsetX.Value, ButtonOffsetY = ButtonOffsetY.Value, ButtonOffsetZ = ButtonOffsetZ.Value, ButtonYaw = ButtonYaw.Value, ButtonPitch = ButtonPitch.Value, StaminaUnlockPct = StaminaUnlockPct.Value, JumpUnlockPct = JumpUnlockPct.Value, EnemyIgnoreUnlockPct = EnemyIgnoreUnlockPct.Value, HealUnlockPct = HealUnlockPct.Value, HealIntervalSec = HealIntervalSec.Value, BeaconChargePct = BeaconChargePct.Value, InformantThresholdPct = InformantThresholdPct.Value, ExtraJumpCount = ExtraJumpCount.Value, BeaconCooldownSec = BeaconCooldownSec.Value, BeaconSuppressStartSec = BeaconSuppressStartSec.Value, BeaconSuppressAfterMeetingSec = BeaconSuppressAfterMeetingSec.Value, CatGaugeSyncIntervalSec = CatGaugeSyncIntervalSec.Value, OrbGaugeEnabled = OrbGaugeEnabled.Value, WerewolfModeEnabled = WerewolfModeEnabled.Value, MinimapHideEnabled = MinimapHideEnabled.Value, ValuableMapMode = valuableMapMode, GameOverAutoReturnSec = GameOverAutoReturnSec.Value, ToastDurationSec = ToastDurationSec.Value, StartLevelNumber = StartLevelNumber.Value, StartMapName = (StartMapName.Value ?? ""), StartItemsSpec = (ItemsSpecProvider?.Invoke() ?? ""), StartEnergyPct = StartEnergyPct.Value, StartUpgradesSpec = WorldgenSpec.Encode(dictionary), OrbDropMax = OrbDropMax.Value, NecroVoiceMode = necroVoiceMode, NecroVoiceVolume = NecroVoiceVolume.Value, NecroVoiceLowPassCutoffHz = NecroVoiceLowPassCutoffHz.Value, NecroVoiceEchoDelayMs = NecroVoiceEchoDelayMs.Value, NecroVoiceEchoDecay = NecroVoiceEchoDecay.Value, NecroVoiceReverbRoom = NecroVoiceReverbRoom.Value, NecroVoiceReverbRoomHF = NecroVoiceReverbRoomHF.Value, NecroVoiceReverbDecayTime = NecroVoiceReverbDecayTime.Value, NecroVoiceReverbDecayHFRatio = NecroVoiceReverbDecayHFRatio.Value, NecroVoiceReverbReflections = NecroVoiceReverbReflections.Value, NecroVoiceReverbReflectionsDelay = NecroVoiceReverbReflectionsDelay.Value, NecroVoiceReverbLevel = NecroVoiceReverbLevel.Value, NecroVoiceReverbDelay = NecroVoiceReverbDelay.Value, NecroVoiceReverbDiffusion = NecroVoiceReverbDiffusion.Value, NecroVoiceReverbDensity = NecroVoiceReverbDensity.Value, NecroVoiceReverbHFReference = NecroVoiceReverbHFReference.Value, ShamanChancePercent = ShamanChancePercent.Value, ShamanGazeFullSec = ShamanGazeFullSec.Value, ShamanGhostCooldownSec = ShamanGhostCooldownSec.Value, ShamanStormWeakMeters = ShamanStormWeakMeters.Value, ShamanStormMediumMeters = ShamanStormMediumMeters.Value, ShamanStormStrongMeters = ShamanStormStrongMeters.Value, BomberProximityMeters = BomberProximityMeters.Value, BomberGaugeFullSec = BomberGaugeFullSec.Value, BomberInitialCooldownSec = BomberInitialCooldownSec.Value, BomberCooldownSec = BomberCooldownSec.Value, BomberBlastRadiusMeters = BomberBlastRadiusMeters.Value, BomberBlastPlayerDamage = BomberBlastPlayerDamage.Value, BomberBlastEnemyDamage = BomberBlastEnemyDamage.Value, BomberAmmoRefillPct = BomberAmmoRefillPct.Value }; } internal bool IsTutorialSeen(TutorialId id) { if (_tutorialSeen.TryGetValue(id, out var value)) { return value.Value; } return true; } internal void MarkTutorialSeen(TutorialId id) { if (_tutorialSeen.TryGetValue(id, out var value)) { value.Value = true; } } internal void TickTutorialReset() { if (!ResetTutorials.Value) { return; } foreach (ConfigEntry value in _tutorialSeen.Values) { value.Value = false; } ResetTutorials.Value = false; } } internal static class GameRefs { public static FieldRef PlayerAvatar_deadSet; public static FieldRef PlayerAvatar_isDisabled; public static FieldRef PlayerAvatar_isTumbling; public static FieldRef PlayerAvatar_levelAnimationCompleted; public static FieldRef PlayerAvatar_playerDeathHead; public static FieldRef PlayerAvatar_voiceChat; public static FieldRef PlayerAvatar_voiceChatFetched; public static FieldRef PlayerAvatar_tumble; public static FieldRef PlayerHealth_health; public static FieldRef PlayerTumble_physGrabObject; public static FieldRef PlayerDeathHead_physGrabObject; public static FieldRef PlayerVoiceChat_isTalking; public static FieldRef PlayerVoiceChat_audioSource; public static FieldRef PlayerVoiceChat_recorder; public static FieldRef PlayerVoiceChat_TTSinstantiated; public static FieldRef PlayerVoiceChat_overrideMuteTimer; public static FieldRef PlayerVoiceChat_clipLoudness; public static FieldRef PlayerVoiceChat_playerAvatar; public static FieldRef RunManager_debugLevel; public static FieldRef RunManager_restarting; public static FieldRef> RunManager_voiceChats; public static FieldRef RunManager_cosmeticWorldObjectCooldown; public static FieldRef RunManager_moonLevelChanged; public static FieldRef RoundDirector_extractionPoints; public static FieldRef RoundDirector_extractionPointsCompleted; public static FieldRef RoundDirector_allExtractionPointsCompleted; public static FieldRef RoundDirector_haulGoal; public static FieldRef RoundDirector_extractionPointSurplus; public static FieldRef StatsManager_saveFileCurrent; public static FieldRef>> StatsManager_dictionaryOfDictionaries; public static FieldRef PunManager_photonView; public static FieldRef> MetaManager_cosmeticTokens; public static FieldRef> InputManager_inputActions; public static FieldRef DataDirector_toggleMute; public static FieldRef ChatManager_chatMessage; public static FieldRef MenuPlayerListed_playerAvatar; public static FieldRef MenuPlayerListed_listSpot; public static FieldRef MenuPlayerHead_isTalking; public static FieldRef MenuPlayerHead_headTransform; public static FieldRef PlayerCrown_playerAvatar; public static FieldRef PlayerCrown_active; public static FieldRef WorldSpaceUIPlayerName_playerAvatar; public static FieldRef MapToolController_Active; public static FieldRef TruckScreenText_playerChatBoxState; public static FieldRef PlayerController_JumpFirst; public static FieldRef PlayerController_JumpExtraCurrent; public static FieldRef ItemMelee_hurtCollider; public static FieldRef PhysGrabber_grabbedPhysGrabObject; public static FieldRef PhysGrabObject_isMelee; public static FieldRef ValuableObject_dollarValueCurrent; public static FieldRef ValuableObject_dollarValueOriginal; public static FieldRef ValuableObject_dollarValueOverride; public static FieldRef ValuableObject_discovered; public static FieldRef PhysGrabObjectImpactDetector_valuableObject; public static FieldRef PhysGrabObjectImpactDetector_physGrabObject; public static FieldRef EnemyParent_Enemy; public static FieldRef EnemyParent_Spawned; public static FieldRef Enemy_Rigidbody; public static FieldRef Enemy_HasRigidbody; public static FieldRef MapCustom_mapCustomEntity; public static FieldRef> EnemyDirector_debugNoVision; public static FieldRef EnemyShadow_playerTarget; public static MethodInfo EnemyShadow_UpdatePlayerTarget; public static FieldRef AudioManager_currentSnapshot; public static FieldInfo ParticleScriptExplosion_explosionPrefab; public static FieldInfo ParticlePrefabExplosion_HurtColliderSecondSetup; private static List s_failures; private static int s_resolvedCount; public static bool ResolveAll() { s_failures = new List(); s_resolvedCount = 0; PlayerAvatar_deadSet = Field("deadSet"); PlayerAvatar_isDisabled = Field("isDisabled"); PlayerAvatar_isTumbling = Field("isTumbling"); PlayerAvatar_levelAnimationCompleted = Field("levelAnimationCompleted"); PlayerAvatar_playerDeathHead = Field("playerDeathHead"); PlayerAvatar_voiceChat = Field("voiceChat"); PlayerAvatar_voiceChatFetched = Field("voiceChatFetched"); PlayerAvatar_tumble = Field("tumble"); PlayerHealth_health = Field("health"); PlayerTumble_physGrabObject = Field("physGrabObject"); PlayerDeathHead_physGrabObject = Field("physGrabObject"); PlayerVoiceChat_isTalking = Field("isTalking"); PlayerVoiceChat_audioSource = Field("audioSource"); PlayerVoiceChat_recorder = Field("recorder"); PlayerVoiceChat_TTSinstantiated = Field("TTSinstantiated"); PlayerVoiceChat_overrideMuteTimer = Field("overrideMuteTimer"); PlayerVoiceChat_clipLoudness = Field("clipLoudness"); PlayerVoiceChat_playerAvatar = Field("playerAvatar"); RunManager_debugLevel = Field("debugLevel"); RunManager_restarting = Field("restarting"); RunManager_voiceChats = Field>("voiceChats"); RunManager_cosmeticWorldObjectCooldown = Field("cosmeticWorldObjectCooldown"); RunManager_moonLevelChanged = Field("moonLevelChanged"); RoundDirector_extractionPoints = Field("extractionPoints"); RoundDirector_extractionPointsCompleted = Field("extractionPointsCompleted"); RoundDirector_allExtractionPointsCompleted = Field("allExtractionPointsCompleted"); RoundDirector_haulGoal = Field("haulGoal"); RoundDirector_extractionPointSurplus = Field("extractionPointSurplus"); StatsManager_saveFileCurrent = Field("saveFileCurrent"); StatsManager_dictionaryOfDictionaries = Field>>("dictionaryOfDictionaries"); PunManager_photonView = Field("photonView"); MetaManager_cosmeticTokens = Field>("cosmeticTokens"); InputManager_inputActions = Field>("inputActions"); DataDirector_toggleMute = Field("toggleMute"); ChatManager_chatMessage = Field("chatMessage"); MenuPlayerListed_playerAvatar = Field("playerAvatar"); MenuPlayerListed_listSpot = Field("listSpot"); MenuPlayerHead_isTalking = Field("isTalking"); MenuPlayerHead_headTransform = Field("headTransform"); PlayerCrown_playerAvatar = Field("playerAvatar"); PlayerCrown_active = Field("active"); WorldSpaceUIPlayerName_playerAvatar = Field("playerAvatar"); MapToolController_Active = Field("Active"); TruckScreenText_playerChatBoxState = Field("playerChatBoxState"); PlayerController_JumpFirst = Field("JumpFirst"); PlayerController_JumpExtraCurrent = Field("JumpExtraCurrent"); ItemMelee_hurtCollider = Field("hurtCollider"); PhysGrabber_grabbedPhysGrabObject = Field("grabbedPhysGrabObject"); PhysGrabObject_isMelee = Field("isMelee"); ValuableObject_dollarValueCurrent = Field("dollarValueCurrent"); ValuableObject_dollarValueOriginal = Field("dollarValueOriginal"); ValuableObject_dollarValueOverride = Field("dollarValueOverride"); ValuableObject_discovered = Field("discovered"); PhysGrabObjectImpactDetector_valuableObject = Field("valuableObject"); PhysGrabObjectImpactDetector_physGrabObject = Field("physGrabObject"); EnemyParent_Enemy = Field("Enemy"); EnemyParent_Spawned = Field("Spawned"); Enemy_Rigidbody = Field("Rigidbody"); Enemy_HasRigidbody = Field("HasRigidbody"); MapCustom_mapCustomEntity = Field("mapCustomEntity"); EnemyDirector_debugNoVision = Field>("debugNoVision"); EnemyShadow_playerTarget = Field("playerTarget"); EnemyShadow_UpdatePlayerTarget = RawMethod(typeof(EnemyShadow), "UpdatePlayerTarget"); AudioManager_currentSnapshot = Field("currentSnapshot"); ParticleScriptExplosion_explosionPrefab = RawField(typeof(ParticleScriptExplosion), "explosionPrefab"); ParticlePrefabExplosion_HurtColliderSecondSetup = RawField(typeof(ParticlePrefabExplosion), "HurtColliderSecondSetup"); if (s_failures.Count == 0) { WLog.Line("game_refs_resolved", false, ("count", s_resolvedCount)); return true; } WLog.Line("game_refs_disabled", false, ("failedFields", s_failures.Count)); return false; } private static FieldRef Field(string fieldName) where TObj : class { try { FieldRef result = AccessTools.FieldRefAccess(fieldName); s_resolvedCount++; return result; } catch (Exception ex) { Fail(typeof(TObj).Name, fieldName, ex.Message); return null; } } private static MethodInfo RawMethod(Type type, string methodName) { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { Fail(type.Name, methodName, "method not found"); return null; } s_resolvedCount++; return methodInfo; } private static FieldInfo RawField(Type type, string fieldName) { FieldInfo fieldInfo = AccessTools.Field(type, fieldName); if (fieldInfo == null) { Fail(type.Name, fieldName, "field not found"); return null; } s_resolvedCount++; return fieldInfo; } private static void Fail(string typeName, string fieldName, string error) { s_failures.Add(typeName + "." + fieldName); WLog.Line("game_ref_failed", false, ("type", typeName), ("field", fieldName), ("err", error)); } } [BepInPlugin("minorunara.werewolf", "Werewolf", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "minorunara.werewolf"; public const string PluginName = "Werewolf"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Logger; internal static GameConfig GameConfig; internal static ConfigBindings Bindings; internal static ConfigEntry WolfModeKey; internal static ConfigEntry BeaconKey; internal static ConfigEntry LobbySettingsPanelKey; internal static ConfigEntry MeetingMapKey; internal static ConfigEntry MeetingChatLogKey; internal static ConfigEntry CorpseReportKey; internal static ConfigEntry ResultReturnKey; internal static ConfigEntry ManualKey; internal static void RefreshGameConfig() { if (Bindings != null) { GameConfig = Bindings.Snapshot(); } } private void Awake() { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; if (!CosmeticLottery.ValidateWeights()) { Logger.LogWarning((object)"CosmeticLottery.ValidateWeights failed: weight constants (Common/Uncommon/Rare/UltraRare) do not sum to TotalWeight. Cosmetic coin drop rates will be inconsistent."); } StructuredLog.Install(); Bindings = new ConfigBindings(((BaseUnityPlugin)this).Config); GameConfig = Bindings.Snapshot(); WolfModeKey = Bindings.WolfModeKey; BeaconKey = Bindings.BeaconKey; LobbySettingsPanelKey = Bindings.LobbySettingsPanelKey; MeetingMapKey = Bindings.MeetingMapKey; MeetingChatLogKey = Bindings.MeetingChatLogKey; CorpseReportKey = Bindings.CorpseReportKey; ManualKey = Bindings.ManualKey; ResultReturnKey = Bindings.ResultReturnKey; WorldgenItemBindings.Install(((BaseUnityPlugin)this).Config, Bindings); WorldgenMapBinding.Install(((BaseUnityPlugin)this).Config, Bindings); LoadLanguageOverride(); WLog.Line("init", false, ("plugin", "Werewolf"), ("version", "1.0.0")); if (!GameRefs.ResolveAll()) { Logger.LogError((object)"Werewolf 1.0.0: 本体フィールド参照の解決に失敗したためMODを無効化しました(本体アップデートでフィールドが変わった可能性。修復手順: docs/steering/tech.md「本体アップデート対応プレイブック」)"); return; } if (!ApplyPatchesIsolated(new Harmony("minorunara.werewolf"))) { Logger.LogError((object)"Werewolf 1.0.0: Harmonyパッチ適用に失敗したためMODを無効化しました(本体アップデートでパッチ対象が変わった可能性。修復手順: docs/steering/tech.md「本体アップデート対応プレイブック」)"); return; } ((Component)this).gameObject.AddComponent(); Logger.LogInfo((object)("Werewolf 1.0.0 loaded! " + $"(werewolfCount={GameConfig.WerewolfCount}, " + $"blackCatChancePercent={GameConfig.BlackCatChancePercent}, " + $"bomberChancePercent={GameConfig.BomberChancePercent}, " + $"roundSeconds={GameConfig.RoundSeconds}, " + $"blackCatRevealDelaySec={GameConfig.BlackCatRevealDelaySec}, " + $"debugMode={GameConfig.DebugMode})")); } private void OnDestroy() { StructuredLog.FlushDeferredSecrets("plugin_destroy"); } private static bool ApplyPatchesIsolated(Harmony harmony) { List<(string, Exception)> list = new List<(string, Exception)>(); Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly); foreach (Type type in typesFromAssembly) { try { harmony.CreateClassProcessor(type).Patch(); } catch (Exception item) { list.Add((type.FullName, item)); } } if (list.Count == 0) { int num = 0; foreach (MethodBase patchedMethod in harmony.GetPatchedMethods()) { _ = patchedMethod; num++; } WLog.Line("harmony_patched", false, ("methods", num)); return true; } foreach (var item4 in list) { string item2 = item4.Item1; Exception item3 = item4.Item2; Exception ex = item3.InnerException ?? item3; WLog.Line("harmony_patch_failed", false, ("patchClass", item2), ("err", ex.Message)); Logger.LogError((object)$"Harmony patch failed: {item2}\n{item3}"); } harmony.UnpatchSelf(); WLog.Line("harmony_disabled", false, ("failedClasses", list.Count)); return false; } private void LoadLanguageOverride() { try { string text = Bindings?.Language?.Value; if (string.IsNullOrEmpty(text)) { return; } int num; switch (text) { case "ja": return; default: num = ((text == "en") ? 1 : 0); break; case "English": num = 1; break; } bool flag = (byte)num != 0; if (flag) { Texts.Current = Language.English; WLog.Line("lang_loaded", false, ("code", "en"), ("source", "builtin")); } string text2 = (flag ? "en" : text); string location = typeof(Plugin).Assembly.Location; string text3 = (string.IsNullOrEmpty(location) ? null : Path.GetDirectoryName(location)); if (string.IsNullOrEmpty(text3)) { if (!flag) { WLog.Line("lang_load_failed", false, ("code", text2), ("reason", "no_dll_dir")); } return; } string text4 = Path.Combine(text3, "Lang", text2 + ".txt"); if (!File.Exists(text4)) { if (!flag) { WLog.Line("lang_load_failed", false, ("code", text2), ("reason", "file_missing"), ("path", text4)); } } else { Dictionary dictionary = LangFile.Parse(File.ReadAllText(text4, Encoding.UTF8)); Texts.SetOverride(dictionary); WLog.Line("lang_loaded", false, ("code", text2), ("entries", dictionary.Count)); } } catch (Exception ex) { WLog.Line("lang_load_failed", false, ("reason", "exception"), ("detail", ex.GetType().Name)); } } } internal static class WorldgenItemBindings { private static ConfigFile _config; private static ConfigBindings _bindings; private static Dictionary> _entries; private static bool _initialized; private static bool _failed; private static bool _skipLogged; private static readonly Dictionary DefaultAmounts = new Dictionary { ["Item Cart Cannon"] = 1, ["Item Cart Medium"] = 2, ["Item Cart Small"] = 1, ["Item Drone Indestructible"] = 1, ["Item Drone Zero Gravity"] = 1, ["Item Grenade Explosive"] = 2, ["Item Grenade Shockwave"] = 2, ["Item Grenade Stun"] = 2, ["Item Gun Laser"] = 1, ["Item Gun Shockwave"] = 1, ["Item Gun Shotgun"] = 1, ["Item Gun Stun"] = 1, ["Item Gun Tranq"] = 1, ["Item Health Pack Large"] = 1, ["Item Health Pack Medium"] = 2, ["Item Health Pack Small"] = 3, ["Item Leaf Blower"] = 1, ["Item Melee Baseball Bat"] = 1, ["Item Melee Frying Pan"] = 1, ["Item Melee Inflatable Hammer"] = 0, ["Item Melee Stun Baton"] = 1, ["Item Melee Sword"] = 1, ["Item Mine Explosive"] = 2, ["Item Mine Shockwave"] = 2, ["Item Mine Stun"] = 2, ["Item Orb Zero Gravity"] = 1, ["Item Phase Bridge"] = 1, ["Item Staff Torque"] = 1, ["Item Staff Void"] = 1, ["Item Staff Zero Gravity"] = 1, ["Item Upgrade Map Player Count"] = 1, ["Item Vehicle Semiscooter"] = 1, ["Item Vehicle Semiscooter Small"] = 1, ["Item WalkieTalkieBox"] = 1 }; internal static void Install(ConfigFile config, ConfigBindings bindings) { _config = config; _bindings = bindings; } internal static void TryInitialize() { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown if (_initialized || _failed || _config == null) { return; } try { IReadOnlyDictionary itemMaxAmounts = GameItemDatabase.Instance.ItemMaxAmounts; if (itemMaxAmounts == null || itemMaxAmounts.Count == 0) { return; } Dictionary> dictionary = new Dictionary>(itemMaxAmounts.Count); foreach (KeyValuePair item in itemMaxAmounts) { try { int value; int num = (DefaultAmounts.TryGetValue(item.Key, out value) ? Math.Min(value, item.Value) : 0); dictionary[item.Key] = _config.Bind("10. Loadout Items", item.Key, num, new ConfigDescription($"Amount brought to the truck at werewolf session start (0 = none, max = {item.Value}) / " + $"人狼セッション開始時にトラックへ持ち込む個数(0 = 持ち込まない, 上限 = {item.Value})", (AcceptableValueBase)(object)new AcceptableValueRange(0, item.Value), Array.Empty())); } catch (Exception ex) { WLog.Line("worldgen_itembind_skip", false, ("item", item.Key), ("err", ex.Message)); } } _entries = dictionary; _initialized = true; if (_bindings != null) { _bindings.ItemsSpecProvider = ComposeSpec; } WLog.Line("worldgen_itembind_init", false, ("count", dictionary.Count)); } catch (Exception ex2) { _failed = true; WLog.Line("worldgen_itembind_error", false, ("err", ex2.Message)); } } internal static string ComposeSpec() { Dictionary> entries = _entries; if (entries == null) { return ""; } Dictionary dictionary = new Dictionary(entries.Count); foreach (KeyValuePair> item in entries) { dictionary[item.Key] = item.Value.Value; } List list = new List(); string result = WorldgenSpec.Encode(dictionary, list); if (list.Count > 0 && !_skipLogged) { _skipLogged = true; WLog.Line("worldgen_itemspec_skip", false, ("names", string.Join(" / ", list))); } return result; } } internal static class WorldgenMapBinding { private static ConfigFile _config; private static ConfigBindings _bindings; private static bool _initialized; private static bool _failed; internal static void Install(ConfigFile config, ConfigBindings bindings) { _config = config; _bindings = bindings; } internal static void TryInitialize() { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown if (_initialized || _failed || _config == null || _bindings == null) { return; } try { IReadOnlyList playableLevelNames = GameItemDatabase.Instance.PlayableLevelNames; if (playableLevelNames == null || playableLevelNames.Count == 0) { return; } List list = new List(playableLevelNames.Count + 1) { "" }; foreach (string item in playableLevelNames) { if (!list.Contains(item)) { list.Add(item); } } ConfigEntry startMapName = _bindings.StartMapName; string text = startMapName.Value ?? ""; _config.Remove(((ConfigEntryBase)startMapName).Definition); ConfigEntry val = _config.Bind("08. Stage", "StartMapName", "", new ConfigDescription("Map type name for the werewolf session. Empty = random (vanilla default selection) / 人狼セッションの開始マップ種別名。空 = ランダム(バニラ既定の選択)", (AcceptableValueBase)(object)new AcceptableValueList(list.ToArray()), Array.Empty())); val.Value = text; _bindings.StartMapName = val; _initialized = true; WLog.Line("worldgen_mapbind_init", false, ("levels", list.Count - 1), ("value", val.Value)); if (!string.Equals(val.Value, text, StringComparison.Ordinal)) { WLog.Line("worldgen_mapbind_fallback", false, ("from", text)); } } catch (Exception ex) { _failed = true; WLog.Line("worldgen_mapbind_error", false, ("err", ex.Message)); } } } } namespace Werewolf.UI { public static class AssetCatalog { private const string EmbeddedNamespace = "Werewolf.Assets"; private const string BundleFileName = "werewolf_assets"; private static readonly Dictionary _sprites = new Dictionary(); private static readonly Dictionary _graySprites = new Dictionary(); private static readonly Dictionary _textures = new Dictionary(); private static readonly Dictionary _prefabs = new Dictionary(); private static readonly Dictionary _clips = new Dictionary(); private static AssetBundle _bundle; private static bool _bundleAttempted; private static Sprite _bombIconPlaceholder; private static AudioClip _heartbeatPlaceholder; public static Sprite GetSprite(string key) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(key)) { return null; } if (_sprites.TryGetValue(key, out var value)) { return value; } Texture2D texture = GetTexture(key); Sprite val = null; if ((Object)(object)texture != (Object)null) { try { val = Sprite.Create(texture, new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { LogFallback(key, "sprite_create_failed", ex.GetType().Name); val = null; } } _sprites[key] = val; return val; } public static Sprite GetGraySprite(string key) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_00ad: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(key)) { return null; } if (_graySprites.TryGetValue(key, out var value)) { return value; } Sprite val = null; Texture2D texture = GetTexture(key); if ((Object)(object)texture != (Object)null) { try { Color[] pixels = texture.GetPixels(); for (int i = 0; i < pixels.Length; i++) { Color val2 = pixels[i]; float num = val2.r * 0.299f + val2.g * 0.587f + val2.b * 0.114f; pixels[i] = new Color(num, num, num, val2.a); } Texture2D val3 = new Texture2D(((Texture)texture).width, ((Texture)texture).height, (TextureFormat)4, false); val3.SetPixels(pixels); val3.Apply(false, false); val = Sprite.Create(val3, new Rect(0f, 0f, (float)((Texture)val3).width, (float)((Texture)val3).height), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { LogFallback(key, "gray_build_failed", ex.GetType().Name); val = null; } } _graySprites[key] = val; return val; } public static Texture2D GetTexture(string key) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown if (string.IsNullOrEmpty(key)) { return null; } if (_textures.TryGetValue(key, out var value)) { return value; } Texture2D val = null; try { byte[] array = LoadEmbeddedBytes(key + ".png"); if (array != null && array.Length != 0) { val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { LogFallback(key, "png_decode_failed"); Object.Destroy((Object)(object)val); val = null; } } else { LogFallback(key, "png_missing"); } } catch (Exception ex) { LogFallback(key, "png_exception", ex.GetType().Name); val = null; } _textures[key] = val; return val; } public static GameObject GetPrefab(string key) { if (string.IsNullOrEmpty(key)) { return null; } if (_prefabs.TryGetValue(key, out var value)) { return value; } GameObject val = null; AssetBundle val2 = TryLoadBundle(); if ((Object)(object)val2 != (Object)null) { try { val = val2.LoadAsset(key); if ((Object)(object)val == (Object)null) { LogFallback(key, "bundle_key_missing"); } } catch (Exception ex) { LogFallback(key, "bundle_load_exception", ex.GetType().Name); val = null; } } else { LogFallback(key, "bundle_missing"); } _prefabs[key] = val; return val; } public static AudioClip GetClip(string key) { if (string.IsNullOrEmpty(key)) { return null; } if (_clips.TryGetValue(key, out var value)) { return value; } AudioClip val = null; try { byte[] array = LoadEmbeddedBytes(key + ".wav"); if (array != null && array.Length != 0) { val = DecodePcm16Wav(key, array); } else { LogFallback(key, "wav_missing"); } } catch (Exception ex) { LogFallback(key, "wav_exception", ex.GetType().Name); val = null; } _clips[key] = val; return val; } public static Sprite GetBombIcon() { Sprite sprite = GetSprite("img_bomb"); if ((Object)(object)sprite != (Object)null) { return sprite; } return BombIconPlaceholder(); } public static AudioClip GetHeartbeatClip() { AudioClip clip = GetClip("sfx_heartbeat"); if ((Object)(object)clip != (Object)null) { return clip; } return HeartbeatPlaceholder(); } private static Sprite BombIconPlaceholder() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0206: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_bombIconPlaceholder != (Object)null) { return _bombIconPlaceholder; } Texture2D val = new Texture2D(128, 128, (TextureFormat)5, false); ((Texture)val).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[16384]; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(64f, 57.6f); float num = 40.96f; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(92.16f, 108.8f); float num2 = 12.8f; Color val4 = default(Color); for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { ((Color)(ref val4))..ctor(0f, 0f, 0f, 0f); float num3 = Vector2.Distance(new Vector2((float)j, (float)i), val2); if (num3 <= num) { ((Color)(ref val4))..ctor(0.08f, 0.08f, 0.08f, 1f); if (num3 > num - 3f) { ((Color)(ref val4))..ctor(0.02f, 0.02f, 0.02f, 1f); } } float num4 = (float)j - 80.64f; float num5 = (float)i - 92.16f; if (Mathf.Abs(num4 - num5) < 2f && (float)j >= 76.8f && (float)j <= 92.16f && (float)i >= 84.48f && (float)i <= 102.4f) { ((Color)(ref val4))..ctor(0.55f, 0.35f, 0.1f, 1f); } float num6 = Vector2.Distance(new Vector2((float)j, (float)i), val3); if (num6 <= num2) { float num7 = 1f - num6 / num2; ((Color)(ref val4))..ctor(1f, Mathf.Lerp(0.35f, 0.85f, num7), 0.1f, 1f); } array[i * 128 + j] = val4; } } val.SetPixels(array); val.Apply(); _bombIconPlaceholder = Sprite.Create(val, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f)); ((Object)_bombIconPlaceholder).name = "WW_BombIconPlaceholder"; return _bombIconPlaceholder; } private static AudioClip HeartbeatPlaceholder() { if ((Object)(object)_heartbeatPlaceholder != (Object)null) { return _heartbeatPlaceholder; } int total = 22050; float[] samples = new float[total]; AddPulse(0.02f, 0.75f); AddPulse(0.35f, 0.55f); AudioClip val = AudioClip.Create("WW_HeartbeatPlaceholder", total, 1, 22050, false); if ((Object)(object)val == (Object)null) { return null; } if (!val.SetData(samples, 0)) { return null; } _heartbeatPlaceholder = val; return val; void AddPulse(float startSec, float amp) { int num = (int)(startSec * 22050f); int num2 = 4410; for (int i = 0; i < num2 && num + i < total; i++) { float num3 = (float)i / 22050f; float num4 = Mathf.Exp(-8f * num3); float num5 = Mathf.Sin(MathF.PI * 120f * num3); samples[num + i] += amp * num4 * num5; } } } internal static void ResetForTests() { _sprites.Clear(); _graySprites.Clear(); _textures.Clear(); _prefabs.Clear(); _clips.Clear(); _bundle = null; _bundleAttempted = false; _bombIconPlaceholder = null; _heartbeatPlaceholder = null; } private static byte[] LoadEmbeddedBytes(string fileName) { Assembly assembly = typeof(AssetCatalog).Assembly; string name = "Werewolf.Assets." + fileName; using Stream stream = assembly.GetManifestResourceStream(name); if (stream == null) { return null; } byte[] array = new byte[stream.Length]; int i; int num; for (i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } if (i != array.Length) { byte[] array2 = new byte[i]; Buffer.BlockCopy(array, 0, array2, 0, i); return array2; } return array; } private static AssetBundle TryLoadBundle() { if ((Object)(object)_bundle != (Object)null) { return _bundle; } if (_bundleAttempted) { return null; } _bundleAttempted = true; try { string location = typeof(AssetCatalog).Assembly.Location; if (string.IsNullOrEmpty(location)) { return null; } string directoryName = Path.GetDirectoryName(location); if (string.IsNullOrEmpty(directoryName)) { return null; } string text = Path.Combine(directoryName, "werewolf_assets"); if (!File.Exists(text)) { return null; } _bundle = AssetBundle.LoadFromFile(text); return _bundle; } catch (Exception ex) { LogFallback("werewolf_assets", "bundle_exception", ex.GetType().Name); return null; } } private static AudioClip DecodePcm16Wav(string key, byte[] bytes) { if (bytes.Length < 44) { LogFallback(key, "wav_too_short"); return null; } if (bytes[0] != 82 || bytes[1] != 73 || bytes[2] != 70 || bytes[3] != 70 || bytes[8] != 87 || bytes[9] != 65 || bytes[10] != 86 || bytes[11] != 69) { LogFallback(key, "wav_not_riff"); return null; } int num = 12; short num2 = 0; int num3 = 0; short num4 = 0; short num5 = 0; byte[] array = null; while (num + 8 <= bytes.Length) { string text = new string(new char[4] { (char)bytes[num], (char)bytes[num + 1], (char)bytes[num + 2], (char)bytes[num + 3] }); int num6 = BitConverter.ToInt32(bytes, num + 4); int num7 = num + 8; if (num7 + num6 > bytes.Length) { LogFallback(key, "wav_chunk_overflow"); return null; } if (text == "fmt ") { if (num6 < 16) { LogFallback(key, "wav_fmt_short"); return null; } num5 = BitConverter.ToInt16(bytes, num7); num2 = BitConverter.ToInt16(bytes, num7 + 2); num3 = BitConverter.ToInt32(bytes, num7 + 4); num4 = BitConverter.ToInt16(bytes, num7 + 14); } else if (text == "data") { array = new byte[num6]; Buffer.BlockCopy(bytes, num7, array, 0, num6); } int num8 = 8 + num6 + (num6 & 1); num += num8; if (array != null && num2 > 0 && num3 > 0) { break; } } if (array == null || num2 <= 0 || num3 <= 0 || num4 != 16 || num5 != 1) { LogFallback(key, "wav_unsupported", "fmt=" + num5 + ",ch=" + num2 + ",sr=" + num3 + ",bps=" + num4); return null; } int num9 = array.Length / (2 * num2); if (num9 <= 0) { LogFallback(key, "wav_no_frames"); return null; } float[] array2 = new float[num9 * num2]; for (int i = 0; i < array2.Length; i++) { short num10 = (short)(array[i * 2] | (array[i * 2 + 1] << 8)); array2[i] = (float)num10 * 3.0517578E-05f; } AudioClip val = AudioClip.Create(key, num9, (int)num2, num3, false); if ((Object)(object)val == (Object)null) { LogFallback(key, "wav_clip_create_failed"); return null; } if (!val.SetData(array2, 0)) { LogFallback(key, "wav_clip_setdata_failed"); return null; } return val; } private static void LogFallback(string key, string reason, string detail = null) { try { if (detail == null) { WLog.Line("asset_fallback", false, ("key", key), ("reason", reason)); } else { WLog.Line("asset_fallback", false, ("key", key), ("reason", reason), ("detail", detail)); } } catch { } } } public sealed class BlackCatAwakeningToast : IClientPanel { private const float CardWidth = 760f; private const float CardHeight = 170f; private const float TopMargin = 150f; private const float TitleFontSize = 40f; private const float BodyFontSize = 22f; private static readonly Vector2 IconSize = new Vector2(120f, 120f); private const float IconLeftMargin = 24f; private const float TextColumnGap = 24f; private const float TextRightMargin = 16f; private const float SlideInSec = 0.4f; private const float HoldSec = 5f; private const float SlideOutSec = 0.4f; private GameObject _root; private CanvasGroup _group; private RectTransform _cardRect; private Image _iconImage; private TextMeshProUGUI _titleText; private TextMeshProUGUI _bodyText; private float _shownY; private float _hiddenY; public string LayerName => "BlackCatAwakening"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Expected O, but got Unknown //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Expected O, but got Unknown //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Expected O, but got Unknown if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_BlackCatAwakeningToast", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _group = val.AddComponent(); _group.alpha = 1f; _group.blocksRaycasts = false; _group.interactable = false; GameObject val3 = new GameObject("Card", new Type[1] { typeof(RectTransform) }); _cardRect = (RectTransform)val3.transform; ((Transform)_cardRect).SetParent((Transform)(object)val2, false); _cardRect.anchorMin = new Vector2(0.5f, 1f); _cardRect.anchorMax = new Vector2(0.5f, 1f); _cardRect.pivot = new Vector2(0.5f, 1f); _cardRect.sizeDelta = new Vector2(760f, 170f); _shownY = -150f; _hiddenY = 210f; _cardRect.anchoredPosition = new Vector2(0f, _hiddenY); float num = -356f + IconSize.x / 2f; float num2 = 736f - IconSize.x - 24f - 16f; float num3 = 364f - num2 / 2f; _iconImage = UiKit.CreateImage((Transform)(object)_cardRect, "Icon", new Vector2(num, (0f - (170f - IconSize.y)) / 2f), IconSize, Color.white); ((Graphic)_iconImage).raycastTarget = false; ((Behaviour)_iconImage).enabled = false; AnchorToCardTop((RectTransform)((Component)_iconImage).transform); _titleText = UiKit.CreateText((Transform)(object)_cardRect, "Title", new Vector2(num3, -16f), new Vector2(num2, 44f), "", 40f, new Color(1f, 0.95f, 0.7f), (TextAlignmentOptions)4097); ((TMP_Text)_titleText).enableWordWrapping = false; AnchorToCardTop((RectTransform)((TMP_Text)_titleText).transform); _bodyText = UiKit.CreateText((Transform)(object)_cardRect, "Body", new Vector2(num3, -72f), new Vector2(num2, 90f), "", 22f, new Color(1f, 1f, 1f, 0.95f), (TextAlignmentOptions)257); ((TMP_Text)_bodyText).enableWordWrapping = true; AnchorToCardTop((RectTransform)((TMP_Text)_bodyText).transform); _root.SetActive(false); WLog.Line("cat_awaken_toast_built", false); } } public IEnumerator Play(RevealContent content) { if ((Object)(object)_root == (Object)null || content == null) { WLog.Line("cat_awaken_toast_skip", false, ("reason", ((Object)(object)_root == (Object)null) ? "not_built" : "no_content")); yield break; } ((TMP_Text)_titleText).text = content.Title ?? string.Empty; ((TMP_Text)_bodyText).text = ((content.Pages != null && content.Pages.Length != 0 && content.Pages[0].BodyLines != null) ? string.Join("\n", content.Pages[0].BodyLines) : string.Empty); Sprite sprite = AssetCatalog.GetSprite("role_blackcat"); if ((Object)(object)sprite != (Object)null) { _iconImage.sprite = sprite; _iconImage.preserveAspect = true; ((Graphic)_iconImage).color = Color.white; ((Behaviour)_iconImage).enabled = true; } else { _iconImage.sprite = null; ((Behaviour)_iconImage).enabled = false; } _group.alpha = 1f; _cardRect.anchoredPosition = new Vector2(0f, _hiddenY); _root.SetActive(true); WLog.Line("cat_awaken_toast_show", true); yield return UiTween.Move(_cardRect, new Vector2(0f, _hiddenY), new Vector2(0f, _shownY), 0.4f, UiTween.EaseOut()); yield return UiTween.Hold(5f); yield return UiTween.Move(_cardRect, new Vector2(0f, _shownY), new Vector2(0f, _hiddenY), 0.4f, UiTween.EaseIn()); HideNow(); WLog.Line("cat_awaken_toast_end", false); } private static void AnchorToCardTop(RectTransform rect) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0.5f, 1f); rect.anchorMax = val; rect.anchorMin = val; rect.pivot = new Vector2(0.5f, 1f); } public void HideNow() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null)) { if ((Object)(object)_cardRect != (Object)null) { _cardRect.anchoredPosition = new Vector2(0f, _hiddenY); } _root.SetActive(false); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _group = null; _cardRect = null; _iconImage = null; _titleText = null; _bodyText = null; } } public sealed class BomberStatusPanel : IClientPanel { private sealed class Slot { public GameObject Root; public Image Icon; public Sprite ColorSprite; public Sprite GraySprite; public Image CooldownGrayOverlay; public TextMeshProUGUI Fallback; public TextMeshProUGUI Cooldown; public TextMeshProUGUI Ammo; public TextMeshProUGUI KeyLabel; } private const float LeftMargin = 48f; private const float BottomMargin = 150f; private const float SlotSize = 112f; private const float SlotGap = 22f; private const float LabelHeight = 34f; private const float LabelGap = 4f; private const float LabelFontSize = 26f; private const float CooldownFontSize = 44f; private const float AmmoFontSize = 34f; private const string PlantSpriteKey = "perk_bomb_plant"; private const string DetonateSpriteKey = "perk_bomb_detonate"; private static readonly Color LockedIconTint = new Color(0.45f, 0.45f, 0.45f, 0.8f); private static readonly Color ReadyIconTint = new Color(1f, 1f, 1f, 1f); private static readonly Color LockedLabelColor = new Color(0.55f, 0.55f, 0.55f, 0.9f); private static readonly Color ReadyLabelColor = new Color(1f, 0.95f, 0.85f, 0.95f); private static readonly Color AmmoTextColor = new Color(0.35f, 0.95f, 0.35f, 1f); private static readonly Color CooldownGrayOverlayTint = new Color(0.25f, 0.25f, 0.25f, 0.82f); private GameObject _root; private Slot _plantSlot; private Slot _detonateSlot; private int _lastPlantBrightQ = -1; private int _lastDetonateBrightQ = -1; private int _lastPlantCd = -1; private int _lastDetonateCd = -1; private byte _lastAmmo = byte.MaxValue; private string _lastPlantKeyName; private string _lastDetonateKeyName; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } float num = 246f; float num2 = 150f; GameObject val = new GameObject("WW_BomberStatusPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 0f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(0f, 0f); val2.anchoredPosition = new Vector2(48f, 150f); val2.sizeDelta = new Vector2(num, num2); _root = val; float num3 = 94f; float num4 = 56f; _plantSlot = BuildSlot(val2, "Plant", new Vector2(num4, num3), "perk_bomb_plant", includeAmmo: true); AddProgressOverlay(_plantSlot, "PlantProgressGray"); float num5 = 190f; _detonateSlot = BuildSlot(val2, "Detonate", new Vector2(num5, num3), "perk_bomb_detonate", includeAmmo: false); AddProgressOverlay(_detonateSlot, "DetonateCooldownGray"); _root.SetActive(false); } } public void Tick(bool visible, float plantFraction, int plantCooldownSec, byte ammo, float detonateBrightFraction, int detonateCooldownSec, string plantKeyName, string detonateKeyName) { if ((Object)(object)_root == (Object)null) { return; } try { if (_root.activeSelf != visible) { _root.SetActive(visible); } if (!visible) { _lastPlantKeyName = null; _lastDetonateKeyName = null; return; } int num = Mathf.Clamp(Mathf.RoundToInt(plantFraction * 100f), 0, 100); if (num != _lastPlantBrightQ || plantCooldownSec != _lastPlantCd || ammo != _lastAmmo || plantKeyName != _lastPlantKeyName) { ApplySlot(_plantSlot, (float)num / 100f, plantCooldownSec, ammo, Texts.Format(TextId.HudBomberPlantKeyFormat, plantKeyName ?? "?")); _lastPlantBrightQ = num; _lastPlantCd = plantCooldownSec; _lastAmmo = ammo; _lastPlantKeyName = plantKeyName; } int num2 = Mathf.Clamp(Mathf.RoundToInt(detonateBrightFraction * 100f), 0, 100); if (num2 != _lastDetonateBrightQ || detonateCooldownSec != _lastDetonateCd || detonateKeyName != _lastDetonateKeyName) { ApplySlot(_detonateSlot, (float)num2 / 100f, detonateCooldownSec, 0, Texts.Format(TextId.HudBomberDetonateKeyFormat, detonateKeyName ?? "?")); _lastDetonateBrightQ = num2; _lastDetonateCd = detonateCooldownSec; _lastDetonateKeyName = detonateKeyName; } } catch (Exception ex) { WLog.Line("bomber_hud_tick_error", false, ("err", ex.Message)); } } public void Hide() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } _lastPlantKeyName = null; _lastDetonateKeyName = null; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _plantSlot = null; _detonateSlot = null; _lastPlantKeyName = null; _lastDetonateKeyName = null; } private static void ApplySlot(Slot slot, float brightness, int cooldownSec, byte ammo, string keyLabel) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) if (slot == null) { return; } float num = Mathf.Clamp01(brightness); Color color = Color.Lerp(LockedIconTint, ReadyIconTint, num); Color color2 = Color.Lerp(LockedLabelColor, ReadyLabelColor, num); if ((Object)(object)slot.Icon != (Object)null && ((Behaviour)slot.Icon).enabled) { ((Graphic)slot.Icon).color = color; if ((Object)(object)slot.CooldownGrayOverlay == (Object)null) { Sprite val = ((num >= 0.999f) ? slot.ColorSprite : (slot.GraySprite ?? slot.ColorSprite)); if ((Object)(object)val != (Object)null && (Object)(object)slot.Icon.sprite != (Object)(object)val) { slot.Icon.sprite = val; } } } if ((Object)(object)slot.Fallback != (Object)null) { ((Graphic)slot.Fallback).color = color; } if ((Object)(object)slot.CooldownGrayOverlay != (Object)null) { ((Graphic)slot.Icon).color = ReadyIconTint; slot.CooldownGrayOverlay.fillAmount = 1f - num; } if ((Object)(object)slot.Cooldown != (Object)null) { ((TMP_Text)slot.Cooldown).text = ((cooldownSec > 0) ? cooldownSec.ToString() : string.Empty); } if ((Object)(object)slot.Ammo != (Object)null) { ((TMP_Text)slot.Ammo).text = ammo.ToString(); } if ((Object)(object)slot.KeyLabel != (Object)null) { ((TMP_Text)slot.KeyLabel).text = keyLabel; ((Graphic)slot.KeyLabel).color = color2; } } private static Slot BuildSlot(RectTransform parent, string name, Vector2 centerPos, string spriteKey, bool includeAmmo) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) Slot slot = new Slot(); RectTransform val = UiKit.CreateRect((Transform)(object)parent, name + "Slot", Vector2.zero, new Vector2(112f, 112f)); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0f, 0f); val.anchorMax = val2; val.anchorMin = val2; val.pivot = new Vector2(0.5f, 0.5f); val.anchoredPosition = centerPos; slot.Root = ((Component)val).gameObject; slot.Icon = UiKit.CreateImage((Transform)(object)val, name + "Icon", Vector2.zero, new Vector2(112f, 112f), LockedIconTint); Sprite val3 = AssetCatalog.GetSprite(spriteKey); Sprite val4 = (((Object)(object)val3 != (Object)null) ? AssetCatalog.GetGraySprite(spriteKey) : null); if ((Object)(object)val3 == (Object)null) { val3 = AssetCatalog.GetBombIcon(); } if ((Object)(object)val3 != (Object)null) { slot.ColorSprite = val3; slot.GraySprite = val4; slot.Icon.sprite = val4 ?? val3; slot.Icon.preserveAspect = true; } else { ((Behaviour)slot.Icon).enabled = false; slot.Fallback = UiKit.CreateText((Transform)(object)val, name + "Fallback", Vector2.zero, new Vector2(112f, 112f), Texts.Get(TextId.RoleNameBomber), 20f, LockedIconTint, (TextAlignmentOptions)514); } slot.Cooldown = UiKit.CreateText((Transform)(object)val, name + "Cooldown", Vector2.zero, new Vector2(112f, 112f), string.Empty, 44f, Color.white, (TextAlignmentOptions)514); ((TMP_Text)slot.Cooldown).outlineWidth = 0.25f; ((TMP_Text)slot.Cooldown).outlineColor = Color32.op_Implicit(Color.black); if (includeAmmo) { slot.Ammo = UiKit.CreateText((Transform)(object)val, name + "Ammo", new Vector2(0f, -28f), new Vector2(112f, 56f), string.Empty, 34f, AmmoTextColor, (TextAlignmentOptions)514); ((TMP_Text)slot.Ammo).outlineWidth = 0.25f; ((TMP_Text)slot.Ammo).outlineColor = Color32.op_Implicit(Color.black); } slot.KeyLabel = UiKit.CreateText((Transform)(object)parent, name + "KeyLabel", new Vector2(centerPos.x, 17f), new Vector2(220f, 34f), string.Empty, 26f, ReadyLabelColor, (TextAlignmentOptions)514); RectTransform rectTransform = ((TMP_Text)slot.KeyLabel).rectTransform; ((Vector2)(ref val2))..ctor(0f, 0f); rectTransform.anchorMax = val2; rectTransform.anchorMin = val2; rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = new Vector2(centerPos.x, 17f); rectTransform.sizeDelta = new Vector2(220f, 34f); return slot; } private static void AddProgressOverlay(Slot slot, string name) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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) if (slot != null && !((Object)(object)slot.Icon == (Object)null) && ((Behaviour)slot.Icon).enabled && !((Object)(object)slot.Icon.sprite == (Object)null)) { slot.Icon.preserveAspect = false; if ((Object)(object)slot.ColorSprite != (Object)null) { slot.Icon.sprite = slot.ColorSprite; } slot.CooldownGrayOverlay = UiKit.CreateImage(((Transform)((Graphic)slot.Icon).rectTransform).parent, name, Vector2.zero, new Vector2(112f, 112f), CooldownGrayOverlayTint); slot.CooldownGrayOverlay.sprite = slot.GraySprite ?? slot.ColorSprite; slot.CooldownGrayOverlay.type = (Type)3; slot.CooldownGrayOverlay.fillMethod = (FillMethod)1; slot.CooldownGrayOverlay.fillOrigin = 1; slot.CooldownGrayOverlay.fillAmount = 1f; ((Component)slot.CooldownGrayOverlay).transform.SetSiblingIndex(((Component)slot.Icon).transform.GetSiblingIndex() + 1); } } } public sealed class BombIconPresenter : IClientPanel { private const float RadialSize = 96f; private const float RadialOutlineSize = 108f; private const float BombIconSize = 144f; private const float HeadOffsetY = 1.7f; private const float BombIconVerticalOffsetPx = -52.8f; private const float VisionMaxDistance = 15f; private const float VisionProbeHeight = 1.2f; private const float PlantFlightSeconds = 3f; private const float PlantArcHeightPx = 260f; private const float PlantThrowOriginScreenX = 0.56f; private const float PlantThrowOriginScreenY = 0.12f; private static readonly Color RadialChargingColor = new Color(1f, 0.85f, 0.15f, 0.65f); private static readonly Color RadialFullColor = new Color(0.35f, 0.95f, 0.35f, 0.65f); private static readonly Color RadialOutlineColor = new Color(1f, 1f, 1f, 0.95f); private GameObject _root; private readonly Dictionary _radialOutlines = new Dictionary(); private readonly Dictionary _radials = new Dictionary(); private readonly Dictionary _bombIcons = new Dictionary(); private Image _plantFlightIcon; private int _plantFlightTarget = -1; private float _plantFlightStartedAt; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_BombIconPresenter", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _root.SetActive(false); } } public void Tick(bool visible, IReadOnlyDictionary radialTargets, IReadOnlyCollection bombIconActors, Func resolveWorldPos) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { if (_root.activeSelf != visible) { _root.SetActive(visible); } if (!visible) { HideAll(); return; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { HideAll(); return; } if (radialTargets != null) { foreach (KeyValuePair radialTarget in radialTargets) { Vector3? anchorPos = resolveWorldPos?.Invoke(radialTarget.Key); Image val = EnsureRadial(radialTarget.Key); if (anchorPos.HasValue && !BodyVisibleFromCamera(anchorPos.Value, main)) { ((Behaviour)val).enabled = false; } else { PlaceOrHide(val, anchorPos, main); } Image value; if (((Behaviour)val).enabled) { float num = (val.fillAmount = Mathf.Clamp01(radialTarget.Value)); ((Graphic)val).color = ((num >= 1f) ? RadialFullColor : RadialChargingColor); Image obj = EnsureRadialOutline(radialTarget.Key, val); ((Graphic)obj).rectTransform.anchoredPosition = ((Graphic)val).rectTransform.anchoredPosition; obj.fillAmount = num; ((Behaviour)obj).enabled = true; } else if (_radialOutlines.TryGetValue(radialTarget.Key, out value) && (Object)(object)value != (Object)null) { ((Behaviour)value).enabled = false; } } List list = new List(); foreach (int key in _radials.Keys) { if (!radialTargets.ContainsKey(key)) { list.Add(key); } } foreach (int item in list) { if ((Object)(object)_radials[item] != (Object)null) { ((Behaviour)_radials[item]).enabled = false; } if (_radialOutlines.TryGetValue(item, out var value2) && (Object)(object)value2 != (Object)null) { ((Behaviour)value2).enabled = false; } } } else { foreach (Image value3 in _radials.Values) { if ((Object)(object)value3 != (Object)null) { ((Behaviour)value3).enabled = false; } } foreach (Image value4 in _radialOutlines.Values) { if ((Object)(object)value4 != (Object)null) { ((Behaviour)value4).enabled = false; } } } if (bombIconActors != null && bombIconActors.Count > 0) { foreach (int bombIconActor in bombIconActors) { Vector3? anchorPos2 = resolveWorldPos?.Invoke(bombIconActor); Image val2 = EnsureBombIcon(bombIconActor); if (bombIconActor == _plantFlightTarget) { ((Behaviour)val2).enabled = false; } else if (anchorPos2.HasValue && !BodyVisibleFromCamera(anchorPos2.Value, main)) { ((Behaviour)val2).enabled = false; } else { PlaceOrHide(val2, anchorPos2, main, -52.8f); } } List list2 = new List(); foreach (int key2 in _bombIcons.Keys) { if (!ContainsActor(bombIconActors, key2)) { list2.Add(key2); } } foreach (int item2 in list2) { if ((Object)(object)_bombIcons[item2] != (Object)null) { ((Behaviour)_bombIcons[item2]).enabled = false; } } } else { foreach (Image value5 in _bombIcons.Values) { if ((Object)(object)value5 != (Object)null) { ((Behaviour)value5).enabled = false; } } } TickPlantFlight(main, resolveWorldPos); } catch (Exception ex) { WLog.Line("bomb_icon_tick_error", false, ("err", ex.Message)); } } public void Hide() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } EndPlantFlight(); HideAll(); } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _radials.Clear(); _radialOutlines.Clear(); _bombIcons.Clear(); _plantFlightIcon = null; _plantFlightTarget = -1; } public void BeginPlantFlight(int targetActor) { if (targetActor != -1) { _plantFlightTarget = targetActor; _plantFlightStartedAt = Time.unscaledTime; if ((Object)(object)_plantFlightIcon != (Object)null) { ((Behaviour)_plantFlightIcon).enabled = true; } } } private static bool ContainsActor(IReadOnlyCollection set, int actor) { foreach (int item in set) { if (item == actor) { return true; } } return false; } private static bool BodyVisibleFromCamera(Vector3 bodyPos, Camera cam) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: 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) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = bodyPos + Vector3.up * 1.2f; Vector3 val2 = ((Component)cam).transform.position - val; float magnitude = ((Vector3)(ref val2)).magnitude; if (magnitude > 15f) { return false; } int num = LayerMask.op_Implicit(SemiFunc.LayerMaskGetVisionObstruct()) & ~LayerMask.GetMask(new string[1] { "Player" }); return !Physics.Raycast(val, val2, magnitude, num, (QueryTriggerInteraction)2); } private Image EnsureRadial(int actor) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (_radials.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null) { if (!((Behaviour)value).enabled) { ((Behaviour)value).enabled = true; } return value; } Image val = UiKit.CreateRadialImage(_root.transform, "Radial_" + actor, Vector2.zero, new Vector2(96f, 96f), RadialChargingColor); _radials[actor] = val; return val; } private Image EnsureRadialOutline(int actor, Image coloredRadial) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (_radialOutlines.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null) { PlaceOutlineDirectlyBehind(value, coloredRadial); return value; } Image val = UiKit.CreateRadialImage(_root.transform, "RadialOutline_" + actor, Vector2.zero, new Vector2(108f, 108f), RadialOutlineColor); PlaceOutlineDirectlyBehind(val, coloredRadial); _radialOutlines[actor] = val; return val; } private static void PlaceOutlineDirectlyBehind(Image outline, Image coloredRadial) { if (!((Object)(object)outline == (Object)null) && !((Object)(object)coloredRadial == (Object)null)) { int siblingIndex = ((Component)outline).transform.GetSiblingIndex(); int siblingIndex2 = ((Component)coloredRadial).transform.GetSiblingIndex(); if (siblingIndex > siblingIndex2) { ((Component)outline).transform.SetSiblingIndex(siblingIndex2); } else if (siblingIndex < siblingIndex2 - 1) { ((Component)outline).transform.SetSiblingIndex(siblingIndex2 - 1); } } } private Image EnsureBombIcon(int actor) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (_bombIcons.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null) { if (!((Behaviour)value).enabled) { ((Behaviour)value).enabled = true; } return value; } Image val = UiKit.CreateImage(_root.transform, "BombIcon_" + actor, Vector2.zero, new Vector2(144f, 144f), Color.white); Sprite bombIcon = AssetCatalog.GetBombIcon(); if ((Object)(object)bombIcon != (Object)null) { val.sprite = bombIcon; val.preserveAspect = true; } _bombIcons[actor] = val; return val; } private Image EnsurePlantFlightIcon() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_plantFlightIcon != (Object)null) { return _plantFlightIcon; } _plantFlightIcon = UiKit.CreateImage(_root.transform, "BombPlantFlight", Vector2.zero, new Vector2(144f, 144f), Color.white); Sprite bombIcon = AssetCatalog.GetBombIcon(); if ((Object)(object)bombIcon != (Object)null) { _plantFlightIcon.sprite = bombIcon; _plantFlightIcon.preserveAspect = true; } return _plantFlightIcon; } private void TickPlantFlight(Camera cam, Func resolveWorldPos) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (_plantFlightTarget == -1) { if ((Object)(object)_plantFlightIcon != (Object)null) { ((Behaviour)_plantFlightIcon).enabled = false; } return; } float num = Mathf.Clamp01((Time.unscaledTime - _plantFlightStartedAt) / 3f); Vector3? val = resolveWorldPos?.Invoke(_plantFlightTarget); if (!val.HasValue) { EndPlantFlight(); return; } Vector2 val2 = ViewportToUi(new Vector2(0.56f, 0.12f)); Vector3 val3 = cam.WorldToViewportPoint(val.Value + Vector3.up * 1.7f); if (val3.z <= 0f) { EndPlantFlight(); return; } Vector2 anchoredPosition = Vector2.Lerp(val2, ViewportToUi(Vector2.op_Implicit(val3)), num); anchoredPosition.y += -52.8f * num; anchoredPosition.y += 1040f * num * (1f - num); Image obj = EnsurePlantFlightIcon(); ((Behaviour)obj).enabled = true; ((Graphic)obj).rectTransform.anchoredPosition = anchoredPosition; ((Transform)((Graphic)obj).rectTransform).localRotation = Quaternion.Euler(0f, 0f, -360f * num); if (num >= 1f) { EndPlantFlight(); } } private void EndPlantFlight() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) _plantFlightTarget = -1; if ((Object)(object)_plantFlightIcon != (Object)null) { ((Behaviour)_plantFlightIcon).enabled = false; ((Transform)((Graphic)_plantFlightIcon).rectTransform).localRotation = Quaternion.identity; } } private void PlaceOrHide(Image img, Vector3? anchorPos, Camera cam, float verticalOffsetPx = 0f) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)img == (Object)null) { return; } if (!anchorPos.HasValue) { ((Behaviour)img).enabled = false; return; } Vector3 val = anchorPos.Value + Vector3.up * 1.7f; Vector3 val2 = cam.WorldToViewportPoint(val); if (val2.z <= 0f || val2.x < 0f || val2.x > 1f || val2.y < 0f || val2.y > 1f) { ((Behaviour)img).enabled = false; return; } if (!((Behaviour)img).enabled) { ((Behaviour)img).enabled = true; } Vector2 anchoredPosition = ViewportToUi(Vector2.op_Implicit(val2)); anchoredPosition.y += verticalOffsetPx; ((Graphic)img).rectTransform.anchoredPosition = anchoredPosition; } private Vector2 ViewportToUi(Vector2 viewport) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) Transform transform = _root.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); float num = viewport.x - 0.5f; Rect rect = val.rect; float num2 = num * ((Rect)(ref rect)).width; float num3 = viewport.y - 0.5f; rect = val.rect; return new Vector2(num2, num3 * ((Rect)(ref rect)).height); } private void HideAll() { foreach (Image value in _radials.Values) { if ((Object)(object)value != (Object)null) { ((Behaviour)value).enabled = false; } } foreach (Image value2 in _radialOutlines.Values) { if ((Object)(object)value2 != (Object)null) { ((Behaviour)value2).enabled = false; } } foreach (Image value3 in _bombIcons.Values) { if ((Object)(object)value3 != (Object)null) { ((Behaviour)value3).enabled = false; } } if ((Object)(object)_plantFlightIcon != (Object)null) { ((Behaviour)_plantFlightIcon).enabled = false; } } } public sealed class BombWarningPresenter : IClientPanel { private const float CenterImageSize = 512f; private const float PulseSpeed = 6f; private GameObject _root; private Image _centerImage; private AudioSource _selfAudio; private GameObject _spatialAudioGo; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_BombWarningPresenter", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _centerImage = UiKit.CreateImage((Transform)(object)val2, "Center", Vector2.zero, new Vector2(512f, 512f), new Color(1f, 0.15f, 0.05f, 0.85f)); Sprite bombIcon = AssetCatalog.GetBombIcon(); if ((Object)(object)bombIcon != (Object)null) { _centerImage.sprite = bombIcon; _centerImage.preserveAspect = true; } ((Behaviour)_centerImage).enabled = false; _selfAudio = _root.AddComponent(); _selfAudio.spatialBlend = 0f; _selfAudio.loop = true; _selfAudio.playOnAwake = false; _root.SetActive(false); } } public void Show(bool localIsTarget, PlayerAvatar targetAvatar, float heartbeatRadiusMeters, Vector3? spatialFallbackPos = null) { if ((Object)(object)_root == (Object)null) { return; } try { if (!_root.activeSelf) { _root.SetActive(true); } AudioClip heartbeatClip = AssetCatalog.GetHeartbeatClip(); if (localIsTarget) { if ((Object)(object)_centerImage != (Object)null) { ((Behaviour)_centerImage).enabled = true; } if ((Object)(object)_selfAudio != (Object)null && (Object)(object)heartbeatClip != (Object)null) { _selfAudio.clip = heartbeatClip; _selfAudio.volume = 1f; if (!_selfAudio.isPlaying) { _selfAudio.Play(); } } } else { if ((Object)(object)_centerImage != (Object)null) { ((Behaviour)_centerImage).enabled = false; } SpawnSpatial(targetAvatar, spatialFallbackPos, heartbeatClip, heartbeatRadiusMeters); } } catch (Exception ex) { WLog.Line("bomb_warning_show_error", false, ("err", ex.Message)); } } public void Tick() { //IL_0052: 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_0066: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !((Object)(object)_centerImage == (Object)null) && ((Behaviour)_centerImage).enabled) { float a = 0.55f + 0.45f * Mathf.Abs(Mathf.Sin(Time.unscaledTime * 6f)); Color color = ((Graphic)_centerImage).color; color.a = a; ((Graphic)_centerImage).color = color; } } public void Hide() { try { if ((Object)(object)_centerImage != (Object)null) { ((Behaviour)_centerImage).enabled = false; } if ((Object)(object)_selfAudio != (Object)null && _selfAudio.isPlaying) { _selfAudio.Stop(); } if ((Object)(object)_spatialAudioGo != (Object)null) { Object.Destroy((Object)(object)_spatialAudioGo); _spatialAudioGo = null; } if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } } catch (Exception ex) { WLog.Line("bomb_warning_hide_error", false, ("err", ex.Message)); } } public void Destroy() { Hide(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _centerImage = null; _selfAudio = null; } private void SpawnSpatial(PlayerAvatar targetAvatar, Vector3? fallbackPos, AudioClip clip, float maxDistance) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) Vector3 position; if ((Object)(object)targetAvatar != (Object)null && (Object)(object)((Component)targetAvatar).transform != (Object)null) { position = ((Component)targetAvatar).transform.position; } else { if (!fallbackPos.HasValue) { return; } position = fallbackPos.Value; } if (!((Object)(object)clip == (Object)null)) { _spatialAudioGo = new GameObject("WW_BombHeartbeat3D"); _spatialAudioGo.transform.position = position; AudioSource obj = _spatialAudioGo.AddComponent(); obj.clip = clip; obj.spatialBlend = 1f; obj.rolloffMode = (AudioRolloffMode)1; obj.maxDistance = ((maxDistance > 0f) ? maxDistance : 2f); obj.minDistance = 0.5f; obj.loop = true; obj.playOnAwake = false; obj.Play(); } } } internal readonly struct ButtonPalette { public Color EnabledBg { get; } public Color HoverBg { get; } public Color DisabledBg { get; } public Color DisabledLabel { get; } public ButtonPalette(Color enabledBg, Color hoverBg, Color disabledBg, Color disabledLabel) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) EnabledBg = enabledBg; HoverBg = hoverBg; DisabledBg = disabledBg; DisabledLabel = disabledLabel; } } internal static class ButtonVisual { public static readonly Color ArmedBg = new Color(0.78f, 0.55f, 0.08f, 0.95f); public static readonly Color ArmedHoverBg = new Color(0.92f, 0.7f, 0.18f, 1f); public static void Resolve(ButtonPalette palette, bool armed, bool hover, bool selected, bool enabled, out Color bg, out Color label) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_0099: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (armed) { bg = (hover ? ArmedHoverBg : ArmedBg); label = Color.white; } else if (hover && enabled) { bg = palette.HoverBg; label = Color.white; } else if (selected) { bg = ArmedBg; label = Color.white; } else if (enabled) { bg = palette.EnabledBg; label = Color.white; } else { bg = palette.DisabledBg; label = palette.DisabledLabel; } } } internal sealed class ChatRowFactory { private struct PendingAvatar { public MenuPlayerListed Listed; public int WaitedFrames; } public const float SpeakerRowHeight = 40f; public const float SpeakerRowWidth = 210f; public const float VoteRowHeight = 36f; private const float AvatarPrefabScale = 1.35f; private const float AvatarOriginTrim = 2.2f; private const float VoteAvatarScale = 0.95f; private const float VoteAvatarWidth = 168f; private const float VoteTextWidth = 122f; private const float VoteTextFontSize = 17f; private const float BubbleWidthRatio = 0.86f; private const float BubblePadding = 9f; private const float BodyFontSize = 20f; private const float NameFontSize = 19f; private const int AvatarFreezeWaitFrames = 120; private static readonly Color AliveBubbleColor = new Color(0.94f, 0.94f, 0.96f, 0.96f); private static readonly Color AliveBodyColor = new Color(0.08f, 0.08f, 0.12f, 1f); private static readonly Color SelfBubbleColor = new Color(0.66f, 0.88f, 0.42f, 0.96f); private static readonly Color SelfBodyColor = new Color(0.06f, 0.13f, 0.03f, 1f); private static readonly Color DeadBubbleColor = new Color(0.34f, 0.35f, 0.4f, 0.9f); private static readonly Color DeadBodyColor = new Color(0.9f, 0.91f, 0.95f, 1f); private static readonly Color VoteTextColor = new Color(0.62f, 0.9f, 0.62f, 0.95f); private static readonly Color NameTextColor = new Color(0.86f, 0.88f, 0.94f, 1f); private readonly float _contentWidth; private readonly float _sidePadding; private RectTransform _content; private TextMeshProUGUI _measure; private bool _measureErrorLogged; private int _localActor; private Func _resolveAvatar; private readonly List _pendingAvatars = new List(); private float MaxBubbleWidth => (_contentWidth - _sidePadding * 2f) * 0.86f; private float MaxTextWidth => MaxBubbleWidth - 18f; public ChatRowFactory(float contentWidth, float sidePadding) { _contentWidth = contentWidth; _sidePadding = sidePadding; } public void Build(Transform measureParent) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) _measure = UiKit.CreateText(measureParent, "Measure", Vector2.zero, Vector2.zero, string.Empty, 20f, new Color(0f, 0f, 0f, 0f), (TextAlignmentOptions)257); ((TMP_Text)_measure).enableWordWrapping = true; } public void Attach(RectTransform content) { _content = content; } public void SetContext(int localActor, Func resolveAvatar) { _localActor = localActor; _resolveAvatar = resolveAvatar; } public GameObject CreateRow(float topY, ChatLayoutBlock block, ChatLogEntry entry) { if (block.Kind == ChatBlockKind.Vote) { return CreateVoteRow(topY, block, entry); } bool isSelf = entry.Actor == _localActor; if (block.Kind != ChatBlockKind.Speaker) { return CreateBubble(topY, block, entry, isSelf); } return CreateSpeakerRow(topY, block, entry, isSelf); } public ChatBlockSize MeasureBubble(ChatLogEntry entry) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) float num = MaxTextWidth; float num2 = 20f; try { if ((Object)(object)_measure != (Object)null) { num = Mathf.Clamp(((TMP_Text)_measure).GetPreferredValues(entry.Text, MaxTextWidth, 0f).x, 20f, MaxTextWidth); num2 = Mathf.Max(20f, ((TMP_Text)_measure).GetPreferredValues(entry.Text, num, 0f).y); } } catch (Exception ex) { if (!_measureErrorLogged) { _measureErrorLogged = true; WLog.Line("chat_measure_error", false, ("err", ex.Message)); } } return new ChatBlockSize(num + 18f, num2 + 18f); } public RectTransform CreateBlock(string name, Vector2 size, float topY, float anchorX) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) }).transform; ((Transform)val).SetParent((Transform)(object)_content, false); val.anchorMin = new Vector2(anchorX, 1f); val.anchorMax = new Vector2(anchorX, 1f); val.pivot = new Vector2(anchorX, 1f); val.sizeDelta = size; float num = ((anchorX <= 0f) ? _sidePadding : ((anchorX >= 1f) ? (0f - _sidePadding) : 0f)); val.anchoredPosition = new Vector2(num, topY); return val; } public void TickAvatarFreeze() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) for (int num = _pendingAvatars.Count - 1; num >= 0; num--) { PendingAvatar value = _pendingAvatars[num]; MenuPlayerListed listed = value.Listed; if ((Object)(object)listed == (Object)null) { _pendingAvatars.RemoveAt(num); } else if (((Component)listed).gameObject.activeInHierarchy) { value.WaitedFrames++; if (!ListedAvatar.IsReady(listed) && value.WaitedFrames < 120) { _pendingAvatars[num] = value; } else { ListedAvatar.Freeze(listed, NameTextColor); _pendingAvatars.RemoveAt(num); } } } } public void ClearPending() { _pendingAvatars.Clear(); } public void Detach() { _content = null; _measure = null; _measureErrorLogged = false; _resolveAvatar = null; _pendingAvatars.Clear(); } private GameObject CreateSpeakerRow(float topY, ChatLayoutBlock block, ChatLogEntry entry, bool isSelf) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) RectTransform val = CreateBlock("Speaker", new Vector2(block.Width, block.Height), topY, isSelf ? 1f : 0f); if ((Object)(object)TryCreateAvatar(val, entry, new Vector2(AvatarHolderX(block.Width, isSelf), 0f), new Vector2(block.Width, block.Height), 1.35f) == (Object)null) { CreateNameLabel(val, entry.Name, Vector2.zero, new Vector2(block.Width, block.Height), (TextAlignmentOptions)(isSelf ? 4100 : 4097)); } return ((Component)val).gameObject; } private GameObject CreateVoteRow(float topY, ChatLayoutBlock block, ChatLogEntry entry) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) RectTransform val = CreateBlock("Vote", new Vector2(block.Width, block.Height), topY, 0.5f); float num = (0f - block.Width) * 0.5f + 84f; Vector2 anchoredPos = default(Vector2); ((Vector2)(ref anchoredPos))..ctor(block.Width * 0.5f - 61f, 0f); Vector2 size = default(Vector2); ((Vector2)(ref size))..ctor(122f, block.Height); if ((Object)(object)TryCreateAvatar(val, entry, new Vector2(num, 0f), new Vector2(168f, block.Height), 0.95f) == (Object)null) { CreateNameLabel(val, entry.Name, new Vector2(num, 0f), new Vector2(168f, block.Height), (TextAlignmentOptions)4097); } ((TMP_Text)UiKit.CreateText((Transform)(object)val, "VoteText", anchoredPos, size, entry.Text, 17f, VoteTextColor, (TextAlignmentOptions)4100)).enableWordWrapping = false; return ((Component)val).gameObject; } private GameObject CreateBubble(float topY, ChatLayoutBlock block, ChatLogEntry entry, bool isSelf) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) bool num = entry.Speaker == ChatSpeaker.Dead; Color color = (num ? DeadBubbleColor : (isSelf ? SelfBubbleColor : AliveBubbleColor)); Color color2 = (num ? DeadBodyColor : (isSelf ? SelfBodyColor : AliveBodyColor)); RectTransform obj = CreateBlock("Bubble", new Vector2(block.Width, block.Height), topY, isSelf ? 1f : 0f); Image obj2 = ((Component)obj).gameObject.AddComponent(); ((Graphic)obj2).color = color; obj2.sprite = UiKit.RoundedRectSprite(); obj2.type = (Type)1; ((Graphic)obj2).raycastTarget = false; float num2 = block.Width - 18f; float num3 = block.Height - 18f; TextMeshProUGUI obj3 = UiKit.CreateText((Transform)(object)obj, "Text", Vector2.zero, new Vector2(num2, num3), entry.Text, 20f, color2, (TextAlignmentOptions)257); ((TMP_Text)obj3).enableWordWrapping = true; RectTransform rectTransform = ((TMP_Text)obj3).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(0f, 1f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.sizeDelta = new Vector2(num2, num3); rectTransform.anchoredPosition = new Vector2(9f, -9f); return ((Component)obj).gameObject; } private static float AvatarHolderX(float blockWidth, bool isSelf) { if (!isSelf) { return (0f - blockWidth) * 0.5f - -31.050001f; } return 0f; } private MenuPlayerListed TryCreateAvatar(RectTransform row, ChatLogEntry entry, Vector2 holderPos, Vector2 holderSize, float scale) { //IL_0042: 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_0055: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar val = ((_resolveAvatar != null) ? _resolveAvatar(entry.Actor) : null); if ((Object)(object)val == (Object)null) { return null; } holderPos.y -= 13.2f * scale; RectTransform val2 = UiKit.CreateRect((Transform)(object)row, "AvatarHolder", holderPos, holderSize); ((Transform)val2).localScale = new Vector3(scale, scale, 1f); MenuPlayerListed val3 = ListedAvatar.TryCreate((Transform)(object)val2, val, entry.Name); if ((Object)(object)val3 == (Object)null) { Object.Destroy((Object)(object)((Component)val2).gameObject); return null; } QueueAvatarFreeze(val3); return val3; } private static void CreateNameLabel(RectTransform row, string playerName, Vector2 pos, Vector2 size, TextAlignmentOptions align) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)UiKit.CreateText((Transform)(object)row, "Name", pos, size, playerName, 19f, NameTextColor, align)).enableWordWrapping = false; } private void QueueAvatarFreeze(MenuPlayerListed listed) { _pendingAvatars.Add(new PendingAvatar { Listed = listed, WaitedFrames = 0 }); } } internal sealed class ChatScrollView { private readonly float _overscan; private readonly float _padding; private readonly float _bottomSlack; private ScrollRect _scroll; private RectTransform _viewport; private RectTransform _content; private readonly Dictionary _live = new Dictionary(); private readonly List _evictScratch = new List(); private int _liveEpoch = int.MinValue; public RectTransform Content => _content; public bool Attached { get { if ((Object)(object)_content != (Object)null) { return (Object)(object)_viewport != (Object)null; } return false; } } public int LiveCount => _live.Count; public ChatScrollView(float overscan, float padding, float bottomSlack) { _overscan = overscan; _padding = padding; _bottomSlack = bottomSlack; } public void Attach(ScrollRect scroll, RectTransform viewport, RectTransform content) { _scroll = scroll; _viewport = viewport; _content = content; } public void Sync(ChatLayout layout, Func createBlock) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (!Attached || layout == null || createBlock == null) { return; } if (layout.Epoch != _liveEpoch) { Clear(); _liveEpoch = layout.Epoch; } float num = _content.anchoredPosition.y - _padding; float num2 = num - _overscan; Rect rect = _viewport.rect; float to = num + ((Rect)(ref rect)).height + _overscan; layout.GetVisibleRange(num2, to, out var first, out var end); _evictScratch.Clear(); foreach (KeyValuePair item in _live) { if (item.Key < first || item.Key >= end) { _evictScratch.Add(item.Key); } } for (int i = 0; i < _evictScratch.Count; i++) { int key = _evictScratch[i]; if ((Object)(object)_live[key] != (Object)null) { Object.Destroy((Object)(object)_live[key]); } _live.Remove(key); } for (int j = first; j < end; j++) { if (!_live.ContainsKey(j)) { GameObject val = createBlock(j); if ((Object)(object)val != (Object)null) { _live[j] = val; } } } } public void Clear() { foreach (KeyValuePair item in _live) { if ((Object)(object)item.Value != (Object)null) { Object.Destroy((Object)(object)item.Value); } } _live.Clear(); } public void SetContentHeight(float height) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_content != (Object)null) { _content.sizeDelta = new Vector2(0f, height); } } public bool IsAtBottom() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_scroll == (Object)null || (Object)(object)_content == (Object)null || (Object)(object)_viewport == (Object)null) { return true; } Rect rect = _content.rect; float height = ((Rect)(ref rect)).height; rect = _viewport.rect; float num = height - ((Rect)(ref rect)).height; if (num <= 0f) { return true; } return _content.anchoredPosition.y >= num - _bottomSlack; } public void ScrollToBottom() { if (!((Object)(object)_scroll == (Object)null)) { Canvas.ForceUpdateCanvases(); _scroll.verticalNormalizedPosition = 0f; } } public void ResetScrollPosition() { if ((Object)(object)_scroll != (Object)null) { _scroll.verticalNormalizedPosition = 0f; } } public void Detach() { _scroll = null; _viewport = null; _content = null; } } public sealed class CheckmateRevealPanel : IClientPanel { private static readonly Vector2 TrackSize = new Vector2(1500f, 20f); private const float GaugeScale = 0.8f; private static readonly Vector2 GaugePos = new Vector2(0f, 220f); private static readonly Color TrackBgColor = new Color(0.1f, 0.1f, 0.14f, 0.9f); private static readonly Color FillColor = new Color(0.85f, 0.65f, 0.15f, 0.95f); private static readonly Color DeliveryFillColor = new Color(0.3f, 0.8f, 0.95f, 0.95f); private static readonly Color QuotaLineColor = new Color(0.25f, 0.55f, 1f, 0.95f); private static readonly Color CheckmateLineColor = new Color(0.95f, 0.12f, 0.12f, 0.95f); private const float QuotaLineWidth = 4f; private const float EntranceStartScale = 2.6f; private static readonly Vector2 IconSize = new Vector2(300f, 300f); private static readonly Vector2 IconPos = new Vector2(0f, -60f); private static readonly Vector2 TitlePos = new Vector2(0f, -290f); private static readonly Color BackdropColor = new Color(0f, 0f, 0f, 0.85f); private static readonly Color TitleColor = new Color(0.85f, 0.08f, 0.08f, 1f); private GameObject _root; private CanvasGroup _group; private RectTransform _gaugeRect; private Image _fillImage; private Image _deliveryFill; private GameObject _deliveryTrack; private Image _quotaLine; private Image _checkmateLine; private TextMeshProUGUI _lossText; private RectTransform _stampRect; private CanvasGroup _stampGroup; private Image _iconImage; private TextMeshProUGUI _titleText; private TextMeshProUGUI _titleTextBack; private readonly GaugeReveal _reveal = new GaugeReveal(); private MeetingGaugeSnapshot _snapshot; private int _finalPermille; private int _finalLoss; private int _pendingFromPermille; private int _pendingFromLoss; public string LayerName => "CheckmateReveal"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0134: 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_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: 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_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03e6: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_0458: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_04a5: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_CheckmateReveal", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _group = val.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; RectTransform rectTransform = ((Graphic)UiKit.CreateImage((Transform)(object)val2, "Backdrop", Vector2.zero, new Vector2(1920f, 1080f), BackdropColor)).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; _gaugeRect = UiKit.CreateRect((Transform)(object)val2, "Gauge", GaugePos, new Vector2(1560f, 104f)); ((Transform)_gaugeRect).localScale = Vector3.one * 0.8f; Image val3 = UiKit.CreateImage((Transform)(object)_gaugeRect, "Track", Vector2.zero, TrackSize, TrackBgColor); _fillImage = UiKit.CreateFilledImage((Transform)(object)((Graphic)val3).rectTransform, "Fill", Vector2.zero, TrackSize, FillColor); Image val4 = UiKit.CreateImage((Transform)(object)_gaugeRect, "DeliveryTrack", new Vector2(0f, 0f - TrackSize.y), TrackSize, TrackBgColor); _deliveryTrack = ((Component)val4).gameObject; _deliveryFill = UiKit.CreateFilledImage((Transform)(object)((Graphic)val4).rectTransform, "DeliveryFill", Vector2.zero, TrackSize, DeliveryFillColor); _deliveryFill.fillOrigin = 1; _quotaLine = UiKit.CreateImage((Transform)(object)_gaugeRect, "QuotaLine", new Vector2(0f, (0f - TrackSize.y) / 2f), new Vector2(4f, TrackSize.y * 2f + 8f), QuotaLineColor); _checkmateLine = UiKit.CreateImage((Transform)(object)_gaugeRect, "CheckmateLine", new Vector2(0f, (0f - TrackSize.y) / 2f), new Vector2(4f, TrackSize.y * 2f + 8f), CheckmateLineColor); Sprite sprite = AssetCatalog.GetSprite("icon_gauge_valuable_loss"); if ((Object)(object)sprite != (Object)null) { Image obj = UiKit.CreateImage((Transform)(object)_gaugeRect, "GaugeIcon", new Vector2((0f - TrackSize.x) / 2f, 0f), new Vector2(128f, 128f), Color.white); obj.sprite = sprite; obj.preserveAspect = true; } _lossText = UiKit.CreateText((Transform)(object)_gaugeRect, "LossText", new Vector2(0f, 0f - TrackSize.y - 36f), new Vector2(1500f, 44f), "", 44f, Color.white, (TextAlignmentOptions)514); _stampRect = UiKit.CreateRect((Transform)(object)val2, "Stamp", Vector2.zero, new Vector2(1400f, 800f)); _stampGroup = ((Component)_stampRect).gameObject.AddComponent(); _stampGroup.alpha = 0f; _stampGroup.blocksRaycasts = false; _stampGroup.interactable = false; _iconImage = UiKit.CreateImage((Transform)(object)_stampRect, "Icon", IconPos, IconSize, Color.white); ((Behaviour)_iconImage).enabled = false; string text = Texts.Get(TextId.CheckmateTitle); _titleTextBack = UiKit.CreateText((Transform)(object)_stampRect, "TitleBack", TitlePos, new Vector2(1400f, 130f), text, 96f, Color.black, (TextAlignmentOptions)514); ((TMP_Text)_titleTextBack).outlineColor = Color32.op_Implicit(Color.white); ((TMP_Text)_titleTextBack).outlineWidth = 0.3f; ((TMP_Text)_titleTextBack).fontStyle = (FontStyles)1; _titleText = UiKit.CreateText((Transform)(object)_stampRect, "Title", TitlePos, new Vector2(1400f, 130f), text, 96f, TitleColor, (TextAlignmentOptions)514); ((TMP_Text)_titleText).outlineColor = Color32.op_Implicit(Color.black); ((TMP_Text)_titleText).outlineWidth = 0.12f; ((TMP_Text)_titleText).fontStyle = (FontStyles)1; _root.SetActive(false); WLog.Line("checkmate_reveal_built", false); } } public void Show(MeetingGaugeSnapshot snapshot, int fromPermille, int fromLoss) { //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null || snapshot == null) { return; } _snapshot = snapshot; _finalPermille = Clamp(snapshot.RatioPermille); _finalLoss = ((snapshot.LostDollars >= 0) ? snapshot.LostDollars : ((int)((long)snapshot.BaseDollars * (long)_finalPermille + 500) / 1000)); if (_finalLoss > snapshot.BaseDollars) { _finalLoss = snapshot.BaseDollars; } if (fromPermille > _finalPermille) { fromPermille = _finalPermille; } if (fromLoss > _finalLoss) { fromLoss = _finalLoss; } RenderLoss(fromPermille, fromLoss); int num = snapshot.DeliveryPermille(); bool flag = num >= 0; if (_deliveryTrack.activeSelf != flag) { _deliveryTrack.SetActive(flag); } if (flag && (Object)(object)_deliveryFill != (Object)null) { _deliveryFill.fillAmount = (float)num / 1000f; } int num2 = snapshot.QuotaPermille(); bool flag2 = flag && num2 >= 0; if ((Object)(object)_quotaLine != (Object)null) { ((Component)_quotaLine).gameObject.SetActive(flag2); if (flag2) { ((Graphic)_quotaLine).rectTransform.anchoredPosition = new Vector2(TrackSize.x / 2f - TrackSize.x * (float)num2 / 1000f, (0f - TrackSize.y) / 2f); } } int num3 = snapshot.CheckmateLinePermille(); if ((Object)(object)_checkmateLine != (Object)null) { ((Component)_checkmateLine).gameObject.SetActive(num3 >= 0); if (num3 >= 0) { ((Graphic)_checkmateLine).rectTransform.anchoredPosition = new Vector2((0f - TrackSize.x) / 2f + TrackSize.x * (float)num3 / 1000f, (0f - TrackSize.y) / 2f); } } Sprite val = AssetCatalog.GetSprite("img_taxman_foreclosure") ?? AssetCatalog.GetSprite("img_taxman_death"); if ((Object)(object)val != (Object)null) { _iconImage.sprite = val; _iconImage.preserveAspect = true; ((Behaviour)_iconImage).enabled = true; } else { _iconImage.sprite = null; ((Behaviour)_iconImage).enabled = false; } _stampGroup.alpha = 0f; ((Transform)_stampRect).localScale = Vector3.one; _reveal.Stop(); _pendingFromPermille = fromPermille; _pendingFromLoss = fromLoss; } public IEnumerator Play(Action onBreak, Action onStamp) { if ((Object)(object)_root == (Object)null || _snapshot == null) { WLog.Line("checkmate_reveal_skip", false, ("reason", "not_built")); yield break; } _group.alpha = 0f; _root.SetActive(true); WLog.Line("checkmate_reveal_show", false, ("from", _pendingFromPermille), ("to", _finalPermille)); yield return UiTween.Fade(_group, 0f, 1f, 0.3f, UiTween.EaseIn()); _reveal.Start(_pendingFromPermille, _finalPermille, _pendingFromLoss, _finalLoss, NowMs()); bool breakPlayed = false; while (!_reveal.Done(NowMs())) { long nowUnixMs = NowMs(); if (!breakPlayed && _reveal.GrowthStarted(nowUnixMs)) { breakPlayed = true; try { onBreak?.Invoke(); } catch (Exception ex) { WLog.Line("checkmate_break_sfx_error", false, ("err", ex.Message)); } } RenderLoss(_reveal.CurrentPermille(nowUnixMs), _reveal.CurrentLoss(nowUnixMs)); yield return null; } _reveal.Stop(); RenderLoss(_finalPermille, _finalLoss); yield return UiTween.Hold(0.4f); ((Transform)_stampRect).localScale = Vector3.one * 2.6f; yield return UiTween.Parallel(UiTween.Scale((Transform)(object)_stampRect, 2.6f, 1f, 0.45f, UiTween.EaseIn()), UiTween.Fade(_stampGroup, 0f, 1f, 0.45f, UiTween.EaseIn())); try { onStamp?.Invoke(); } catch (Exception ex2) { WLog.Line("checkmate_stamp_sfx_error", false, ("err", ex2.Message)); } yield return UiTween.Hold(3f); WLog.Line("checkmate_reveal_hold_end", false); } public void HideNow() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) _reveal.Stop(); _snapshot = null; if (!((Object)(object)_root == (Object)null)) { _group.alpha = 0f; _stampGroup.alpha = 0f; ((Transform)_stampRect).localScale = Vector3.one; _root.SetActive(false); } } public void Destroy() { _reveal.Stop(); _snapshot = null; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _group = null; _gaugeRect = null; _fillImage = null; _deliveryFill = null; _deliveryTrack = null; _quotaLine = null; _checkmateLine = null; _lossText = null; _stampRect = null; _stampGroup = null; _iconImage = null; _titleText = null; _titleTextBack = null; } private void RenderLoss(int permille, int lossDollars) { if ((Object)(object)_fillImage != (Object)null) { _fillImage.fillAmount = (float)permille / 1000f; } if ((Object)(object)_lossText != (Object)null && _snapshot != null) { ((TMP_Text)_lossText).text = Texts.Format(TextId.GaugeLossOverBaseFormat, lossDollars, _snapshot.BaseDollars); } } private static int Clamp(int permille) { if (permille < 0) { return 0; } if (permille > 1000) { return 1000; } return permille; } private static long NowMs() { return (long)(Time.unscaledTimeAsDouble * 1000.0); } } public sealed class ConveneCountdown : IClientPanel { private const float PhaseEnterSec = 0.18f; private const float PhaseHoldSec = 0.14f; private const float PhaseExitSec = 0.68f; private const float ScaleEnterFrom = 0.35f; private const float ScaleHold = 1.35f; private const float ScaleExitTo = 2.4f; private const float BaseScaleAtStart = 0.7f; private const float BaseScaleAtEnd = 3f; private const float HeaderY = 140f; private const float NumberY = -40f; private const float HeaderFontSize = 44f; private const float NumberFontSize = 220f; private static readonly Vector2 HeaderSize = new Vector2(1400f, 240f); private static readonly Vector2 NumberSize = new Vector2(600f, 400f); private GameObject _root; private CanvasGroup _group; private TextMeshProUGUI _headerText; private TextMeshProUGUI _numberText; private CanvasGroup _numberGroup; private int _currentSecond = -1; private int _totalSeconds; public string LayerName => "ConveneCountdown"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_ConveneCountdown", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _group = val.AddComponent(); _group.alpha = 1f; _group.blocksRaycasts = false; _group.interactable = false; _headerText = UiKit.CreateText((Transform)(object)val2, "Header", new Vector2(0f, 140f), HeaderSize, string.Empty, 44f, new Color(1f, 0.95f, 0.7f, 1f), (TextAlignmentOptions)514); ((TMP_Text)_headerText).enableWordWrapping = false; ((Graphic)_headerText).raycastTarget = false; _numberText = UiKit.CreateText((Transform)(object)val2, "Number", new Vector2(0f, -40f), NumberSize, string.Empty, 220f, new Color(1f, 0.98f, 0.85f, 1f), (TextAlignmentOptions)514); ((TMP_Text)_numberText).enableWordWrapping = false; ((Graphic)_numberText).raycastTarget = false; _numberGroup = ((Component)_numberText).gameObject.AddComponent(); _numberGroup.alpha = 0f; _numberGroup.blocksRaycasts = false; _numberGroup.interactable = false; _root.SetActive(false); WLog.Line("convene_countdown_built", false); } } public IEnumerator Tick(string callerName, int remainingSeconds, TextId headerFormatId = TextId.ConveneCountdownHeaderFormat) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return null; } if (remainingSeconds < 0) { remainingSeconds = 0; } ApplyHeader(callerName, headerFormatId); if ((Object)(object)_group != (Object)null) { _group.alpha = 1f; } if (!_root.activeSelf) { _root.SetActive(true); } if (remainingSeconds == _currentSecond) { return null; } if (remainingSeconds > _totalSeconds) { _totalSeconds = remainingSeconds; } _currentSecond = remainingSeconds; ((TMP_Text)_numberText).text = remainingSeconds.ToString(); float num = ComputeBaseScaleFactor(); ((TMP_Text)_numberText).transform.localScale = Vector3.one * (0.35f * num); _numberGroup.alpha = 0f; return BuildSecondTween(num); } private float ComputeBaseScaleFactor() { if (_totalSeconds <= 0) { return 0.7f; } float num = 1f - (float)_currentSecond / (float)_totalSeconds; if (num < 0f) { num = 0f; } else if (num > 1f) { num = 1f; } return 0.7f + num * 2.3f; } private IEnumerator BuildSecondTween(float baseFactor) { Transform transform = ((TMP_Text)_numberText).transform; float num = 0.35f * baseFactor; float num2 = 1.35f * baseFactor; float to = 2.4f * baseFactor; return UiTween.Sequence(UiTween.Parallel(UiTween.Scale(transform, num, num2, 0.18f, UiTween.EaseIn()), UiTween.Fade(_numberGroup, 0f, 1f, 0.18f)), UiTween.Hold(0.14f), UiTween.Parallel(UiTween.Scale(transform, num2, to, 0.68f), UiTween.Fade(_numberGroup, 1f, 0f, 0.68f))); } public void Hide() { _currentSecond = -1; _totalSeconds = 0; if ((Object)(object)_numberText != (Object)null) { ((TMP_Text)_numberText).text = string.Empty; } if ((Object)(object)_numberGroup != (Object)null) { _numberGroup.alpha = 0f; } if ((Object)(object)_headerText != (Object)null) { ((TMP_Text)_headerText).text = string.Empty; } if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _group = null; _headerText = null; _numberText = null; _numberGroup = null; _currentSecond = -1; _totalSeconds = 0; } public IEnumerator PlayStandalone(string callerName, int totalSeconds) { if ((Object)(object)_root == (Object)null) { WLog.Line("convene_countdown_skip", false, ("reason", "not_built")); yield break; } if (totalSeconds < 0) { totalSeconds = 0; } _currentSecond = -1; _totalSeconds = 0; for (int s = totalSeconds; s >= 0; s--) { IEnumerator enumerator = Tick(callerName, s); if (enumerator != null) { yield return enumerator; } else { yield return null; } } Hide(); } private void ApplyHeader(string callerName, TextId headerFormatId) { if (!((Object)(object)_headerText == (Object)null)) { string text = (string.IsNullOrEmpty(callerName) ? Texts.Get(TextId.ConveneCountdownDefaultCallerName) : callerName); string text2 = Texts.Format(headerFormatId, text); if (((TMP_Text)_headerText).text != text2) { ((TMP_Text)_headerText).text = text2; } } } } public sealed class ConveneHoldGauge : IClientPanel { private const float RadialSize = 96f; private static readonly Color ChargingColor = new Color(1f, 0.85f, 0.15f, 0.65f); private GameObject _root; private Image _radial; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_ConveneHoldGauge", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _radial = UiKit.CreateRadialImage(val.transform, "HoldRadial", Vector2.zero, new Vector2(96f, 96f), ChargingColor); _root = val; _root.SetActive(false); } } public void Tick(bool visible, float ratio, Vector3? buttonWorldPos) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { Camera main = Camera.main; bool flag = visible && buttonWorldPos.HasValue && (Object)(object)main != (Object)null; Vector3 val = default(Vector3); if (flag) { val = main.WorldToViewportPoint(buttonWorldPos.Value); if (val.z <= 0f || val.x < 0f || val.x > 1f || val.y < 0f || val.y > 1f) { flag = false; } } if (_root.activeSelf != flag) { _root.SetActive(flag); } if (flag) { RectTransform val2 = (RectTransform)_root.transform; RectTransform rectTransform = ((Graphic)_radial).rectTransform; float num = val.x - 0.5f; Rect rect = val2.rect; float num2 = num * ((Rect)(ref rect)).width; float num3 = val.y - 0.5f; rect = val2.rect; rectTransform.anchoredPosition = new Vector2(num2, num3 * ((Rect)(ref rect)).height); _radial.fillAmount = Mathf.Clamp01(ratio); } } catch (Exception ex) { WLog.Line("convene_hold_gauge_tick_error", false, ("err", ex.Message)); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _radial = null; } } public sealed class CorpseReportHudPanel : IClientPanel { public enum Layout { AtGaugeSlot, AboveMiniGauge } internal const float RightMargin = 25f; private const float GaugeBottomY = 50f; private const float GaugeTopY = 140f; private const float AboveGaugeGap = 20f; internal const float IconSize = 112f; internal const float PlatePadding = 8f; internal const float LabelHeight = 34f; internal const float LabelGap = 4f; internal const float LabelFontSize = 26f; internal const float StackHeight = 158f; private static readonly Color IdleIconTint = new Color(0.45f, 0.45f, 0.45f, 0.8f); private static readonly Color ActiveIconTint = new Color(1f, 0.95f, 0.85f, 1f); private static readonly Color IdleLabelColor = new Color(0.55f, 0.55f, 0.55f, 0.9f); private static readonly Color ActiveLabelColor = new Color(1f, 0.55f, 0.45f, 1f); private const float PulseAmplitude = 0.15f; private const float PulseHz = 1.6f; private GameObject _root; private RectTransform _iconRect; private RectTransform _slotRect; private Image _icon; private Sprite _iconSpriteColor; private Sprite _iconSpriteGray; private TextMeshProUGUI _iconFallback; private TextMeshProUGUI _keyLabel; private Layout _lastLayout = (Layout)(-1); private bool _lastNear; private string _lastKeyName; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; internal static float BaseY(Layout layout) { if (layout != Layout.AboveMiniGauge) { return 50f; } return 160f; } public void Build(Transform layerRoot) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } float num = 120f; float num2 = num; float num3 = num + 4f + 34f; GameObject val = new GameObject("WW_CorpseReportHudPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(1f, 0f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(1f, 0f); val2.anchoredPosition = new Vector2(-25f, 50f); val2.sizeDelta = new Vector2(num2, num3); _root = val; float num4 = num2 / 2f; float num5 = 38f + num / 2f; _slotRect = UiKit.CreateRect((Transform)(object)val2, "Slot", Vector2.zero, new Vector2(num, num)); SetAnchorsBottomLeft(_slotRect, new Vector2(num4, num5), new Vector2(num, num)); _icon = UiKit.CreateImage((Transform)(object)_slotRect, "Icon", Vector2.zero, new Vector2(112f, 112f), IdleIconTint); _iconRect = ((Graphic)_icon).rectTransform; Sprite sprite = AssetCatalog.GetSprite("icon_host_megaphone"); if ((Object)(object)sprite != (Object)null) { _iconSpriteColor = sprite; _iconSpriteGray = AssetCatalog.GetGraySprite("icon_host_megaphone"); _icon.sprite = _iconSpriteGray ?? sprite; _icon.preserveAspect = true; } else { ((Behaviour)_icon).enabled = false; _iconFallback = UiKit.CreateText((Transform)(object)_slotRect, "IconFallback", Vector2.zero, new Vector2(112f, 112f), "通報", 26f, IdleIconTint, (TextAlignmentOptions)514); } _keyLabel = UiKit.CreateText((Transform)(object)val2, "KeyLabel", new Vector2(num4, 17f), new Vector2(num2 + 120f, 34f), string.Empty, 26f, IdleLabelColor, (TextAlignmentOptions)514); SetAnchorsBottomLeft(((TMP_Text)_keyLabel).rectTransform, new Vector2(num4, 17f), new Vector2(num2 + 120f, 34f)); _root.SetActive(false); WLog.Line("corpse_report_hud_built", false); } } public void Tick(bool visible, bool nearCorpse, string keyName, Layout layout) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: 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) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { if (_root.activeSelf != visible) { _root.SetActive(visible); } if (!visible) { _lastKeyName = null; return; } if (layout != _lastLayout) { _lastLayout = layout; ((RectTransform)_root.transform).anchoredPosition = new Vector2(-25f, BaseY(layout)); } if (nearCorpse && (Object)(object)_iconRect != (Object)null) { float num = 1f + 0.15f * Mathf.Sin(Time.unscaledTime * 1.6f * 2f * MathF.PI); ((Transform)_iconRect).localScale = new Vector3(num, num, 1f); } else if ((Object)(object)_iconRect != (Object)null && ((Transform)_iconRect).localScale != Vector3.one) { ((Transform)_iconRect).localScale = Vector3.one; } if (nearCorpse == _lastNear && keyName == _lastKeyName) { return; } _lastNear = nearCorpse; _lastKeyName = keyName; Color color = (nearCorpse ? ActiveIconTint : IdleIconTint); Color color2 = (nearCorpse ? ActiveLabelColor : IdleLabelColor); if ((Object)(object)_icon != (Object)null && ((Behaviour)_icon).enabled) { ((Graphic)_icon).color = color; Sprite val = (nearCorpse ? (_iconSpriteColor ?? _iconSpriteGray) : (_iconSpriteGray ?? _iconSpriteColor)); if ((Object)(object)val != (Object)null && (Object)(object)_icon.sprite != (Object)(object)val) { _icon.sprite = val; } } if ((Object)(object)_iconFallback != (Object)null) { ((Graphic)_iconFallback).color = color; } if ((Object)(object)_keyLabel != (Object)null) { ((TMP_Text)_keyLabel).text = Texts.Format(TextId.HudCorpseReportKeyFormat, keyName ?? "?"); ((Graphic)_keyLabel).color = color2; } } catch (Exception ex) { WLog.Line("corpse_report_hud_tick_error", false, ("err", ex.Message)); } } public void Hide() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } _lastKeyName = null; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _slotRect = null; _icon = null; _iconRect = null; _iconFallback = null; _keyLabel = null; _lastLayout = (Layout)(-1); _lastNear = false; _lastKeyName = null; } private static void SetAnchorsBottomLeft(RectTransform rect, Vector2 centerPos, Vector2 size) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0f, 0f); rect.anchorMax = val; rect.anchorMin = val; rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = centerPos; rect.sizeDelta = size; } } public sealed class CurseTargetSource : ITargetSelectionSource { private readonly RolesClientState _roles; private readonly MeetingClientState _meeting; private readonly IReadOnlyList _roster; private readonly Func _nowUnixMs; private readonly Action _sendDesignate; private readonly List _targets = new List(); public IReadOnlyList TargetActors { get { _targets.Clear(); int curseCatActor = _roles.CurseCatActor; int[] curseCandidates = _roles.CurseCandidates; foreach (WPlayer item in _roster) { if (item != null && item.ActorNumber != curseCatActor && (curseCandidates == null || Array.IndexOf(curseCandidates, item.ActorNumber) >= 0) && _meeting.GetRowStatus(item.ActorNumber) == RowStatus.Alive) { _targets.Add(item.ActorNumber); } } return _targets; } } public bool AllowSkip => false; public int CurrentSelection { get; private set; } = -1; public CurseTargetSource(RolesClientState roles, MeetingClientState meeting, IReadOnlyList roster, Func nowUnixMs, Action sendDesignate) { _roles = roles ?? throw new ArgumentNullException("roles"); _meeting = meeting ?? throw new ArgumentNullException("meeting"); _roster = roster ?? throw new ArgumentNullException("roster"); _nowUnixMs = nowUnixMs ?? throw new ArgumentNullException("nowUnixMs"); _sendDesignate = sendDesignate ?? throw new ArgumentNullException("sendDesignate"); } public bool CanConfirm(int localActor) { if (localActor != _roles.CurseCatActor) { return false; } return _roles.CurseActive(_nowUnixMs()); } public void Confirm(int localActor, int targetActor) { if (CanConfirm(localActor)) { if (!IsValidTarget(targetActor)) { WLog.Line("curse_designate_invalid", true, ("target", targetActor)); return; } WLog.Line("curse_designate_send", true, ("target", targetActor)); _sendDesignate(targetActor); CurrentSelection = targetActor; } } private bool IsValidTarget(int targetActor) { if (targetActor == _roles.CurseCatActor) { return false; } int[] curseCandidates = _roles.CurseCandidates; if (curseCandidates != null && Array.IndexOf(curseCandidates, targetActor) < 0) { return false; } if (_meeting.GetRowStatus(targetActor) != RowStatus.Alive) { return false; } foreach (WPlayer item in _roster) { if (item != null && item.ActorNumber == targetActor) { return true; } } return false; } } public sealed class CursorMirror : IClientPanel { private const int CaptureLayer = 31; private const int CaptureTexSize = 128; private const float MinHeight = 12f; private const float MaxHeight = 200f; internal static bool SuppressVanillaCursor; private GameObject _root; private Image _image; private RectTransform _imageRect; private Canvas _canvas; private MenuCursor _capturedFrom; private Sprite _capturedSprite; private Vector2 _capturedPivot; private Vector2 _capturedSize; private Color _capturedColor = Color.white; private bool _captureLoggedForInstance; public string LayerName => "Cursor"; public float SizeScale { get; set; } = 1f; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_CursorMirror", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Canvas val3 = val.AddComponent(); val3.overrideSorting = true; val3.sortingOrder = 10001; _canvas = val3; _root = val; GameObject val4 = new GameObject("Cursor", new Type[1] { typeof(RectTransform) }); _imageRect = (RectTransform)val4.transform; ((Transform)_imageRect).SetParent((Transform)(object)val2, false); RectTransform imageRect = _imageRect; Vector2 anchorMin = (_imageRect.anchorMax = Vector2.zero); imageRect.anchorMin = anchorMin; _image = val4.AddComponent(); ((Graphic)_image).raycastTarget = false; _image.preserveAspect = true; val4.SetActive(false); } } public void Tick(bool active) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null || !active) { SetVisible(visible: false); SuppressVanillaCursor = false; return; } MenuCursor instance = MenuCursor.instance; if (!TryEnsureCapture(instance)) { SetVisible(visible: false); SuppressVanillaCursor = false; return; } _image.sprite = _capturedSprite; ((Graphic)_image).color = _capturedColor; _imageRect.pivot = _capturedPivot; _imageRect.sizeDelta = _capturedSize * Mathf.Clamp(SizeScale, 0.25f, 4f); float num = (((Object)(object)_canvas != (Object)null) ? _canvas.scaleFactor : 1f); if (num <= 0f) { num = 1f; } _imageRect.anchoredPosition = Vector2.op_Implicit(Input.mousePosition) / num; SetVisible(visible: true); SuppressVanillaCursor = true; } public void Destroy() { SuppressVanillaCursor = false; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _image = null; _imageRect = null; _canvas = null; } private void SetVisible(bool visible) { if ((Object)(object)_image != (Object)null && ((Component)_image).gameObject.activeSelf != visible) { ((Component)_image).gameObject.SetActive(visible); } } private unsafe bool TryEnsureCapture(MenuCursor cursor) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)cursor == (Object)null) { return false; } if ((Object)(object)_capturedSprite != (Object)null && (Object)(object)_capturedFrom == (Object)(object)cursor) { return true; } if ((Object)(object)_capturedFrom != (Object)(object)cursor) { _captureLoggedForInstance = false; } try { if (((Component)cursor).transform.childCount == 0) { return false; } GameObject gameObject = ((Component)((Component)cursor).transform.GetChild(0)).gameObject; if (!gameObject.activeInHierarchy) { return false; } float x = ((Component)cursor).transform.localScale.x; if (x < 0.9f) { return false; } Canvas componentInParent = ((Component)cursor).GetComponentInParent(); Canvas val = (((Object)(object)componentInParent != (Object)null) ? componentInParent.rootCanvas : null); if ((Object)(object)val == (Object)null) { return false; } LogInventoryOnce(cursor, gameObject, val); if (!TryGetWorldBounds(gameObject, out var bounds)) { return false; } Color tint; Sprite val2 = ResolveSprite(gameObject, val, bounds, out tint); if ((Object)(object)val2 == (Object)null) { WLog.Line("cursor_mirror_capture_failed", false, ("reason", "no_strategy")); return false; } Transform transform = ((Component)val).transform; float num = ProjectedHalfExtent(bounds, transform.right); float num2 = ProjectedHalfExtent(bounds, transform.up); if (num <= 0f || num2 <= 0f) { return false; } Vector3 val3 = ((Component)cursor).transform.position - ((Bounds)(ref bounds)).center; float num3 = Mathf.Clamp01(0.5f + Vector3.Dot(val3, transform.right) / (2f * num)); float num4 = Mathf.Clamp01(0.5f + Vector3.Dot(val3, transform.up) / (2f * num2)); Rect rect = ((RectTransform)transform).rect; float num5 = ((Rect)(ref rect)).height * transform.lossyScale.y; rect = ((RectTransform)_root.transform).rect; float height = ((Rect)(ref rect)).height; float num6 = ((num5 > 0f) ? Mathf.Clamp(2f * num2 / x / num5 * height, 12f, 200f) : 48f); float num7 = num6 * (num / num2); _capturedFrom = cursor; _capturedSprite = val2; _capturedPivot = new Vector2(num3, num4); _capturedSize = new Vector2(num7, num6); _capturedColor = tint; WLog.Line("cursor_mirror_captured", false, ("size", $"{num7:F1}x{num6:F1}"), ("pivot", $"{num3:F2},{num4:F2}"), ("tint", ((object)(*(Color*)(&tint))/*cast due to .constrained prefix*/).ToString()), ("settleScale", x.ToString("F3")), ("worldHalf", $"{num:F4}x{num2:F4}"), ("canvasWorldH", num5.ToString("F4"))); return true; } catch (Exception ex) { WLog.Line("cursor_mirror_capture_failed", false, ("reason", "exception"), ("err", ex.Message)); return false; } } private void LogInventoryOnce(MenuCursor cursor, GameObject visual, Canvas vanillaRoot) { //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) if (_captureLoggedForInstance) { return; } _captureLoggedForInstance = true; List list = new List(); Component[] componentsInChildren = visual.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string text = ""; Image val2 = (Image)(object)((val is Image) ? val : null); if (val2 != null) { text = "(sprite=" + (((Object)(object)val2.sprite != (Object)null) ? ((Object)val2.sprite).name : "null") + ")"; } else { RawImage val3 = (RawImage)(object)((val is RawImage) ? val : null); if (val3 != null) { text = "(tex=" + (((Object)(object)val3.texture != (Object)null) ? ((Object)val3.texture).name : "null") + ")"; } else { SpriteRenderer val4 = (SpriteRenderer)(object)((val is SpriteRenderer) ? val : null); if (val4 != null) { text = "(sprite=" + (((Object)(object)val4.sprite != (Object)null) ? ((Object)val4.sprite).name : "null") + ")"; } else { MeshFilter val5 = (MeshFilter)(object)((val is MeshFilter) ? val : null); if (val5 != null) { text = "(mesh=" + (((Object)(object)val5.sharedMesh != (Object)null) ? ((Object)val5.sharedMesh).name : "null") + ")"; } else { Renderer val6 = (Renderer)(object)((val is Renderer) ? val : null); if (val6 != null) { text = "(mat=" + (((Object)(object)val6.sharedMaterial != (Object)null) ? ((Object)val6.sharedMaterial).name : "null") + ")"; } } } } } list.Add(((Object)val.gameObject).name + ":" + ((object)val).GetType().Name + text); } (string, object)[] obj = new(string, object)[4] { ("visual", ((Object)visual).name), ("components", string.Join(" | ", list)), ("canvasMode", vanillaRoot.renderMode), default((string, object)) }; Vector3 lossyScale = ((Component)cursor).transform.lossyScale; obj[3] = ("cursorScale", ((Vector3)(ref lossyScale)).ToString("F4")); WLog.Line("cursor_mirror_inventory", secret: false, obj); } private static bool TryGetWorldBounds(GameObject visual, out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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) bounds = default(Bounds); bool flag = false; Renderer[] componentsInChildren = visual.GetComponentsInChildren(false); foreach (Renderer val in componentsInChildren) { if (!flag) { bounds = val.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val.bounds); } } Vector3[] array = (Vector3[])(object)new Vector3[4]; Graphic[] componentsInChildren2 = visual.GetComponentsInChildren(false); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].rectTransform.GetWorldCorners(array); for (int j = 0; j < 4; j++) { if (!flag) { bounds = new Bounds(array[j], Vector3.zero); flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(array[j]); } } } if (flag) { Vector3 size = ((Bounds)(ref bounds)).size; return ((Vector3)(ref size)).sqrMagnitude > 1E-10f; } return false; } private static float ProjectedHalfExtent(Bounds bounds, Vector3 axis) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) Vector3 extents = ((Bounds)(ref bounds)).extents; return Mathf.Abs(axis.x * extents.x) + Mathf.Abs(axis.y * extents.y) + Mathf.Abs(axis.z * extents.z); } private Sprite ResolveSprite(GameObject visual, Canvas vanillaRoot, Bounds bounds, out Color tint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) tint = Color.white; Image componentInChildren = visual.GetComponentInChildren(false); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.sprite != (Object)null) { tint = ((Graphic)componentInChildren).color; WLog.Line("cursor_mirror_strategy", false, ("mode", "image_sprite"), ("name", ((Object)componentInChildren.sprite).name), ("tint", ((object)Unsafe.As(ref tint)/*cast due to .constrained prefix*/).ToString()), ("mat", ((Object)(object)((Graphic)componentInChildren).material != (Object)null) ? ((Object)((Graphic)componentInChildren).material).name : "null")); return componentInChildren.sprite; } SpriteRenderer componentInChildren2 = visual.GetComponentInChildren(false); if ((Object)(object)componentInChildren2 != (Object)null && (Object)(object)componentInChildren2.sprite != (Object)null) { tint = componentInChildren2.color; WLog.Line("cursor_mirror_strategy", false, ("mode", "sprite_renderer"), ("name", ((Object)componentInChildren2.sprite).name), ("tint", ((object)Unsafe.As(ref tint)/*cast due to .constrained prefix*/).ToString())); return componentInChildren2.sprite; } RawImage componentInChildren3 = visual.GetComponentInChildren(false); if ((Object)(object)componentInChildren3 != (Object)null) { Texture texture = componentInChildren3.texture; Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (val != null) { tint = ((Graphic)componentInChildren3).color; WLog.Line("cursor_mirror_strategy", false, ("mode", "raw_image"), ("name", ((Object)val).name), ("tint", ((object)Unsafe.As(ref tint)/*cast due to .constrained prefix*/).ToString()), ("mat", ((Object)(object)((Graphic)componentInChildren3).material != (Object)null) ? ((Object)((Graphic)componentInChildren3).material).name : "null")); return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } } Sprite val2 = SnapshotRenderers(visual, vanillaRoot, bounds); if ((Object)(object)val2 != (Object)null) { WLog.Line("cursor_mirror_strategy", false, ("mode", "snapshot")); } return val2; } private static Sprite SnapshotRenderers(GameObject visual, Canvas vanillaRoot, Bounds bounds) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) Renderer[] componentsInChildren = visual.GetComponentsInChildren(false); if (componentsInChildren.Length == 0) { return null; } Transform transform = ((Component)vanillaRoot).transform; float num = ProjectedHalfExtent(bounds, transform.right) * 1.05f; float num2 = ProjectedHalfExtent(bounds, transform.up) * 1.05f; if (num <= 0f || num2 <= 0f) { return null; } int num3 = ((num >= num2) ? 128 : Mathf.Max(8, Mathf.RoundToInt(128f * num / num2))); int num4 = ((num2 >= num) ? 128 : Mathf.Max(8, Mathf.RoundToInt(128f * num2 / num))); Dictionary dictionary = new Dictionary(); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { dictionary[((Component)val).gameObject] = ((Component)val).gameObject.layer; ((Component)val).gameObject.layer = 31; } RenderTexture temporary = RenderTexture.GetTemporary(num3, num4, 16, (RenderTextureFormat)0); GameObject val2 = new GameObject("WW_CursorSnapCam"); try { Camera val3 = val2.AddComponent(); ((Behaviour)val3).enabled = false; val3.orthographic = true; val3.orthographicSize = num2; val3.aspect = num / num2; val3.clearFlags = (CameraClearFlags)2; val3.backgroundColor = Color.clear; val3.cullingMask = int.MinValue; float num5 = num + num2 + 0.5f; val3.nearClipPlane = 0.01f; val3.farClipPlane = num5 * 2f + 1f; val3.targetTexture = temporary; Vector3[] array2 = (Vector3[])(object)new Vector3[2] { -transform.forward, transform.forward }; foreach (Vector3 val4 in array2) { ((Component)val3).transform.position = ((Bounds)(ref bounds)).center + val4 * num5; ((Component)val3).transform.rotation = Quaternion.LookRotation(-val4, transform.up); val3.Render(); Texture2D val5 = ReadRenderTexture(temporary); if (CountVisiblePixels(val5) >= 8) { return Sprite.Create(val5, new Rect(0f, 0f, (float)((Texture)val5).width, (float)((Texture)val5).height), new Vector2(0.5f, 0.5f)); } Object.Destroy((Object)(object)val5); } WLog.Line("cursor_mirror_capture_failed", false, ("reason", "snapshot_blank")); return null; } finally { foreach (KeyValuePair item in dictionary) { if ((Object)(object)item.Key != (Object)null) { item.Key.layer = item.Value; } } RenderTexture.ReleaseTemporary(temporary); Object.Destroy((Object)(object)val2); } } private static Texture2D ReadRenderTexture(RenderTexture rt) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown RenderTexture active = RenderTexture.active; RenderTexture.active = rt; Texture2D val = new Texture2D(((Texture)rt).width, ((Texture)rt).height, (TextureFormat)5, false); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)rt).width, (float)((Texture)rt).height), 0, 0); val.Apply(); RenderTexture.active = active; return val; } private static int CountVisiblePixels(Texture2D tex) { Color32[] pixels = tex.GetPixels32(); int num = 0; for (int i = 0; i < pixels.Length; i++) { if (pixels[i].a > 8) { num++; } } return num; } } public sealed class DeathRevealPanel : IClientPanel { private const float GridTopY = -80f; private static readonly Vector2 IconSize = new Vector2(280f, 280f); private const float EntranceStartScale = 2.6f; private const float ExitRiseY = 700f; private static readonly Color BackdropColor = new Color(0f, 0f, 0f, 0.75f); private GameObject _root; private CanvasGroup _group; private RectTransform _content; private Image _iconImage; private TextMeshProUGUI _titleText; private TextMeshProUGUI _titleTextBack; private readonly List _rows = new List(); public string LayerName => "DeathReveal"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_DeathReveal", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _group = val.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; RectTransform rectTransform = ((Graphic)UiKit.CreateImage((Transform)(object)val2, "Backdrop", Vector2.zero, new Vector2(1920f, 1080f), BackdropColor)).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; _content = UiKit.CreateRect((Transform)(object)val2, "Content", Vector2.zero, new Vector2(1280f, 900f)); _iconImage = UiKit.CreateImage((Transform)(object)_content, "Icon", new Vector2(0f, 260f), IconSize, Color.white); ((Behaviour)_iconImage).enabled = false; _titleTextBack = UiKit.CreateText((Transform)(object)_content, "TitleBack", new Vector2(0f, 40f), new Vector2(1200f, 140f), "", 120f, Color.black, (TextAlignmentOptions)514); ((TMP_Text)_titleTextBack).outlineColor = Color32.op_Implicit(Color.white); ((TMP_Text)_titleTextBack).outlineWidth = 0.3f; ((TMP_Text)_titleTextBack).fontStyle = (FontStyles)1; _titleText = UiKit.CreateText((Transform)(object)_content, "Title", new Vector2(0f, 40f), new Vector2(1200f, 140f), "", 120f, Color.black, (TextAlignmentOptions)514); ((TMP_Text)_titleText).outlineColor = Color32.op_Implicit(Color.red); ((TMP_Text)_titleText).outlineWidth = 0.15f; ((TMP_Text)_titleText).fontStyle = (FontStyles)1; _root.SetActive(false); WLog.Line("death_reveal_built", false); } } public void Show(IReadOnlyList deadRoster, Func resolveAvatar) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } ClearRows(); int num; int id; if (deadRoster != null) { num = ((deadRoster.Count > 0) ? 1 : 0); if (num != 0) { id = 129; goto IL_0033; } } else { num = 0; } id = 130; goto IL_0033; IL_0033: string text = Texts.Get((TextId)id); ((TMP_Text)_titleText).text = text; ((TMP_Text)_titleTextBack).text = text; Sprite sprite = AssetCatalog.GetSprite((num != 0) ? "img_taxman_death" : "img_taxman_nodeath"); if ((Object)(object)sprite != (Object)null) { _iconImage.sprite = sprite; _iconImage.preserveAspect = true; ((Behaviour)_iconImage).enabled = true; } else { _iconImage.sprite = null; ((Behaviour)_iconImage).enabled = false; } if (num == 0) { return; } for (int i = 0; i < deadRoster.Count; i++) { WPlayer wPlayer = deadRoster[i]; if (wPlayer == null) { continue; } VoteRow voteRow = VoteRow.Build((Transform)(object)_content, wPlayer, resolveAvatar, VoteRowGrid.RowSize); RectTransform root = voteRow.Root; if ((Object)(object)root != (Object)null) { if (deadRoster.Count == 1) { root.anchoredPosition = new Vector2(0f, -80f); } else { root.anchoredPosition = VoteRowGrid.Position(i % 2, Math.Min(i / 2, 6), -80f); } } _rows.Add(voteRow); } } public IEnumerator Play(Action onImpact) { if ((Object)(object)_root == (Object)null) { WLog.Line("death_reveal_skip", false, ("reason", "not_built")); yield break; } _group.alpha = 0f; ((Transform)_content).localScale = Vector3.one * 2.6f; _content.anchoredPosition = Vector2.zero; _root.SetActive(true); WLog.Line("death_reveal_show", false, ("rows", _rows.Count)); yield return UiTween.Parallel(UiTween.Scale((Transform)(object)_content, 2.6f, 1f, 0.2f, UiTween.EaseIn()), UiTween.Fade(_group, 0f, 1f, 0.2f, UiTween.EaseIn())); try { onImpact?.Invoke(); } catch (Exception ex) { WLog.Line("death_reveal_impact_error", false, ("err", ex.Message)); } yield return UiTween.Hold(4f); yield return UiTween.Parallel(UiTween.Move(_content, Vector2.zero, new Vector2(0f, 700f), 0.8f, UiTween.EaseIn()), UiTween.Fade(_group, 1f, 0f, 0.8f, UiTween.EaseIn())); HideNow(); WLog.Line("death_reveal_end", false); } public void Tick() { if (!Visible) { return; } foreach (VoteRow row in _rows) { row.Tick(); } } public void HideNow() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) ClearRows(); if (!((Object)(object)_root == (Object)null)) { _group.alpha = 0f; ((Transform)_content).localScale = Vector3.one; _content.anchoredPosition = Vector2.zero; _root.SetActive(false); } } public void Destroy() { ClearRows(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _group = null; _content = null; _iconImage = null; _titleText = null; _titleTextBack = null; } private void ClearRows() { foreach (VoteRow row in _rows) { row.Destroy(); } _rows.Clear(); } } public sealed class HudPanel : IClientPanel { private const float LeftMargin = 40f; private const float TopMargin = 220f; private const float RootWidth = 400f; private const float RootHeight = 220f; private const float BadgeIconSize = 128f; private const float BadgeRowHeight = 132f; private const float BadgeGap = 10f; private const float BadgeFontSize = 48f; private const float TimerFontSize = 66f; private const float TimerTopMargin = 16f; private static readonly Vector2 TimerSize = new Vector2(700f, 80f); private const float TestPlayFontSize = 34f; private const float TestPlayTopMargin = 100f; private static readonly Vector2 TestPlaySize = new Vector2(1100f, 44f); private static readonly Color TestPlayColor = new Color(1f, 0.6f, 0.15f, 0.95f); private GameObject _root; private GameObject _badgeRow; private Image _badgeIcon; private TextMeshProUGUI _badgeText; private GameObject _timerRow; private TextMeshProUGUI _timerText; private GameObject _testPlayRow; private Role? _lastBadgeRole; private long _lastTimerSec = -1L; private bool _lastTimerFrozen; private Vector2 _positionOffset; private Vector2 _appliedOffset; private static readonly Color TimerNormalColor = new Color(0.95f, 0.95f, 1f, 0.95f); private static readonly Color TimerFrozenColorA = new Color(1f, 0.95f, 0.35f, 1f); private static readonly Color TimerFrozenColorB = new Color(1f, 0.75f, 0.15f, 0.55f); private static readonly Color TimerAlertColorA = new Color(1f, 0.3f, 0.25f, 1f); private static readonly Color TimerAlertColorB = new Color(1f, 0.15f, 0.1f, 0.5f); private const float TimerBlinkHz = 1.5f; public string LayerName => "Hud"; public Vector2 PositionOffset { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return _positionOffset; } set { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _positionOffset = value; } } public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_HudPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 1f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(0f, 1f); val2.sizeDelta = new Vector2(400f, 220f); _root = val; ApplyPositionOffset(force: true); _badgeRow = BuildBadgeRow(val2); _timerRow = BuildTimerBanner(layerRoot, out _timerText); _testPlayRow = BuildTestPlayBanner(layerRoot); _badgeRow.SetActive(false); _timerRow.SetActive(false); _testPlayRow.SetActive(false); WLog.Line("hud_panel_built", false); } } public HudState Tick(HudModel model, HudInput input) { if ((Object)(object)_root == (Object)null || model == null) { return HudState.Hidden; } try { ApplyPositionOffset(force: false); HudState hudState = model.Compute(input); ApplyBadge(hudState); ApplyTimer(hudState); ApplyTestPlay(hudState); return hudState; } catch (Exception ex) { WLog.Line("hud_panel_tick_error", false, ("err", ex.Message)); return HudState.Hidden; } } private void ApplyPositionOffset(bool force) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && (force || !(_appliedOffset == _positionOffset))) { ((RectTransform)_root.transform).anchoredPosition = new Vector2(40f + _positionOffset.x, 0f - (220f + _positionOffset.y)); _appliedOffset = _positionOffset; } } public void Hide() { if (!((Object)(object)_root == (Object)null)) { if ((Object)(object)_badgeRow != (Object)null && _badgeRow.activeSelf) { _badgeRow.SetActive(false); } if ((Object)(object)_timerRow != (Object)null && _timerRow.activeSelf) { _timerRow.SetActive(false); } if ((Object)(object)_testPlayRow != (Object)null && _testPlayRow.activeSelf) { _testPlayRow.SetActive(false); } } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } if ((Object)(object)_timerRow != (Object)null) { Object.Destroy((Object)(object)_timerRow); } if ((Object)(object)_testPlayRow != (Object)null) { Object.Destroy((Object)(object)_testPlayRow); } _badgeRow = null; _badgeIcon = null; _badgeText = null; _timerRow = null; _timerText = null; _testPlayRow = null; _lastBadgeRole = null; _lastTimerSec = -1L; } private void ApplyBadge(HudState s) { bool flag = s.ShowBadge && s.BadgeRole.HasValue; if (_badgeRow.activeSelf != flag) { _badgeRow.SetActive(flag); } if (!flag) { _lastBadgeRole = null; } else { if (_lastBadgeRole == s.BadgeRole) { return; } _lastBadgeRole = s.BadgeRole; ((TMP_Text)_badgeText).text = RoleLabel(s.BadgeRole.Value); Sprite val = ResolveRoleIcon(s.BadgeRole.Value); if ((Object)(object)_badgeIcon != (Object)null) { if ((Object)(object)val != (Object)null) { _badgeIcon.sprite = val; ((Behaviour)_badgeIcon).enabled = true; } else { ((Behaviour)_badgeIcon).enabled = false; } } } } private void ApplyTimer(HudState s) { //IL_00f3: 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_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) if (_timerRow.activeSelf != s.ShowTimer) { _timerRow.SetActive(s.ShowTimer); } if (!s.ShowTimer) { _lastTimerSec = -1L; _lastTimerFrozen = false; return; } long num = (s.TimerRemainingMs + 999) / 1000; if (num < 0) { num = 0L; } if (num != _lastTimerSec || s.TimerFrozen != _lastTimerFrozen) { _lastTimerSec = num; _lastTimerFrozen = s.TimerFrozen; ((TMP_Text)_timerText).text = (s.TimerFrozen ? Texts.Format(TextId.HudTimerFrozenFormat, FormatTime(num)) : FormatTime(num)); } if (s.TimerFrozen) { float num2 = 0.5f + 0.5f * Mathf.Sin(Time.unscaledTime * 1.5f * 2f * MathF.PI); ((Graphic)_timerText).color = Color.Lerp(TimerFrozenColorB, TimerFrozenColorA, num2); } else if (s.TimerAlert) { float num3 = 0.5f + 0.5f * Mathf.Sin(Time.unscaledTime * 1.5f * 2f * MathF.PI); ((Graphic)_timerText).color = Color.Lerp(TimerAlertColorB, TimerAlertColorA, num3); } else if (((Graphic)_timerText).color != TimerNormalColor) { ((Graphic)_timerText).color = TimerNormalColor; } } private void ApplyTestPlay(HudState s) { if ((Object)(object)_testPlayRow != (Object)null && _testPlayRow.activeSelf != s.ShowTestPlay) { _testPlayRow.SetActive(s.ShowTestPlay); } } private GameObject BuildBadgeRow(RectTransform parent) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0045: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("BadgeRow", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent((Transform)(object)parent, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 1f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(0f, 1f); val2.anchoredPosition = Vector2.zero; val2.sizeDelta = new Vector2(400f, 132f); GameObject val4 = new GameObject("Icon", new Type[1] { typeof(RectTransform) }); RectTransform val5 = (RectTransform)val4.transform; ((Transform)val5).SetParent((Transform)(object)val2, false); ((Vector2)(ref val3))..ctor(0f, 0.5f); val5.anchorMax = val3; val5.anchorMin = val3; val5.pivot = new Vector2(0f, 0.5f); val5.anchoredPosition = new Vector2(0f, 0f); val5.sizeDelta = new Vector2(128f, 128f); _badgeIcon = val4.AddComponent(); ((Graphic)_badgeIcon).raycastTarget = false; ((Behaviour)_badgeIcon).enabled = false; _badgeText = UiKit.CreateText((Transform)(object)val2, "Text", Vector2.zero, new Vector2(262f, 132f), string.Empty, 48f, new Color(1f, 1f, 0.9f, 1f), (TextAlignmentOptions)4097); RectTransform rectTransform = ((TMP_Text)_badgeText).rectTransform; rectTransform.anchorMin = new Vector2(0f, 0f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0f, 0.5f); rectTransform.offsetMin = new Vector2(138f, 0f); rectTransform.offsetMax = new Vector2(0f, 0f); return val; } private static GameObject BuildTimerBanner(Transform layerRoot, out TextMeshProUGUI text) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown GameObject val = new GameObject("WW_HudTimer", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0.5f, 1f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(0.5f, 1f); val2.anchoredPosition = new Vector2(0f, -16f); val2.sizeDelta = TimerSize; text = UiKit.CreateText((Transform)(object)val2, "Text", Vector2.zero, TimerSize, string.Empty, 66f, TimerNormalColor, (TextAlignmentOptions)514); RectTransform rectTransform = ((TMP_Text)text).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; return val; } private static GameObject BuildTestPlayBanner(Transform layerRoot) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_00ae: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown GameObject val = new GameObject("WW_HudTestPlay", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0.5f, 1f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(0.5f, 1f); val2.anchoredPosition = new Vector2(0f, -100f); val2.sizeDelta = TestPlaySize; RectTransform rectTransform = ((TMP_Text)UiKit.CreateText((Transform)val2, "Text", Vector2.zero, TestPlaySize, Texts.Get(TextId.HudTestPlayBanner), 34f, TestPlayColor, (TextAlignmentOptions)514)).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; return val; } private static string RoleLabel(Role role) { return role switch { Role.Werewolf => Texts.Get(TextId.RoleNameWerewolf), Role.BlackCat => Texts.Get(TextId.RoleNameBlackCat), Role.Villager => Texts.Get(TextId.RoleNameVillager), Role.Bomber => Texts.Get(TextId.RoleNameBomber), Role.Shaman => Texts.Get(TextId.RoleNameShaman), _ => role.ToString(), }; } private static Sprite ResolveRoleIcon(Role role) { return (Sprite)(role switch { Role.Werewolf => AssetCatalog.GetSprite("role_werewolf"), Role.BlackCat => AssetCatalog.GetSprite("role_blackcat"), Role.Villager => AssetCatalog.GetSprite("role_villager"), Role.Bomber => AssetCatalog.GetSprite("role_bomber"), Role.Shaman => AssetCatalog.GetSprite("role_shaman"), _ => null, }); } private static string FormatTime(long totalSec) { long num = totalSec / 60; long num2 = totalSec % 60; return Texts.Format(TextId.HudTimeRemainingFormat, num, num2.ToString("00")); } } public interface IClientPanel { string LayerName { get; } bool Exists { get; } void Build(Transform layerRoot); void Destroy(); } internal enum ToggleHit : byte { None, Icon, Label } internal readonly struct IconToggleStyle { public float IconSize { get; } public float LabelWidth { get; } public float LabelHeight { get; } public float LabelFontSize { get; } public Color LabelColor { get; } public string FallbackText { get; } public Color FallbackPlateColor { get; } public Color FallbackTextColor { get; } public IconToggleStyle(float iconSize, float labelWidth, float labelHeight, float labelFontSize, Color labelColor, string fallbackText, Color fallbackPlateColor, Color fallbackTextColor) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) IconSize = iconSize; LabelWidth = labelWidth; LabelHeight = labelHeight; LabelFontSize = labelFontSize; LabelColor = labelColor; FallbackText = fallbackText; FallbackPlateColor = fallbackPlateColor; FallbackTextColor = fallbackTextColor; } } internal sealed class IconToggleButton { private const float AlphaHitThreshold = 0.5f; private const float IconLabelGap = 4f; private const float FallbackFontSize = 32f; private const float HoverOutlineThickness = 4f; private static readonly Color HoverOutlineColor = new Color(1f, 0.85f, 0.1f, 1f); private RectTransform _container; private Image _icon; private RectTransform _iconRect; private TextMeshProUGUI _label; private RectTransform _labelRect; private Sprite _sprite; private Outline _hoverOutline; private bool _hovering; public RectTransform Container => _container; public bool HasIcon => (Object)(object)_sprite != (Object)null; public RectTransform Build(Transform parent, string name, IconToggleStyle style, string spriteKey, string initialLabel) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: 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) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(style.IconSize, style.LabelWidth); float num2 = style.IconSize + 4f + style.LabelHeight; _container = UiKit.CreateRect(parent, name, Vector2.zero, new Vector2(num, num2)); float num3 = num2 * 0.5f - style.IconSize * 0.5f; float num4 = 0f - (num2 * 0.5f - style.LabelHeight * 0.5f); _sprite = AssetCatalog.GetSprite(spriteKey); _icon = UiKit.CreateImage((Transform)(object)_container, "Icon", new Vector2(0f, num3), new Vector2(style.IconSize, style.IconSize), Color.white); _iconRect = ((Graphic)_icon).rectTransform; ((Graphic)_icon).raycastTarget = true; if ((Object)(object)_sprite != (Object)null) { _icon.sprite = _sprite; _icon.preserveAspect = true; try { _icon.alphaHitTestMinimumThreshold = 0.5f; } catch { } _hoverOutline = ((Component)_icon).gameObject.AddComponent(); ((Shadow)_hoverOutline).effectColor = HoverOutlineColor; ((Shadow)_hoverOutline).effectDistance = new Vector2(4f, 4f); ((Shadow)_hoverOutline).useGraphicAlpha = true; ((Behaviour)_hoverOutline).enabled = false; } else { ((Graphic)_icon).color = style.FallbackPlateColor; UiKit.CreateText((Transform)(object)_iconRect, "IconFallback", Vector2.zero, new Vector2(style.IconSize, style.IconSize), style.FallbackText, 32f, style.FallbackTextColor, (TextAlignmentOptions)514); } _label = UiKit.CreateText((Transform)(object)_container, "Label", new Vector2(0f, num4), new Vector2(style.LabelWidth, style.LabelHeight), initialLabel, style.LabelFontSize, style.LabelColor, (TextAlignmentOptions)514); _labelRect = ((TMP_Text)_label).rectTransform; return _container; } public void SetLabel(string text) { if ((Object)(object)_label != (Object)null) { ((TMP_Text)_label).text = text; } } public ToggleHit HitTest(Vector2 screenPoint) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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) if ((Object)(object)_container == (Object)null || !((Component)_container).gameObject.activeSelf) { return ToggleHit.None; } if ((Object)(object)_iconRect != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_iconRect, screenPoint, (Camera)null)) { if ((Object)(object)_sprite == (Object)null) { return ToggleHit.Icon; } if (!(SampleIconAlpha(screenPoint) >= 0.5f)) { return ToggleHit.None; } return ToggleHit.Icon; } if ((Object)(object)_labelRect != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_labelRect, screenPoint, (Camera)null)) { return ToggleHit.Label; } return ToggleHit.None; } public bool ContainsRect(Vector2 screenPoint) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_iconRect != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_iconRect, screenPoint, (Camera)null)) { return true; } if ((Object)(object)_labelRect != (Object)null) { return RectTransformUtility.RectangleContainsScreenPoint(_labelRect, screenPoint, (Camera)null); } return false; } public bool IsPointerOverOpaqueIcon() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_sprite != (Object)null) { return HitTest(Vector2.op_Implicit(Input.mousePosition)) == ToggleHit.Icon; } return false; } public bool IsPointerOverHitArea() { //IL_0001: 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) return HitTest(Vector2.op_Implicit(Input.mousePosition)) != ToggleHit.None; } public bool WasClicked() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (Input.GetMouseButtonDown(0)) { return HitTest(Vector2.op_Implicit(Input.mousePosition)) != ToggleHit.None; } return false; } public void SetHover(bool over) { if (!((Object)(object)_hoverOutline == (Object)null) && _hovering != over) { _hovering = over; ((Behaviour)_hoverOutline).enabled = over; } } private float SampleIconAlpha(Vector2 screenPoint) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_iconRect == (Object)null || (Object)(object)_sprite == (Object)null) { return 1f; } Texture2D texture = _sprite.texture; if ((Object)(object)texture == (Object)null) { return 1f; } Vector2 val = default(Vector2); if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(_iconRect, screenPoint, (Camera)null, ref val)) { return 1f; } Rect rect = _iconRect.rect; if (((Rect)(ref rect)).width <= 0f || ((Rect)(ref rect)).height <= 0f) { return 1f; } float num = (val.x - ((Rect)(ref rect)).xMin) / ((Rect)(ref rect)).width; float num2 = (val.y - ((Rect)(ref rect)).yMin) / ((Rect)(ref rect)).height; if (num < 0f || num > 1f || num2 < 0f || num2 > 1f) { return 1f; } return texture.GetPixelBilinear(num, num2).a; } catch { return 1f; } } public void Clear() { _container = null; _icon = null; _iconRect = null; _label = null; _labelRect = null; _sprite = null; _hoverOutline = null; _hovering = false; } } internal static class ListedAvatar { internal const float OriginOffsetX = -23f; internal const float RowHeight = 22f; private static readonly FieldRef _listSpotRef = GameRefs.MenuPlayerListed_listSpot; private static readonly FieldRef _headIsTalkingRef = GameRefs.MenuPlayerHead_isTalking; private static readonly FieldRef _headTransformRef = GameRefs.MenuPlayerHead_headTransform; public static MenuPlayerListed TryCreate(Transform holder, PlayerAvatar avatar, string playerName) { //IL_00ad: 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) if ((Object)(object)avatar == (Object)null) { return null; } try { GameObject val = ResolveRowPrefab(); if ((Object)(object)val == (Object)null || !HasFieldRefs()) { return null; } GameObject val2 = Object.Instantiate(val, holder); MenuPlayerListed component = val2.GetComponent(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val2); return null; } StripChatDisplay(val2); _listSpotRef.Invoke(component) = 0; component.ForcePlayer(avatar); if ((Object)(object)component.playerName != (Object)null && !string.IsNullOrEmpty(playerName)) { ((TMP_Text)component.playerName).text = playerName; } ((Component)component).transform.localPosition = (Vector3)(SemiFunc.RunIsLobbyMenu() ? Vector3.zero : new Vector3(-23f, 0f, 0f)); return component; } catch (Exception ex) { WLog.Line("listed_visual_failed", false, ("err", ex.Message)); return null; } } public static bool IsReady(MenuPlayerListed listed) { if ((Object)(object)listed == (Object)null || (Object)(object)listed.playerHead == (Object)null) { return false; } if (_headTransformRef != null) { return (Object)(object)_headTransformRef.Invoke(listed.playerHead) != (Object)null; } return false; } public static void Freeze(MenuPlayerListed listed, Color nameColor) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)listed == (Object)null) { return; } try { SuppressSpeech(listed, nameColor); if ((Object)(object)listed.leftCrown != (Object)null) { listed.leftCrown.SetActive(false); } if ((Object)(object)listed.rightCrown != (Object)null) { listed.rightCrown.SetActive(false); } MenuPlayerHead playerHead = listed.playerHead; if (!((Object)(object)playerHead == (Object)null)) { if ((Object)(object)playerHead.muteIconTransform != (Object)null) { ((Transform)playerHead.muteIconTransform).localScale = Vector3.zero; } if ((Object)(object)MenuManager.instance != (Object)null) { MenuManager.instance.PlayerHeadRemove(playerHead); } } } catch (Exception ex) { WLog.Line("listed_visual_freeze_failed", false, ("err", ex.Message)); } } public static void SuppressSpeech(MenuPlayerListed listed, Color mutedNameColor) { //IL_0025: 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_0075: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)listed == (Object)null) { return; } ((Behaviour)listed).enabled = false; if ((Object)(object)listed.playerName != (Object)null) { ((Graphic)listed.playerName).color = mutedNameColor; } MenuPlayerHead playerHead = listed.playerHead; if (!((Object)(object)playerHead == (Object)null)) { ((Behaviour)playerHead).enabled = false; if ((Object)(object)playerHead.headRight != (Object)null) { playerHead.headRight.localEulerAngles = Vector3.zero; } if ((Object)(object)playerHead.headLeft != (Object)null) { playerHead.headLeft.localEulerAngles = Vector3.zero; } if (_headIsTalkingRef != null) { _headIsTalkingRef.Invoke(playerHead) = false; } } } public static void ResumeSpeech(MenuPlayerListed listed) { if (!((Object)(object)listed == (Object)null)) { ((Behaviour)listed).enabled = true; if ((Object)(object)listed.playerHead != (Object)null) { ((Behaviour)listed.playerHead).enabled = true; } } } private static void StripChatDisplay(GameObject visual) { LobbyChatUI[] componentsInChildren = visual.GetComponentsInChildren(true); foreach (LobbyChatUI val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } if ((Object)(object)((Component)val).gameObject != (Object)(object)visual) { ((Component)val).gameObject.SetActive(false); continue; } ((Behaviour)val).enabled = false; TextMeshProUGUI component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; } } } private static GameObject ResolveRowPrefab() { MenuSpectateList val = Object.FindObjectOfType(true); if (!((Object)(object)val != (Object)null)) { return null; } return val.menuPlayerListedPrefab; } private static bool HasFieldRefs() { if (_listSpotRef != null && _headIsTalkingRef != null) { return _headTransformRef != null; } return false; } } public sealed class LobbySettingsPanel { private static readonly Vector2 PanelAnchorMin = new Vector2(0.55f, 0.05f); private static readonly Vector2 PanelAnchorMax = new Vector2(0.98f, 0.95f); private const float PanelWidth = 820f; private const float PanelHeight = 1000f; private const float RowWidth = 780f; private const float RowHeight = 44f; private const float SectionHeaderHeight = 52f; private const float SectionGap = 10f; private const float TopPadding = 28f; private const float BottomPadding = 20f; private const float FooterHeight = 48f; private const float LabelFontSize = 28f; private const float SectionFontSize = 34f; private const float ScrollSensitivity = 44f; private const float ScrollbarWidth = 16f; private static readonly Color ScrollbarBgColor = new Color(0.1f, 0.1f, 0.14f, 0.9f); private static readonly Color ScrollbarHandleColor = new Color(0.55f, 0.55f, 0.65f, 0.95f); private const float HintFontSize = 28f; private static readonly Vector2 HintAnchor = new Vector2(0.98f, 0.05f); private static readonly Vector2 HintSize = new Vector2(640f, 40f); private static readonly Color PanelBgColor = new Color(0.02f, 0.02f, 0.05f, 0.85f); private static readonly Color SectionBgColor = new Color(0.2f, 0.2f, 0.25f, 0.9f); private static readonly Color SectionTextColor = new Color(1f, 0.9f, 0.6f, 1f); private static readonly Color LabelTextColor = new Color(0.9f, 0.9f, 0.95f, 1f); private static readonly Color HintTextColor = new Color(0.95f, 0.95f, 0.98f, 1f); private GameObject _root; private GameObject _hintRoot; private RectTransform _content; private ScrollRect _scrollRect; private readonly List _rowObjects = new List(); public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot, string toggleKeyName) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: 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_014f: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Expected O, but got Unknown //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Expected O, but got Unknown //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Expected O, but got Unknown //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Expected O, but got Unknown //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Expected O, but got Unknown //IL_0434: Unknown result type (might be due to invalid IL or missing references) //IL_0440: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_046c: Unknown result type (might be due to invalid IL or missing references) //IL_048e: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Expected O, but got Unknown //IL_04ac: Unknown result type (might be due to invalid IL or missing references) //IL_04b8: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_04d0: Unknown result type (might be due to invalid IL or missing references) //IL_04e3: Unknown result type (might be due to invalid IL or missing references) //IL_0551: Unknown result type (might be due to invalid IL or missing references) //IL_055b: Unknown result type (might be due to invalid IL or missing references) //IL_0560: Unknown result type (might be due to invalid IL or missing references) //IL_0568: Unknown result type (might be due to invalid IL or missing references) //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_057d: Unknown result type (might be due to invalid IL or missing references) //IL_0588: Unknown result type (might be due to invalid IL or missing references) //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_059d: Unknown result type (might be due to invalid IL or missing references) //IL_05a7: Unknown result type (might be due to invalid IL or missing references) //IL_05a8: Unknown result type (might be due to invalid IL or missing references) //IL_05b2: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Unknown result type (might be due to invalid IL or missing references) //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_05e5: Unknown result type (might be due to invalid IL or missing references) //IL_0604: Unknown result type (might be due to invalid IL or missing references) //IL_0613: Expected O, but got Unknown //IL_062c: Unknown result type (might be due to invalid IL or missing references) //IL_0633: Expected O, but got Unknown //IL_063a: Unknown result type (might be due to invalid IL or missing references) //IL_063f: Unknown result type (might be due to invalid IL or missing references) //IL_0647: Unknown result type (might be due to invalid IL or missing references) //IL_0648: Unknown result type (might be due to invalid IL or missing references) //IL_0652: Unknown result type (might be due to invalid IL or missing references) //IL_0653: Unknown result type (might be due to invalid IL or missing references) //IL_065d: Unknown result type (might be due to invalid IL or missing references) //IL_0668: Unknown result type (might be due to invalid IL or missing references) //IL_0672: Unknown result type (might be due to invalid IL or missing references) //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_067d: Unknown result type (might be due to invalid IL or missing references) //IL_067e: Unknown result type (might be due to invalid IL or missing references) //IL_068d: Unknown result type (might be due to invalid IL or missing references) //IL_0692: Unknown result type (might be due to invalid IL or missing references) //IL_06b1: Unknown result type (might be due to invalid IL or missing references) //IL_06c0: Expected O, but got Unknown if ((Object)(object)_root != (Object)null || (Object)(object)layerRoot == (Object)null) { return; } if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } try { GameObject val = new GameObject("WW_LobbySettingsPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = PanelAnchorMin; val2.anchorMax = PanelAnchorMax; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Canvas obj = val.AddComponent(); obj.overrideSorting = true; obj.sortingOrder = 9999; val.AddComponent(); _root = val; GameObject val3 = new GameObject("Bg", new Type[1] { typeof(RectTransform) }); RectTransform val4 = (RectTransform)val3.transform; ((Transform)val4).SetParent((Transform)(object)val2, false); val4.anchorMin = Vector2.zero; val4.anchorMax = Vector2.one; val4.offsetMin = Vector2.zero; val4.offsetMax = Vector2.zero; Image obj2 = val3.AddComponent(); ((Graphic)obj2).color = PanelBgColor; ((Graphic)obj2).raycastTarget = false; GameObject val5 = new GameObject("Scroll", new Type[1] { typeof(RectTransform) }); RectTransform val6 = (RectTransform)val5.transform; ((Transform)val6).SetParent((Transform)(object)val2, false); val6.anchorMin = new Vector2(0f, 0f); val6.anchorMax = new Vector2(1f, 1f); val6.offsetMin = new Vector2(0f, 48f); val6.offsetMax = Vector2.zero; ScrollRect val7 = val5.AddComponent(); val7.horizontal = false; val7.vertical = true; val7.movementType = (MovementType)2; val7.inertia = false; val7.scrollSensitivity = 44f; Image obj3 = val5.AddComponent(); ((Graphic)obj3).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj3).raycastTarget = true; _scrollRect = val7; GameObject val8 = new GameObject("Viewport", new Type[1] { typeof(RectTransform) }); RectTransform val9 = (RectTransform)val8.transform; ((Transform)val9).SetParent((Transform)(object)val6, false); val9.anchorMin = Vector2.zero; val9.anchorMax = Vector2.one; val9.offsetMin = Vector2.zero; val9.offsetMax = new Vector2(-16f, 0f); Image obj4 = val8.AddComponent(); ((Graphic)obj4).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj4).raycastTarget = true; val8.AddComponent(); val7.viewport = val9; RectTransform val10 = (RectTransform)new GameObject("Content", new Type[1] { typeof(RectTransform) }).transform; ((Transform)val10).SetParent((Transform)(object)val9, false); val10.anchorMin = new Vector2(0f, 1f); val10.anchorMax = new Vector2(1f, 1f); val10.pivot = new Vector2(0.5f, 1f); val10.anchoredPosition = Vector2.zero; val10.sizeDelta = new Vector2(0f, 1000f); val7.content = val10; _content = val10; GameObject val11 = new GameObject("Scrollbar", new Type[1] { typeof(RectTransform) }); RectTransform val12 = (RectTransform)val11.transform; ((Transform)val12).SetParent((Transform)(object)val6, false); val12.anchorMin = new Vector2(1f, 0f); val12.anchorMax = new Vector2(1f, 1f); val12.pivot = new Vector2(1f, 0.5f); val12.anchoredPosition = Vector2.zero; val12.sizeDelta = new Vector2(16f, 0f); Image obj5 = val11.AddComponent(); ((Graphic)obj5).color = ScrollbarBgColor; ((Graphic)obj5).raycastTarget = true; RectTransform val13 = (RectTransform)new GameObject("SlidingArea", new Type[1] { typeof(RectTransform) }).transform; ((Transform)val13).SetParent((Transform)(object)val12, false); val13.anchorMin = Vector2.zero; val13.anchorMax = Vector2.one; val13.offsetMin = new Vector2(2f, 2f); val13.offsetMax = new Vector2(-2f, -2f); GameObject val14 = new GameObject("Handle", new Type[1] { typeof(RectTransform) }); RectTransform val15 = (RectTransform)val14.transform; ((Transform)val15).SetParent((Transform)(object)val13, false); val15.anchorMin = Vector2.zero; val15.anchorMax = Vector2.one; val15.offsetMin = Vector2.zero; val15.offsetMax = Vector2.zero; Image val16 = val14.AddComponent(); ((Graphic)val16).color = ScrollbarHandleColor; ((Graphic)val16).raycastTarget = true; Scrollbar val17 = val11.AddComponent(); val17.direction = (Direction)2; ((Selectable)val17).targetGraphic = (Graphic)(object)val16; val17.handleRect = val15; val7.verticalScrollbar = val17; val7.verticalScrollbarVisibility = (ScrollbarVisibility)0; string text = (string.IsNullOrEmpty(toggleKeyName) ? "?" : toggleKeyName); RectTransform val18 = (RectTransform)new GameObject("Footer", new Type[1] { typeof(RectTransform) }).transform; ((Transform)val18).SetParent((Transform)(object)val2, false); val18.anchorMin = new Vector2(0f, 0f); val18.anchorMax = new Vector2(1f, 0f); val18.pivot = new Vector2(0.5f, 0f); val18.anchoredPosition = Vector2.zero; val18.sizeDelta = new Vector2(0f, 48f); UiKit.CreateText((Transform)val18, "ToggleHintFooter", new Vector2(0f, 24f), new Vector2(780f, 40f), Texts.Format(TextId.LobbySettingsFooterHintFormat, text), 28f, HintTextColor, (TextAlignmentOptions)516); GameObject val19 = new GameObject("WW_LobbySettingsHint", new Type[1] { typeof(RectTransform) }); RectTransform val20 = (RectTransform)val19.transform; ((Transform)val20).SetParent(layerRoot, false); val20.anchorMin = HintAnchor; val20.anchorMax = HintAnchor; val20.pivot = new Vector2(1f, 1f); val20.anchoredPosition = Vector2.zero; val20.sizeDelta = HintSize; UiKit.CreateText((Transform)val20, "ToggleHintLabel", Vector2.zero, HintSize, Texts.Format(TextId.LobbySettingsMiniHintFormat, text), 28f, HintTextColor, (TextAlignmentOptions)516); _hintRoot = val19; _root.SetActive(false); _hintRoot.SetActive(false); WLog.Line("lobby_settings_panel_built", false); } catch (Exception ex) { WLog.Line("lobby_settings_panel_error", false, ("reason", "build_failed"), ("err", ex.Message)); Destroy(); } } public void SetRows(IReadOnlyList rows) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_content == (Object)null) { return; } ClearRows(); if (rows == null || rows.Count == 0) { _content.sizeDelta = new Vector2(_content.sizeDelta.x, 1000f); return; } float num = 28f; string text = null; for (int i = 0; i < rows.Count; i++) { SettingRow settingRow = rows[i]; if (settingRow == null) { continue; } if (!string.Equals(settingRow.Section, text, StringComparison.Ordinal)) { if (text != null) { num += 10f; } num += 52f; text = settingRow.Section; } num += 44f; } num += 20f; _content.sizeDelta = new Vector2(_content.sizeDelta.x, num); float num2 = num * 0.5f - 28f; string text2 = null; for (int j = 0; j < rows.Count; j++) { SettingRow settingRow2 = rows[j]; if (settingRow2 == null) { continue; } if (!string.Equals(settingRow2.Section, text2, StringComparison.Ordinal)) { if (text2 != null) { num2 -= 10f; } float centerY = num2 - 26f; BuildSectionHeader((Transform)(object)_content, settingRow2.Section, centerY); num2 -= 52f; text2 = settingRow2.Section; } float centerY2 = num2 - 22f; BuildRow((Transform)(object)_content, settingRow2.Label, settingRow2.Value, centerY2); num2 -= 44f; } if ((Object)(object)_scrollRect != (Object)null) { _scrollRect.verticalNormalizedPosition = 1f; } } public void SetVisibility(bool panelVisible, bool hintVisible) { if (!((Object)(object)_root == (Object)null) && (_root.activeSelf != panelVisible || ((Object)(object)_hintRoot != (Object)null && _hintRoot.activeSelf != hintVisible))) { _root.SetActive(panelVisible); if ((Object)(object)_hintRoot != (Object)null) { _hintRoot.SetActive(hintVisible); } if (panelVisible && (Object)(object)_scrollRect != (Object)null) { _scrollRect.verticalNormalizedPosition = 1f; } WLog.Line("lobby_settings_panel_visible", false, ("visible", panelVisible), ("hint", hintVisible)); } } public void Destroy() { ClearRows(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } if ((Object)(object)_hintRoot != (Object)null) { Object.Destroy((Object)(object)_hintRoot); _hintRoot = null; } _content = null; _scrollRect = null; } private void ClearRows() { for (int i = 0; i < _rowObjects.Count; i++) { GameObject val = _rowObjects[i]; if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } _rowObjects.Clear(); } private void BuildSectionHeader(Transform parent, string sectionName, float centerY) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) Image val = UiKit.CreateImage(parent, "Section_Bg_" + sectionName, new Vector2(0f, centerY), new Vector2(780f, 52f), SectionBgColor); _rowObjects.Add(((Component)val).gameObject); TextMeshProUGUI val2 = UiKit.CreateText(parent, "Section_Label_" + sectionName, new Vector2(0f, centerY), new Vector2(756f, 52f), sectionName ?? string.Empty, 34f, SectionTextColor, (TextAlignmentOptions)514); _rowObjects.Add(((Component)val2).gameObject); } private void BuildRow(Transform parent, string label, string value, float centerY) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrEmpty(value) ? (label ?? string.Empty) : (label + ": " + value)); TextMeshProUGUI val = UiKit.CreateText(parent, "Row", new Vector2(0f, centerY), new Vector2(780f, 44f), text, 28f, LabelTextColor, (TextAlignmentOptions)513); _rowObjects.Add(((Component)val).gameObject); } } public sealed class LobbyStartWarning : IClientPanel { private const float ButtonRowY = -160f; private GameObject _root; private TextMeshProUGUI _title; private TextMeshProUGUI _body; private Image _panelBackground; private RectTransform _backRect; private RectTransform _detailsRect; private RectTransform _continueRect; private Action _onBack; private Action _onDetails; private Action _onContinue; public string LayerName => "ModIntegrityModal"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_LobbyStartWarning", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Canvas obj = val.AddComponent(); obj.overrideSorting = true; obj.sortingOrder = 9999; val.AddComponent(); Image obj2 = val.AddComponent(); ((Graphic)obj2).color = new Color(0f, 0f, 0f, 0.72f); ((Graphic)obj2).raycastTarget = true; _root = val; RectTransform val3 = UiKit.CreateRect((Transform)val2, "Panel", Vector2.zero, new Vector2(980f, 460f)); _panelBackground = UiKit.CreateImage((Transform)(object)val3, "Bg", Vector2.zero, val3.sizeDelta, new Color(0.26f, 0.16f, 0.03f, 0.99f)); _title = UiKit.CreateText((Transform)(object)val3, "Title", new Vector2(0f, 150f), new Vector2(900f, 70f), string.Empty, 38f, Color.white, (TextAlignmentOptions)514); _body = UiKit.CreateText((Transform)(object)val3, "Body", new Vector2(0f, 20f), new Vector2(860f, 190f), string.Empty, 27f, Color.white, (TextAlignmentOptions)514); ((TMP_Text)_title).richText = false; ((TMP_Text)_body).richText = false; _backRect = BuildButton((Transform)(object)val3, "Back", new Vector2(-320f, -160f), new Vector2(220f, 58f), Texts.Get(TextId.ModIntegrityButtonBack)); _detailsRect = BuildButton((Transform)(object)val3, "Details", new Vector2(0f, -160f), new Vector2(260f, 58f), Texts.Get(TextId.ModIntegrityButtonDetails)); _continueRect = BuildButton((Transform)(object)val3, "Continue", new Vector2(320f, -160f), new Vector2(280f, 58f), Texts.Get(TextId.ModIntegrityButtonContinue)); _root.SetActive(false); } } public void Show(LobbyStartDecision decision, Action onBack, Action onDetails, Action onContinue) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { throw new InvalidOperationException("warning_not_built"); } ((TMP_Text)_title).text = ResolveTitle(decision); ((TMP_Text)_body).text = ResolveBody(decision); ((Graphic)_panelBackground).color = ((decision.WarningKind == LobbyStartWarningKind.Caution) ? new Color(0.35f, 0.24f, 0.03f, 0.99f) : new Color(0.37f, 0.045f, 0.055f, 0.99f)); LayoutButtons(decision.ModWarningKind != LobbyStartWarningKind.None, decision.AllowsContinue); _onBack = onBack; _onDetails = onDetails; _onContinue = onContinue; _root.SetActive(true); } private void LayoutButtons(bool showDetails, bool showContinue) { ((Component)_detailsRect).gameObject.SetActive(showDetails); ((Component)_continueRect).gameObject.SetActive(showContinue); int num = 1 + (showDetails ? 1 : 0) + (showContinue ? 1 : 0); float num2 = ((num == 3) ? 320f : 200f); float left = (0f - num2) * (float)(num - 1) * 0.5f; int index = 0; PlaceButton(_backRect, left, ref index, num2); if (showDetails) { PlaceButton(_detailsRect, left, ref index, num2); } if (showContinue) { PlaceButton(_continueRect, left, ref index, num2); } } private static void PlaceButton(RectTransform rect, float left, ref int index, float spacing) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) rect.anchoredPosition = new Vector2(left + spacing * (float)index, -160f); index++; } private static string ResolveTitle(LobbyStartDecision decision) { if (decision.HasPlayerShortfall) { return Texts.Get(TextId.LobbyStartTooFewPlayersTitle); } if (decision.HasTeamOverflow) { return Texts.Get(TextId.LobbyStartTeamOverflowTitle); } if (decision.WarningKind != LobbyStartWarningKind.Caution) { return Texts.Get(TextId.ModIntegrityStartSevereTitle); } return Texts.Get(TextId.ModIntegrityStartCautionTitle); } private static string ResolveBody(LobbyStartDecision decision) { string text = string.Empty; if (decision.HasPlayerShortfall) { text = Texts.Format(TextId.LobbyStartTooFewPlayersBodyFormat, decision.PlayerCount, decision.RequiredPlayerCount); } if (decision.HasTeamOverflow) { if (text.Length > 0) { text += "\n"; } text += Texts.Format(TextId.LobbyStartTeamOverflowBodyFormat, decision.WerewolfCount, decision.PlayerCount); } if (decision.ModWarningKind != LobbyStartWarningKind.None) { if (text.Length > 0) { text += "\n"; } text += Texts.Format(TextId.ModIntegrityStartBodyFormat, decision.DifferenceCount, decision.UnavailableCount, decision.PendingCount); } return text; } public void Tick() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_005e: Unknown result type (might be due to invalid IL or missing references) if (!Visible) { return; } UiKit.KeepCursorFree(); if (Input.GetMouseButtonDown(0)) { Vector2 point = Vector2.op_Implicit(Input.mousePosition); if (Hit(_backRect, point)) { InvokeAndHide(_onBack); } else if (Hit(_detailsRect, point)) { InvokeAndHide(_onDetails); } else if (Hit(_continueRect, point)) { InvokeAndHide(_onContinue); } } } public void Hide() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } _onBack = null; _onDetails = null; _onContinue = null; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _title = null; _body = null; _panelBackground = null; _backRect = null; _detailsRect = null; _continueRect = null; _onBack = null; _onDetails = null; _onContinue = null; } private static RectTransform BuildButton(Transform parent, string name, Vector2 position, Vector2 size, string text) { //IL_0002: 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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) Image obj = UiKit.CreateImage(parent, name, position, size, new Color(0.16f, 0.18f, 0.24f, 1f)); ((TMP_Text)UiKit.CreateText((Transform)(object)((Graphic)obj).rectTransform, "Label", Vector2.zero, size, text, 23f, Color.white, (TextAlignmentOptions)514)).richText = false; return ((Graphic)obj).rectTransform; } private void InvokeAndHide(Action callback) { Hide(); callback?.Invoke(); } private static bool Hit(RectTransform rect, Vector2 point) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)rect != (Object)null && ((Component)rect).gameObject.activeInHierarchy) { return RectTransformUtility.RectangleContainsScreenPoint(rect, point, (Camera)null); } return false; } } public sealed class ManualOverlay : IClientPanel { private const float PanelWidth = 1100f; private const float PanelHeight = 760f; private const float TitleFontSize = 40f; private const float BodyFontSize = 26f; private const float FooterFontSize = 24f; private const float HintFontSize = 18f; private const float ImageFrameWidth = 640f; private const float ImageDefaultHeight = 240f; private const float ImageTopY = 300f; private const float ImageBodyGap = 20f; private const float BodyWidth = 1000f; private const float BodyBottomY = -310f; private static readonly Vector2 BodyPosNoImage = new Vector2(0f, -5f); private static readonly Vector2 BodySizeNoImage = new Vector2(1000f, 610f); private const float BlockIconSize = 104f; private const float BlockIconTextGap = 6f; private const float BlockGap = 16f; private const float BlockLineHeightFallback = 36f; private const float IconLeftMargin = 40f; private const float IconTopMargin = 364f; private const float IconLobbyLeftMargin = 20f; private const float IconLobbyTopMargin = 20f; private static readonly IconToggleStyle ToggleStyle = new IconToggleStyle(72f, 170f, 28f, 22f, new Color(1f, 0.9f, 0.6f, 1f), "?", new Color(0.08f, 0.08f, 0.1f, 0.55f), new Color(0.95f, 0.95f, 0.95f, 0.95f)); private static readonly Color PanelFrameColor = new Color(0.02f, 0.02f, 0.05f, 0.97f); private static readonly Color TitleColor = new Color(1f, 0.95f, 0.7f, 1f); private static readonly Color BodyColor = new Color(1f, 1f, 1f, 0.95f); private static readonly Color FooterColor = new Color(1f, 1f, 1f, 0.85f); private static readonly Color HintColor = new Color(1f, 1f, 1f, 0.6f); private GameObject _root; private GameObject _panel; private RectTransform _panelRect; private TextMeshProUGUI _titleText; private Image _pageImage; private RectTransform _bodyRect; private TextMeshProUGUI _footerText; private TextMeshProUGUI _hintText; private RectTransform _iconContainer; private readonly IconToggleButton _toggle = new IconToggleButton(); private bool _open; private int _pageIndex; private KeyCode _lastLabelKey; private Vector2 _positionOffset; private Vector2 _appliedOffset; private bool _offsetApplied; private bool _lobbyLayout; public string LayerName => "Manual"; public bool Exists => (Object)(object)_root != (Object)null; public bool IsOpen => _open; public Vector2 PositionOffset { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return _positionOffset; } set { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _positionOffset = value; } } public void Build(Transform layerRoot) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } RectTransform val = UiKit.CreateRect(layerRoot, "WW_ManualOverlay", Vector2.zero, new Vector2(1920f, 1080f)); _root = ((Component)val).gameObject; Canvas obj = _root.AddComponent(); obj.overrideSorting = true; obj.sortingOrder = 10000; RectTransform val2 = UiKit.CreateRect((Transform)(object)val, "ManualPanel", Vector2.zero, new Vector2(1100f, 760f)); _panel = ((Component)val2).gameObject; _panelRect = val2; UiKit.CreateImage((Transform)(object)val2, "PanelBg", Vector2.zero, new Vector2(1112f, 772f), PanelFrameColor); _titleText = UiKit.CreateText((Transform)(object)val2, "Title", new Vector2(0f, 330f), new Vector2(1000f, 60f), "", 40f, TitleColor, (TextAlignmentOptions)514); _pageImage = UiKit.CreateImage((Transform)(object)val2, "PageImage", new Vector2(0f, 180f), new Vector2(640f, 240f), Color.white); _pageImage.preserveAspect = true; ((Behaviour)_pageImage).enabled = false; _bodyRect = UiKit.CreateRect((Transform)(object)val2, "BodyArea", BodyPosNoImage, BodySizeNoImage); _footerText = UiKit.CreateText((Transform)(object)val2, "Footer", new Vector2(0f, -335f), new Vector2(400f, 30f), "", 24f, FooterColor, (TextAlignmentOptions)514); _hintText = UiKit.CreateText((Transform)(object)val2, "NavHint", new Vector2(0f, -364f), new Vector2(1000f, 26f), "", 18f, HintColor, (TextAlignmentOptions)514); _panel.SetActive(false); _iconContainer = _toggle.Build((Transform)(object)val, "ToggleButton", ToggleStyle, "manual_icon", Texts.Format(TextId.ManualToggleLabelFormat, "-")); RectTransform iconContainer = _iconContainer; RectTransform iconContainer2 = _iconContainer; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 1f); iconContainer2.anchorMax = val3; iconContainer.anchorMin = val3; ApplyIconOffset(force: true); WLog.Line("manual_built", false, ("pages", ManualCatalog.PageCount), ("icon", _toggle.HasIcon ? 1 : 0)); } } public void Tick(KeyCode key, bool available, bool suppressed, bool lobbyMenu) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Invalid comparison between Unknown and I4 //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { if (_lobbyLayout != lobbyMenu) { _lobbyLayout = lobbyMenu; _offsetApplied = false; } ApplyIconOffset(force: false); if (key != _lastLabelKey) { _toggle.SetLabel(Texts.Format(TextId.ManualToggleLabelFormat, FormatKey(key))); if ((Object)(object)_hintText != (Object)null) { ((TMP_Text)_hintText).text = BuildNavHintText(key); } _lastLabelKey = key; } bool flag = available && !suppressed; if ((Object)(object)_iconContainer != (Object)null && ((Component)_iconContainer).gameObject.activeSelf != flag) { ((Component)_iconContainer).gameObject.SetActive(flag); } if (!available || suppressed) { _toggle.SetHover(over: false); if (_open) { CloseInternal(); } return; } bool flag2 = (int)Cursor.lockState != 1; _toggle.SetHover(flag2 && _toggle.IsPointerOverOpaqueIcon()); bool flag3 = NoTextInputsActive(); bool num = flag3 && (int)key != 0 && Input.GetKeyDown(key); bool flag4 = flag2 && _toggle.WasClicked(); if (num || flag4) { if (_open) { CloseInternal(); } else { OpenInternal(); } } else { if (!_open) { return; } SuppressEscMenu(); if (flag3 && MenuKeyDown()) { CloseInternal(); return; } bool flag5 = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); if (flag3 && Input.GetKeyDown((KeyCode)276)) { ShowPage(flag5 ? ManualCatalog.PreviousSectionStart(_pageIndex) : ManualCatalog.ClampIndex(_pageIndex - 1)); } if (flag3 && Input.GetKeyDown((KeyCode)275)) { ShowPage(flag5 ? ManualCatalog.NextSectionStart(_pageIndex) : ManualCatalog.ClampIndex(_pageIndex + 1)); } } } catch (Exception ex) { WLog.Line("manual_tick_error", false, ("err", ex.Message)); } } public bool IsPointerOverPanel(Vector2 screenPoint) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!_open || (Object)(object)_panelRect == (Object)null) { return false; } return RectTransformUtility.RectangleContainsScreenPoint(_panelRect, screenPoint, (Camera)null); } public void ForceClose() { if (_open) { CloseInternal(); } } public void Destroy() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) _open = false; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _panel = null; _panelRect = null; _titleText = null; _pageImage = null; _bodyRect = null; _footerText = null; _hintText = null; _iconContainer = null; _toggle.Clear(); _lastLabelKey = (KeyCode)0; _offsetApplied = false; _lobbyLayout = false; } private void OpenInternal() { _open = true; _panel.SetActive(true); ShowPage(ManualCatalog.ClampIndex(_pageIndex)); WLog.Line("manual_open", false, ("page", _pageIndex + 1)); } private void CloseInternal() { _open = false; if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } WLog.Line("manual_close", false); } private void ShowPage(int index) { //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) _pageIndex = index; ManualPage manualPage = ManualCatalog.Pages[index]; ((TMP_Text)_titleText).text = Texts.Get(manualPage.TitleId); ManualSection manualSection = ManualCatalog.Sections[ManualCatalog.SectionIndexForPage(index)]; ((TMP_Text)_footerText).text = Texts.Format(TextId.ManualPageFooterFormat, Texts.Get(manualSection.TitleId), index + 1, ManualCatalog.PageCount); Sprite val = ((manualPage.ImageKey != null) ? AssetCatalog.GetSprite(manualPage.ImageKey) : null); Vector2 bodySizeNoImage = default(Vector2); if ((Object)(object)val != (Object)null) { float num = ((manualPage.ImageHeight > 0f) ? manualPage.ImageHeight : 240f); float num2 = ((manualPage.ImageWidth > 0f) ? manualPage.ImageWidth : 640f); _pageImage.sprite = val; ((Behaviour)_pageImage).enabled = true; ((Graphic)_pageImage).rectTransform.sizeDelta = new Vector2(num2, num); ((Graphic)_pageImage).rectTransform.anchoredPosition = new Vector2(0f, 300f - num * 0.5f); float num3 = 300f - num - 20f; ((Vector2)(ref bodySizeNoImage))..ctor(1000f, num3 - -310f); _bodyRect.anchoredPosition = new Vector2(0f, (num3 + -310f) * 0.5f); _bodyRect.sizeDelta = bodySizeNoImage; } else { _pageImage.sprite = null; ((Behaviour)_pageImage).enabled = false; bodySizeNoImage = BodySizeNoImage; _bodyRect.anchoredPosition = BodyPosNoImage; _bodyRect.sizeDelta = bodySizeNoImage; } RebuildBlocks(manualPage, bodySizeNoImage); } private void RebuildBlocks(ManualPage page, Vector2 areaSize) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0137: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_00b0: Unknown result type (might be due to invalid IL or missing references) for (int num = ((Transform)_bodyRect).childCount - 1; num >= 0; num--) { Object.DestroyImmediate((Object)(object)((Component)((Transform)_bodyRect).GetChild(num)).gameObject); } float x = areaSize.x; float num2 = 0f; ManualBlock[] blocks = page.Blocks; foreach (ManualBlock manualBlock in blocks) { if (manualBlock.IconKey != null) { Sprite sprite = AssetCatalog.GetSprite(manualBlock.IconKey); if ((Object)(object)sprite != (Object)null) { Image obj = UiKit.CreateImage((Transform)(object)_bodyRect, "BlockIcon", Vector2.zero, new Vector2(104f, 104f), Color.white); obj.sprite = sprite; obj.preserveAspect = true; PlaceTopLeft(((Graphic)obj).rectTransform, areaSize, 0f, num2, 104f, 104f); num2 += 110f; } } string text = Texts.Get(manualBlock.BodyId); TextMeshProUGUI obj2 = UiKit.CreateText((Transform)(object)_bodyRect, "BlockText", Vector2.zero, new Vector2(x, 36f), text, 26f, BodyColor, (TextAlignmentOptions)257); ((TMP_Text)obj2).enableWordWrapping = true; float num3 = MeasureTextHeight(obj2, text, x); ((TMP_Text)obj2).rectTransform.sizeDelta = new Vector2(x, num3); PlaceTopLeft(((TMP_Text)obj2).rectTransform, areaSize, 0f, num2, x, num3); num2 += num3 + 16f; } float num4 = num2 - 16f; if (num4 > areaSize.y) { WLog.Line("manual_page_overflow", false, ("page", _pageIndex + 1), ("height", (int)num4), ("area", (int)areaSize.y)); } } private static float MeasureTextHeight(TextMeshProUGUI label, string text, float width) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) float num = 0f; try { num = ((TMP_Text)label).GetPreferredValues(text, width, 0f).y; } catch { num = 0f; } if (num > 0f) { return Mathf.Ceil(num); } int num2 = 1; for (int i = 0; i < text.Length; i++) { if (text[i] == '\n') { num2++; } } return (float)num2 * 36f; } private static void PlaceTopLeft(RectTransform rect, Vector2 areaSize, float x, float y, float w, float h) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) rect.anchoredPosition = new Vector2((0f - areaSize.x) * 0.5f + x + w * 0.5f, areaSize.y * 0.5f - y - h * 0.5f); } private void ApplyIconOffset(bool force) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_0091: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_iconContainer == (Object)null) && (force || !_offsetApplied || !(_appliedOffset == _positionOffset))) { Vector2 sizeDelta = _iconContainer.sizeDelta; float num = (_lobbyLayout ? 20f : (40f + _positionOffset.x)); float num2 = (_lobbyLayout ? 20f : (364f + _positionOffset.y)); _iconContainer.anchoredPosition = new Vector2(num + sizeDelta.x * 0.5f, 0f - (num2 + sizeDelta.y * 0.5f)); _appliedOffset = _positionOffset; _offsetApplied = true; } } private static void SuppressEscMenu() { try { GameDirector instance = GameDirector.instance; if ((Object)(object)instance != (Object)null) { instance.SetDisableEscMenu(1f); } } catch { } } private static bool MenuKeyDown() { try { return SemiFunc.InputDown((InputKey)6); } catch { return false; } } private static bool NoTextInputsActive() { try { return SemiFunc.NoTextInputsActive(); } catch { return true; } } private static string BuildNavHintText(KeyCode key) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) string text = Texts.Format(TextId.ManualNavHint, FormatKey(key)); try { InputManager instance = InputManager.instance; if ((Object)(object)instance != (Object)null) { text = instance.InputDisplayReplaceTags(text, "", ""); } } catch { } return text; } private unsafe static string FormatKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)key != 0) { return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); } return "-"; } } public sealed class MeetingChatPanel : IClientPanel { private const float PanelWidth = 330f; private const float PanelHeight = 690f; private const float PanelMarginRight = 24f; private const float PanelOffsetY = -10f; private const float ToggleButtonMargin = 24f; private static readonly IconToggleStyle ToggleStyle = new IconToggleStyle(96f, 320f, 32f, 24f, new Color(0.95f, 0.95f, 1f, 0.95f), "LOG", new Color(0.15f, 0.15f, 0.2f, 0.85f), new Color(0.9f, 0.9f, 0.95f, 1f)); private const float HeaderHeight = 44f; private const float FooterHeight = 34f; private const float ScrollbarWidth = 12f; private const float SidePadding = 8f; private const float BlockGap = 5f; private const float SpeakerGap = 10f; private const float RecapPadding = 8f; private const float VirtualOverscan = 240f; private const float TitleFontSize = 26f; private const float RecapFontSize = 18f; private const float HintFontSize = 17f; private const float ScrollSensitivity = 40f; private const float BottomStickSlack = 24f; private static readonly Color PanelBgColor = new Color(0.02f, 0.02f, 0.05f, 0.85f); private static readonly Color HeaderBgColor = new Color(0.2f, 0.2f, 0.25f, 0.9f); private static readonly Color TitleTextColor = new Color(1f, 0.9f, 0.6f, 1f); private static readonly Color HintTextColor = new Color(0.72f, 0.74f, 0.82f, 1f); private static readonly Color ScrollbarBgColor = new Color(0.1f, 0.1f, 0.14f, 0.9f); private static readonly Color ScrollbarHandleColor = new Color(0.55f, 0.55f, 0.65f, 0.95f); private static readonly Color RecapBgColor = new Color(0.1f, 0.1f, 0.17f, 0.92f); private static readonly Color RecapBodyColor = new Color(0.86f, 0.88f, 0.93f, 1f); private GameObject _root; private GameObject _panel; private RectTransform _panelRect; private readonly IconToggleButton _toggle = new IconToggleButton(); private KeyCode _labelKey; private RectTransform _recapBox; private TextMeshProUGUI _recapText; private RectTransform _scrollRect; private TextMeshProUGUI _footerHint; private GameObject _emptyHint; private readonly ChatLayout _layout = new ChatLayout(new ChatLayoutMetrics(210f, 40f, ContentWidth - 16f, 36f, 5f, 10f)); private readonly ChatScrollView _view = new ChatScrollView(240f, 8f, 24f); private readonly ChatRowFactory _rows = new ChatRowFactory(ContentWidth, 8f); private Func _measureFn; private Func _createBlockFn; private long _renderedRecapSignature = long.MinValue; private int _renderedVersion = int.MinValue; private bool _renderedLocalDead; private bool _scrollToBottomPending; private MeetingChatLog _log; private bool _hasRenderState; private bool _open = true; public bool Exists => (Object)(object)_root != (Object)null; public bool IsOpen => _open; public string LayerName => "Meeting"; private static float ContentWidth => 318f; public void Build(Transform layerRoot) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012a: 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_0157: 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_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Expected O, but got Unknown //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_0377: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Expected O, but got Unknown //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_0449: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Unknown result type (might be due to invalid IL or missing references) //IL_0454: Unknown result type (might be due to invalid IL or missing references) //IL_045b: Expected O, but got Unknown //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047f: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_049f: Unknown result type (might be due to invalid IL or missing references) //IL_04ba: Unknown result type (might be due to invalid IL or missing references) //IL_04f1: Unknown result type (might be due to invalid IL or missing references) //IL_04fb: Unknown result type (might be due to invalid IL or missing references) //IL_0502: Expected O, but got Unknown //IL_0518: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Unknown result type (might be due to invalid IL or missing references) //IL_0544: Unknown result type (might be due to invalid IL or missing references) //IL_0550: Unknown result type (might be due to invalid IL or missing references) //IL_055c: Unknown result type (might be due to invalid IL or missing references) //IL_05a7: Unknown result type (might be due to invalid IL or missing references) //IL_05b6: Unknown result type (might be due to invalid IL or missing references) //IL_05c5: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { GameObject val = new GameObject("WW_MeetingChatRoot", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Stretch(val2); _root = val; GameObject val3 = new GameObject("WW_MeetingChatPanel", new Type[1] { typeof(RectTransform) }); RectTransform val4 = (RectTransform)val3.transform; ((Transform)val4).SetParent((Transform)(object)val2, false); Vector2 val5 = default(Vector2); ((Vector2)(ref val5))..ctor(1f, 0.5f); val4.anchorMax = val5; val4.anchorMin = val5; val4.pivot = new Vector2(1f, 0.5f); val4.anchoredPosition = new Vector2(-24f, -10f); val4.sizeDelta = new Vector2(330f, 690f); Canvas obj = val3.AddComponent(); obj.overrideSorting = true; obj.sortingOrder = 9999; val3.AddComponent(); _panel = val3; _panelRect = val4; Stretch(((Graphic)UiKit.CreateImage((Transform)(object)val4, "Bg", Vector2.zero, new Vector2(330f, 690f), PanelBgColor)).rectTransform); float num = 323f; UiKit.CreateImage((Transform)(object)val4, "HeaderBg", new Vector2(0f, num), new Vector2(330f, 44f), HeaderBgColor); UiKit.CreateText((Transform)(object)val4, "Title", new Vector2(0f, num), new Vector2(314f, 36f), Texts.Get(TextId.ChatLogTitle), 26f, TitleTextColor, (TextAlignmentOptions)514); GameObject val6 = new GameObject("RecapBox", new Type[1] { typeof(RectTransform) }); RectTransform val7 = (RectTransform)val6.transform; ((Transform)val7).SetParent((Transform)(object)val4, false); val7.anchorMin = new Vector2(0f, 1f); val7.anchorMax = new Vector2(1f, 1f); val7.pivot = new Vector2(0.5f, 1f); val7.offsetMin = new Vector2(8f, 0f); val7.offsetMax = new Vector2(-8f, 0f); val7.anchoredPosition = new Vector2(0f, -48f); val7.sizeDelta = new Vector2(-16f, 0f); Image obj2 = val6.AddComponent(); ((Graphic)obj2).color = RecapBgColor; obj2.sprite = UiKit.RoundedRectSprite(); obj2.type = (Type)1; ((Graphic)obj2).raycastTarget = false; _recapBox = val7; _recapText = UiKit.CreateText((Transform)(object)val7, "RecapText", Vector2.zero, new Vector2(100f, 20f), string.Empty, 18f, RecapBodyColor, (TextAlignmentOptions)257); ((TMP_Text)_recapText).enableWordWrapping = true; RectTransform rectTransform = ((TMP_Text)_recapText).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(0f, 1f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.anchoredPosition = new Vector2(8f, -8f); GameObject val8 = new GameObject("Scroll", new Type[1] { typeof(RectTransform) }); RectTransform val9 = (RectTransform)val8.transform; ((Transform)val9).SetParent((Transform)(object)val4, false); val9.anchorMin = Vector2.zero; val9.anchorMax = Vector2.one; val9.offsetMin = new Vector2(0f, 34f); val9.offsetMax = new Vector2(0f, -44f); ScrollRect val10 = val8.AddComponent(); val10.horizontal = false; val10.vertical = true; val10.movementType = (MovementType)2; val10.inertia = false; val10.scrollSensitivity = 40f; Image obj3 = val8.AddComponent(); ((Graphic)obj3).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj3).raycastTarget = true; _scrollRect = val9; GameObject val11 = new GameObject("Viewport", new Type[1] { typeof(RectTransform) }); RectTransform val12 = (RectTransform)val11.transform; ((Transform)val12).SetParent((Transform)(object)val9, false); val12.anchorMin = Vector2.zero; val12.anchorMax = Vector2.one; val12.offsetMin = Vector2.zero; val12.offsetMax = new Vector2(-12f, 0f); Image obj4 = val11.AddComponent(); ((Graphic)obj4).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj4).raycastTarget = true; val11.AddComponent(); val10.viewport = val12; RectTransform val13 = (RectTransform)new GameObject("Content", new Type[1] { typeof(RectTransform) }).transform; ((Transform)val13).SetParent((Transform)(object)val12, false); val13.anchorMin = new Vector2(0f, 1f); val13.anchorMax = new Vector2(1f, 1f); val13.pivot = new Vector2(0.5f, 1f); val13.anchoredPosition = Vector2.zero; val13.sizeDelta = Vector2.zero; val10.content = val13; _view.Attach(val10, val12, val13); _rows.Attach(val13); BuildScrollbar(val9, val10); _footerHint = UiKit.CreateText((Transform)(object)val4, "DeadHint", new Vector2(0f, -328f), new Vector2(318f, 34f), string.Empty, 17f, HintTextColor, (TextAlignmentOptions)514); ((TMP_Text)_footerHint).enableWordWrapping = true; _rows.Build((Transform)(object)val2); _measureFn = _rows.MeasureBubble; _createBlockFn = CreateBlockObject; BuildToggleButton(val2); _panel.SetActive(_open); WLog.Line("chat_panel_built", false, ("icon", _toggle.HasIcon ? 1 : 0)); } } private void BuildToggleButton(RectTransform containerRect) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) RectTransform obj = _toggle.Build((Transform)(object)containerRect, "ToggleButton", ToggleStyle, "icon_chat_log", Texts.Format(TextId.ChatLogToggleLabelFormat, "L")); Vector2 sizeDelta = obj.sizeDelta; obj.anchoredPosition = new Vector2(960f - sizeDelta.x * 0.5f - 24f, -540f + sizeDelta.y * 0.5f + 24f); } private static void Stretch(RectTransform rect) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } private static void BuildScrollbar(RectTransform scrollRect, ScrollRect scroll) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_003d: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Expected O, but got Unknown //IL_014a: 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_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Scrollbar", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent((Transform)(object)scrollRect, false); val2.anchorMin = new Vector2(1f, 0f); val2.anchorMax = new Vector2(1f, 1f); val2.pivot = new Vector2(1f, 0.5f); val2.anchoredPosition = Vector2.zero; val2.sizeDelta = new Vector2(12f, 0f); Image obj = val.AddComponent(); ((Graphic)obj).color = ScrollbarBgColor; ((Graphic)obj).raycastTarget = true; RectTransform val3 = (RectTransform)new GameObject("SlidingArea", new Type[1] { typeof(RectTransform) }).transform; ((Transform)val3).SetParent((Transform)(object)val2, false); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.offsetMin = new Vector2(2f, 2f); val3.offsetMax = new Vector2(-2f, -2f); GameObject val4 = new GameObject("Handle", new Type[1] { typeof(RectTransform) }); RectTransform val5 = (RectTransform)val4.transform; ((Transform)val5).SetParent((Transform)(object)val3, false); val5.anchorMin = Vector2.zero; val5.anchorMax = Vector2.one; val5.offsetMin = Vector2.zero; val5.offsetMax = Vector2.zero; Image val6 = val4.AddComponent(); ((Graphic)val6).color = ScrollbarHandleColor; ((Graphic)val6).raycastTarget = true; Scrollbar val7 = val.AddComponent(); val7.direction = (Direction)2; ((Selectable)val7).targetGraphic = (Graphic)(object)val6; val7.handleRect = val5; scroll.verticalScrollbar = val7; scroll.verticalScrollbarVisibility = (ScrollbarVisibility)0; } public void Tick(KeyCode key, bool keysFree) { //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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } SyncVirtualization(); _rows.TickAvatarFreeze(); if (_labelKey != key) { _labelKey = key; _toggle.SetLabel(Texts.Format(TextId.ChatLogToggleLabelFormat, FormatKey(key))); } bool flag = _toggle.IsPointerOverHitArea(); _toggle.SetHover(flag); bool num = keysFree && (int)key != 0 && Input.GetKeyDown(key); bool flag2 = Input.GetMouseButtonDown(0) && flag; if (num || flag2) { _open = !_open; if ((Object)(object)_panel != (Object)null) { _panel.SetActive(_open); } if (_open) { _scrollToBottomPending = true; } else { ClearLiveBlocks(); } WLog.Line("chat_panel_toggle", false, ("open", _open)); } } public bool IsPointerOverPanel(Vector2 screenPoint) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return false; } if (_open && (Object)(object)_panelRect != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_panelRect, screenPoint, (Camera)null)) { return true; } return _toggle.ContainsRect(screenPoint); } private unsafe static string FormatKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)key != 0) { return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); } return "-"; } public void Render(MeetingChatLog log, MeetingRecapData recap, int localActor, Func resolveAvatar, bool localDead) { if ((Object)(object)_root == (Object)null || !_view.Attached) { return; } _log = log; _rows.SetContext(localActor, resolveAvatar); _hasRenderState = true; bool flag = _open && _view.IsAtBottom(); long signature = recap.Signature; bool flag2 = _open && signature != _renderedRecapSignature; if (flag2) { RenderRecap(recap); _renderedRecapSignature = signature; } if (localDead != _renderedLocalDead) { _renderedLocalDead = localDead; if ((Object)(object)_footerHint != (Object)null) { ((TMP_Text)_footerHint).text = (localDead ? Texts.Get(TextId.ChatLogDeadHint) : string.Empty); } } _layout.Sync(log, _measureFn); if (_open) { bool flag3 = _layout.Version != _renderedVersion; if (flag3) { _renderedVersion = _layout.Version; ApplyContentHeight(); UpdateEmptyHint(); } if (flag && (flag2 || flag3)) { _scrollToBottomPending = true; } if (_scrollToBottomPending) { _scrollToBottomPending = false; _view.ScrollToBottom(); } SyncVirtualization(); } } private void RenderRecap(MeetingRecapData recap) { //IL_0034: 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_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_recapText == (Object)null) && !((Object)(object)_recapBox == (Object)null)) { List list = MeetingRecap.BuildLines(recap); string text = string.Concat("" + Texts.Get(TextId.RecapTitle) + "", "\n", string.Join("\n", list.ToArray())); float num = ContentWidth - 16f - 16f; ((TMP_Text)_recapText).text = text; float num2 = Mathf.Max(18f, ((TMP_Text)_recapText).GetPreferredValues(text, num, 0f).y); ((TMP_Text)_recapText).rectTransform.sizeDelta = new Vector2(num, num2); float num3 = num2 + 16f; _recapBox.sizeDelta = new Vector2(-16f, num3); if ((Object)(object)_scrollRect != (Object)null) { _scrollRect.offsetMax = new Vector2(0f, 0f - (48f + num3 + 6f)); } } } private void ApplyContentHeight() { _view.SetContentHeight((_layout.Count > 0) ? (_layout.TotalHeight + 16f) : 56f); } private void UpdateEmptyHint() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) bool flag = _layout.Count == 0; if (flag != ((Object)(object)_emptyHint != (Object)null)) { if (flag) { Vector2 size = default(Vector2); ((Vector2)(ref size))..ctor(ContentWidth - 16f, 40f); RectTransform val = _rows.CreateBlock("Empty", size, -8f, 0f); UiKit.CreateText((Transform)(object)val, "Text", Vector2.zero, size, Texts.Get(TextId.ChatLogEmpty), 17f, HintTextColor, (TextAlignmentOptions)514); _emptyHint = ((Component)val).gameObject; } else { Object.Destroy((Object)(object)_emptyHint); _emptyHint = null; } } } private void SyncVirtualization() { if (_open && _hasRenderState) { _view.Sync(_layout, _createBlockFn); } } private GameObject CreateBlockObject(int index) { if (_log == null) { return null; } ChatLayoutBlock block = _layout[index]; long num = block.EntrySeq - _log.DroppedTotal; if (num < 0 || num >= _log.Count) { return null; } ChatLogEntry entry = _log.Entries[(int)num]; float topY = 0f - (8f + _layout.ContentTop(index)); return _rows.CreateRow(topY, block, entry); } private void ClearLiveBlocks() { _view.Clear(); _rows.ClearPending(); } public void ResetView() { _renderedRecapSignature = long.MinValue; _renderedVersion = int.MinValue; _scrollToBottomPending = true; ClearLiveBlocks(); _view.ResetScrollPosition(); } public void Destroy() { //IL_00c7: Unknown result type (might be due to invalid IL or missing references) try { ClearLiveBlocks(); if ((Object)(object)_emptyHint != (Object)null) { Object.Destroy((Object)(object)_emptyHint); } if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } } catch (Exception ex) { WLog.Line("chat_panel_destroy_error", false, ("err", ex.Message)); } _layout.Reset(); _renderedVersion = int.MinValue; _emptyHint = null; _measureFn = null; _createBlockFn = null; _log = null; _hasRenderState = false; _open = true; _root = null; _panel = null; _panelRect = null; _toggle.Clear(); _labelKey = (KeyCode)0; _recapBox = null; _recapText = null; _scrollRect = null; _footerHint = null; _renderedLocalDead = false; ResetView(); _view.Detach(); _rows.Detach(); } } public sealed class MeetingGaugePanel : IClientPanel { private static readonly Vector2 FullPanelSize = new Vector2(1560f, 104f); private static readonly Vector2 FullPanelPos = new Vector2(0f, 400f); private static readonly Vector2 CenterPanelPos = new Vector2(0f, 0f); private static readonly Vector2 TrackSize = new Vector2(1500f, 20f); private static readonly Vector2 TrackPos = new Vector2(0f, -6f); private static readonly Color TrackBgColor = new Color(0.1f, 0.1f, 0.14f, 0.9f); private static readonly Color FillColor = new Color(0.85f, 0.65f, 0.15f, 0.95f); private static readonly Color DeliveryFillColor = new Color(0.3f, 0.8f, 0.95f, 0.95f); private static readonly Color QuotaLineColor = new Color(0.25f, 0.55f, 1f, 0.95f); private const float QuotaLineWidth = 4f; private static readonly Color CheckmateLineColor = new Color(0.95f, 0.12f, 0.12f, 0.95f); private static readonly Vector2 PctTextPos = new Vector2(0f, -36f); private const float TickWidth = 3f; private const float TickHeight = 14f; private const float PerkIconSize = 36f; private const float TierGap = 2f; private const float ScaleTickWidth = 2f; private const float ScaleTickHeight = 8f; private const float BalloonWidth = 96f; private const float BalloonHeight = 26f; private static readonly Color LockedTickColor = new Color(0.55f, 0.55f, 0.55f, 0.95f); private static readonly Color UnlockedTickColor = new Color(0.35f, 0.95f, 0.35f, 0.95f); private static readonly Color LockedIconTint = new Color(0.5f, 0.5f, 0.5f, 0.85f); private static readonly Color LockedBalloonBg = new Color(0.18f, 0.18f, 0.2f, 0.92f); private static readonly Color UnlockedBalloonBg = new Color(0.16f, 0.32f, 0.16f, 0.92f); private static readonly Color LockedTextColor = new Color(0.75f, 0.75f, 0.75f, 1f); private static readonly Color UnlockedTextColor = new Color(0.65f, 1f, 0.65f, 1f); private GameObject _root; private RectTransform _rootRect; private RectTransform _trackRect; private Image _fillImage; private GameObject _deliveryTrack; private RectTransform _deliveryTrackRect; private Image _deliveryFillImage; private Image _quotaLine; private Image _checkmateLine; private GameObject _deliveryIcon; private TextMeshProUGUI _pctText; private TextMeshProUGUI _nextUpdateText; private readonly List _markerObjects = new List(); private MeetingGaugeSnapshot _renderedSnapshot; private bool _wasMeetingActive; private int _lastNextUpdateSec = -1; private readonly GaugeReveal _reveal = new GaugeReveal(); private int _lastRevealedPermille; private int _lastRevealedLoss; private int _revealUnlockCount; private bool _revealBreakPlayed; private readonly GaugeReveal _deliveryReveal = new GaugeReveal(); private int _lastRevealedDeliveryPermille; private bool _deliverySfxPlayed; public Action OnRevealBreak; public Action OnDeliveryReveal; private readonly string _rootName; private readonly Vector2 _panelSize; private readonly Vector2 _rootPos; private readonly Vector2 _rootAnchor; private readonly Vector2 _rootPivot; private readonly float _scale; private readonly float _markerScale; public int LastRevealedPermille => _lastRevealedPermille; public int LastRevealedLoss => _lastRevealedLoss; public bool Exists => (Object)(object)_root != (Object)null; public string LayerName => "RoleGauge"; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public MeetingGaugePanel() : this("WW_MeetingGaugePanel", FullPanelSize, FullPanelPos, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), 0.8f, 2f) { }//IL_0006: 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) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) public MeetingGaugePanel(string rootName, Vector2 panelSize, Vector2 pos, Vector2 anchor, Vector2 pivot, float scale, float markerScale) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) _rootName = rootName; _panelSize = panelSize; _rootPos = pos; _rootAnchor = anchor; _rootPivot = pivot; _scale = scale; _markerScale = markerScale; } public void Build(Transform layerRoot) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_0400: Unknown result type (might be due to invalid IL or missing references) //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(_panelSize.x - 24f, 22f); RectTransform val2 = UiKit.CreateRect(layerRoot, _rootName, _rootPos, _panelSize); Vector2 anchorMin = (val2.anchorMax = _rootAnchor); val2.anchorMin = anchorMin; val2.pivot = _rootPivot; ((Transform)val2).localScale = Vector3.one * _scale; _root = ((Component)val2).gameObject; _rootRect = val2; UiKit.CreateImage((Transform)(object)val2, "Bg", Vector2.zero, _panelSize, new Color(0.02f, 0.02f, 0.05f, 0.75f)); Image val3 = UiKit.CreateImage((Transform)(object)val2, "Track", TrackPos, TrackSize, TrackBgColor); _trackRect = ((Graphic)val3).rectTransform; _fillImage = UiKit.CreateFilledImage((Transform)(object)_trackRect, "Fill", Vector2.zero, TrackSize, FillColor); Image val4 = UiKit.CreateImage((Transform)(object)val2, "DeliveryTrack", TrackPos + new Vector2(0f, 0f - TrackSize.y), TrackSize, TrackBgColor); _deliveryTrackRect = ((Graphic)val4).rectTransform; _deliveryTrack = ((Component)val4).gameObject; _deliveryFillImage = UiKit.CreateFilledImage((Transform)(object)_deliveryTrackRect, "DeliveryFill", Vector2.zero, TrackSize, DeliveryFillColor); _deliveryFillImage.fillOrigin = 1; _quotaLine = UiKit.CreateImage((Transform)(object)val2, "QuotaLine", new Vector2(0f, TrackPos.y - TrackSize.y / 2f), new Vector2(4f, TrackSize.y * 2f + 8f), QuotaLineColor); ((Component)_quotaLine).gameObject.SetActive(false); _checkmateLine = UiKit.CreateImage((Transform)(object)val2, "CheckmateLine", new Vector2(0f, TrackPos.y - TrackSize.y / 2f), new Vector2(4f, TrackSize.y * 2f + 8f), CheckmateLineColor); ((Component)_checkmateLine).gameObject.SetActive(false); _deliveryTrack.SetActive(false); _pctText = UiKit.CreateText((Transform)(object)val2, "PctText", PctTextPos, val, "", 22f * _markerScale, Color.white, (TextAlignmentOptions)514); Vector2 size = default(Vector2); ((Vector2)(ref size))..ctor(val.x - 160f, 28f); _nextUpdateText = UiKit.CreateText((Transform)(object)val2, "NextUpdate", new Vector2(PctTextPos.x + 80f, PctTextPos.y), size, "", 24f * _markerScale, new Color(0.9f, 0.9f, 0.9f, 0.95f), (TextAlignmentOptions)4097); Sprite sprite = AssetCatalog.GetSprite("icon_gauge_valuable_loss"); if ((Object)(object)sprite != (Object)null) { float num = TrackPos.x - TrackSize.x / 2f; Image obj = UiKit.CreateImage((Transform)(object)val2, "GaugeIcon", new Vector2(num, TrackPos.y), new Vector2(128f, 128f), Color.white); obj.sprite = sprite; obj.preserveAspect = true; } Sprite sprite2 = AssetCatalog.GetSprite("icon_gauge_delivery"); if ((Object)(object)sprite2 != (Object)null) { float num2 = TrackPos.x + TrackSize.x / 2f; Image val5 = UiKit.CreateImage((Transform)(object)val2, "DeliveryIcon", new Vector2(num2, TrackPos.y - TrackSize.y), new Vector2(128f, 128f), Color.white); val5.sprite = sprite2; val5.preserveAspect = true; _deliveryIcon = ((Component)val5).gameObject; _deliveryIcon.SetActive(false); } _root.SetActive(false); WLog.Line("gauge_panel_built", false); } } public void Tick(RolesClientState roles, MeetingClientState meeting, long nowUnixMs) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { bool flag = meeting?.MeetingActive ?? false; if (_wasMeetingActive && !flag && roles != null && roles.MeetingGauge != null) { roles.ClearMeetingGauge(); WLog.Line("gauge_panel_cleared", false); } _wasMeetingActive = flag; MeetingGaugeSnapshot meetingGaugeSnapshot = roles?.MeetingGauge; bool flag2 = flag && meetingGaugeSnapshot != null && meeting.GaugeIntroReady(nowUnixMs); if (_root.activeSelf != flag2) { _root.SetActive(flag2); WLog.Line("gauge_panel_visible", false, ("visible", flag2)); } if (flag2) { if ((Object)(object)_rootRect != (Object)null) { _rootRect.anchoredPosition = Vector2.Lerp(CenterPanelPos, FullPanelPos, (float)meeting.GaugeMoveProgress(nowUnixMs)); } if (meetingGaugeSnapshot != _renderedSnapshot) { BeginReveal(meetingGaugeSnapshot, nowUnixMs); _renderedSnapshot = meetingGaugeSnapshot; } else if (_reveal.Active || _deliveryReveal.Active) { TickReveal(meetingGaugeSnapshot, nowUnixMs); } } } catch (Exception ex) { WLog.Line("gauge_panel_tick_error", false, ("err", ex.Message)); } } public void TickPlay(RolesClientState roles, MeetingClientState meeting, GamePhase phase, Role? localRole, long nowUnixMs) { if ((Object)(object)_root == (Object)null) { return; } try { bool flag = meeting != null && meeting.MeetingActive && meeting.WarpDone(nowUnixMs); MeetingGaugeSnapshot meetingGaugeSnapshot = roles?.PlayGauge; bool flag2 = (phase == GamePhase.Play || phase == GamePhase.Meeting) && !flag && meetingGaugeSnapshot != null && roles.CanShowHud(localRole); if (_root.activeSelf != flag2) { _root.SetActive(flag2); WLog.Line("play_gauge_visible", false, ("visible", flag2)); } if (flag2) { if (meetingGaugeSnapshot != _renderedSnapshot) { Render(meetingGaugeSnapshot); _renderedSnapshot = meetingGaugeSnapshot; } UpdateNextUpdateText(roles, nowUnixMs); } } catch (Exception ex) { WLog.Line("play_gauge_tick_error", false, ("err", ex.Message)); } } private void UpdateNextUpdateText(RolesClientState roles, long nowUnixMs) { if (!((Object)(object)_nextUpdateText == (Object)null)) { long num = roles?.GaugeNextUpdateUnixMs ?? 0; int num2; if (num <= 0) { num2 = -1; } else { long num3 = num - nowUnixMs; num2 = (int)((num3 > 0) ? ((num3 + 999) / 1000) : 0); } if (num2 != _lastNextUpdateSec) { _lastNextUpdateSec = num2; ((TMP_Text)_nextUpdateText).text = ((num2 < 0) ? "" : Texts.Format(TextId.GaugeNextUpdateFormat, num2)); } } } public void Destroy() { ClearMarkers(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _rootRect = null; _trackRect = null; _fillImage = null; _deliveryTrack = null; _deliveryTrackRect = null; _deliveryFillImage = null; _quotaLine = null; _checkmateLine = null; _deliveryIcon = null; _pctText = null; _nextUpdateText = null; _renderedSnapshot = null; _wasMeetingActive = false; _lastNextUpdateSec = -1; _reveal.Stop(); _lastRevealedPermille = 0; _lastRevealedLoss = 0; _revealUnlockCount = 0; _revealBreakPlayed = false; _deliveryReveal.Stop(); _lastRevealedDeliveryPermille = 0; _deliverySfxPlayed = false; } private void BeginReveal(MeetingGaugeSnapshot s, long nowUnixMs) { int num = ClampPermille(s.RatioPermille); int num2 = LossOf(s, num); int lastRevealedPermille = _lastRevealedPermille; int lastRevealedLoss = _lastRevealedLoss; _lastRevealedPermille = num; _lastRevealedLoss = num2; int num3 = s.DeliveryPermille(); int lastRevealedDeliveryPermille = _lastRevealedDeliveryPermille; _lastRevealedDeliveryPermille = ((num3 >= 0) ? num3 : 0); bool flag = GaugeReveal.ShouldAnimate(lastRevealedPermille, num); bool flag2 = num3 >= 0 && GaugeReveal.ShouldAnimate(lastRevealedDeliveryPermille, num3); if (!flag && !flag2) { _reveal.Stop(); _deliveryReveal.Stop(); Render(s, num, num2, num3); return; } if (flag) { _reveal.Start(lastRevealedPermille, num, lastRevealedLoss, num2, nowUnixMs); } else { _reveal.Stop(); } _revealUnlockCount = GaugeMarkerLayout.UnlockedCount(s, flag ? lastRevealedPermille : num); _revealBreakPlayed = !flag; if (flag2) { _deliveryReveal.Start(lastRevealedDeliveryPermille, num3, 0, 0, nowUnixMs + (flag ? 1600 : 0)); _deliverySfxPlayed = false; } else { _deliveryReveal.Stop(); } Render(s, flag ? lastRevealedPermille : num, flag ? lastRevealedLoss : num2, flag2 ? lastRevealedDeliveryPermille : num3); WLog.Line("gauge_reveal_start", false, ("from", lastRevealedPermille), ("to", num), ("dFrom", lastRevealedDeliveryPermille), ("dTo", num3)); } private void TickReveal(MeetingGaugeSnapshot s, long nowUnixMs) { if (_reveal.Active && !_revealBreakPlayed && _reveal.GrowthStarted(nowUnixMs)) { _revealBreakPlayed = true; OnRevealBreak?.Invoke(); } if (_deliveryReveal.Active && !_deliverySfxPlayed && _deliveryReveal.GrowthStarted(nowUnixMs)) { _deliverySfxPlayed = true; OnDeliveryReveal?.Invoke(); } int permille = (_reveal.Active ? _reveal.CurrentPermille(nowUnixMs) : ClampPermille(s.RatioPermille)); int lossDollars = (_reveal.Active ? _reveal.CurrentLoss(nowUnixMs) : LossOf(s, permille)); RenderFill(s, permille, lossDollars); if (_deliveryReveal.Active) { RenderDelivery(s, _deliveryReveal.CurrentPermille(nowUnixMs)); } int num = GaugeMarkerLayout.UnlockedCount(s, permille); if (num != _revealUnlockCount) { _revealUnlockCount = num; RenderMarkers(s, permille); } if (_reveal.Done(nowUnixMs) && _deliveryReveal.Done(nowUnixMs)) { _reveal.Stop(); _deliveryReveal.Stop(); Render(s); } } private void Render(MeetingGaugeSnapshot s) { int permille = ClampPermille(s.RatioPermille); Render(s, permille, LossOf(s, permille), s.DeliveryPermille()); } private void Render(MeetingGaugeSnapshot s, int permille, int lossDollars, int deliveryPermille) { RenderFill(s, permille, lossDollars); RenderDelivery(s, deliveryPermille); RenderMarkers(s, permille); } private void RenderFill(MeetingGaugeSnapshot s, int permille, int lossDollars) { if ((Object)(object)_fillImage != (Object)null) { _fillImage.fillAmount = (float)permille / 1000f; } if ((Object)(object)_pctText != (Object)null) { ((TMP_Text)_pctText).text = Texts.Format(TextId.GaugeLossOverBaseFormat, lossDollars, s.BaseDollars); } } private void RenderDelivery(MeetingGaugeSnapshot s, int deliveryPermille) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_deliveryTrack == (Object)null) { return; } bool flag = deliveryPermille >= 0; if (_deliveryTrack.activeSelf != flag) { _deliveryTrack.SetActive(flag); } if ((Object)(object)_deliveryIcon != (Object)null && _deliveryIcon.activeSelf != flag) { _deliveryIcon.SetActive(flag); } if ((Object)(object)_pctText != (Object)null) { ((TMP_Text)_pctText).rectTransform.anchoredPosition = (flag ? (PctTextPos + new Vector2(0f, 0f - TrackSize.y)) : PctTextPos); } int num = s.QuotaPermille(); if ((Object)(object)_quotaLine != (Object)null) { bool flag2 = flag && num >= 0; if (((Component)_quotaLine).gameObject.activeSelf != flag2) { ((Component)_quotaLine).gameObject.SetActive(flag2); } if (flag2) { ((Graphic)_quotaLine).rectTransform.anchoredPosition = new Vector2(TrackSize.x / 2f - TrackSize.x * (float)num / 1000f, TrackPos.y - TrackSize.y / 2f); } } int num2 = s.CheckmateLinePermille(); if ((Object)(object)_checkmateLine != (Object)null) { bool flag3 = num2 >= 0; if (((Component)_checkmateLine).gameObject.activeSelf != flag3) { ((Component)_checkmateLine).gameObject.SetActive(flag3); } if (flag3) { ((Graphic)_checkmateLine).rectTransform.anchoredPosition = new Vector2((0f - TrackSize.x) / 2f + TrackSize.x * (float)num2 / 1000f, flag ? (TrackPos.y - TrackSize.y / 2f) : TrackPos.y); ((Graphic)_checkmateLine).rectTransform.sizeDelta = new Vector2(4f, flag ? (TrackSize.y * 2f + 8f) : (TrackSize.y + 8f)); } } if (flag && (Object)(object)_deliveryFillImage != (Object)null) { _deliveryFillImage.fillAmount = (float)deliveryPermille / 1000f; } } private static int ClampPermille(int permille) { if (permille < 0) { return 0; } if (permille > 1000) { return 1000; } return permille; } private static int LossOf(MeetingGaugeSnapshot s, int permille) { int num = ((s.LostDollars >= 0) ? s.LostDollars : ((int)((long)s.BaseDollars * (long)permille + 500) / 1000)); if (num > s.BaseDollars) { num = s.BaseDollars; } return num; } private void RenderMarkers(MeetingGaugeSnapshot s, int permille) { //IL_0120: 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_0144: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) ClearMarkers(); if ((Object)(object)_trackRect == (Object)null) { return; } int minGapPct = Mathf.CeilToInt(36f * _markerScale * 100f / TrackSize.x); List list = GaugeMarkerLayout.Build(s, permille, minGapPct); float x = TrackSize.x; foreach (GaugeMarker item in list) { float num = (0f - x) / 2f + x * (float)item.Pct / 100f; if (item.Kind == GaugeMarkerKind.Scale) { float num2 = 0f - (TrackSize.y / 2f + 4f) - ((s.DeliveryPermille() >= 0) ? TrackSize.y : 0f); Image val = UiKit.CreateImage((Transform)(object)_trackRect, "ScaleTick", new Vector2(num, num2), new Vector2(2f, 8f), LockedTickColor); _markerObjects.Add(((Component)val).gameObject); continue; } Image val2 = UiKit.CreateImage((Transform)(object)_trackRect, "Tick", new Vector2(num, TrackSize.y / 2f + 7f), new Vector2(3f, 14f), item.Unlocked ? UnlockedTickColor : LockedTickColor); _markerObjects.Add(((Component)val2).gameObject); Sprite sprite = AssetCatalog.GetSprite(item.IconKey); if ((Object)(object)sprite != (Object)null) { float num3 = 36f * _markerScale; float num4 = TrackSize.y / 2f + 14f + num3 / 2f + (float)item.Tier * (num3 + 2f); Image val3 = UiKit.CreateImage((Transform)(object)_trackRect, "PerkIcon", new Vector2(num, num4), new Vector2(num3, num3), item.Unlocked ? Color.white : LockedIconTint); val3.sprite = sprite; val3.preserveAspect = true; _markerObjects.Add(((Component)val3).gameObject); } else { float num5 = 96f * _markerScale; float num6 = 26f * _markerScale; float num7 = TrackSize.y / 2f + 14f + num6 / 2f + (float)item.Tier * (num6 + 2f); Image val4 = UiKit.CreateImage((Transform)(object)_trackRect, "Balloon", new Vector2(num, num7), new Vector2(num5, num6), item.Unlocked ? UnlockedBalloonBg : LockedBalloonBg); _markerObjects.Add(((Component)val4).gameObject); UiKit.CreateText((Transform)(object)((Graphic)val4).rectTransform, "Label", Vector2.zero, new Vector2(num5 - 8f, num6 - 4f), item.Label, 14f * _markerScale, item.Unlocked ? UnlockedTextColor : LockedTextColor, (TextAlignmentOptions)514); } } RenderRuleTexts(s); } private void RenderRuleTexts(MeetingGaugeSnapshot s) { bool flag = s.DeliveryPermille() >= 0; float rowY = -30f - (flag ? TrackSize.y : 0f); if (!flag) { RenderBeaconRuleRight(s, rowY); return; } float x = (0f - TrackSize.x) / 2f + 80f; if (s.BeaconChargePct >= 1) { x = RenderRuleUnit(x, rowY, "perk_beacon", "+1/" + s.BeaconChargePct + "%", Texts.Format(TextId.GaugeBeaconRuleFormat, s.BeaconChargePct)); } if (s.BombRefillPct >= 1) { RenderRuleUnit(x, rowY, "perk_bomb_plant", "+1/" + s.BombRefillPct + "%", Texts.Format(TextId.GaugeBombRuleFormat, s.BombRefillPct)); } } private float RenderRuleUnit(float x, float rowY, string iconKey, string valueText, string fallbackText) { //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) Color color = default(Color); ((Color)(ref color))..ctor(0.9f, 0.9f, 0.9f, 0.95f); Sprite sprite = AssetCatalog.GetSprite(iconKey); if ((Object)(object)sprite != (Object)null) { float num = 20f * _markerScale; float num2 = 90f * _markerScale; Image val = UiKit.CreateImage((Transform)(object)_trackRect, "RuleIcon", new Vector2(x + num / 2f, rowY), new Vector2(num, num), Color.white); val.sprite = sprite; val.preserveAspect = true; _markerObjects.Add(((Component)val).gameObject); TextMeshProUGUI val2 = UiKit.CreateText((Transform)(object)_trackRect, "RuleText", new Vector2(x + num + 4f + num2 / 2f, rowY), new Vector2(num2, 24f * _markerScale), valueText, 16f * _markerScale, color, (TextAlignmentOptions)4097); _markerObjects.Add(((Component)val2).gameObject); return x + num + 4f + num2 + 8f; } float num3 = 200f * _markerScale; TextMeshProUGUI val3 = UiKit.CreateText((Transform)(object)_trackRect, "RuleText", new Vector2(x + num3 / 2f, rowY), new Vector2(num3, 24f * _markerScale), fallbackText, 16f * _markerScale, color, (TextAlignmentOptions)4097); _markerObjects.Add(((Component)val3).gameObject); return x + num3 + 8f; } private void RenderBeaconRuleRight(MeetingGaugeSnapshot s, float rowY) { //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0183: 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) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) if (s.BeaconChargePct >= 1) { Color color = default(Color); ((Color)(ref color))..ctor(0.9f, 0.9f, 0.9f, 0.95f); Sprite sprite = AssetCatalog.GetSprite("perk_beacon"); if ((Object)(object)sprite != (Object)null) { float num = 90f * _markerScale; float num2 = 20f * _markerScale; TextMeshProUGUI val = UiKit.CreateText((Transform)(object)_trackRect, "BeaconRule", new Vector2(TrackSize.x / 2f - num / 2f, rowY), new Vector2(num, 24f * _markerScale), "+1/" + s.BeaconChargePct + "%", 16f * _markerScale, color, (TextAlignmentOptions)4100); _markerObjects.Add(((Component)val).gameObject); Image val2 = UiKit.CreateImage((Transform)(object)_trackRect, "BeaconRuleIcon", new Vector2(TrackSize.x / 2f - num - num2 / 2f - 4f, rowY), new Vector2(num2, num2), Color.white); val2.sprite = sprite; val2.preserveAspect = true; _markerObjects.Add(((Component)val2).gameObject); } else { float num3 = 200f * _markerScale; TextMeshProUGUI val3 = UiKit.CreateText((Transform)(object)_trackRect, "BeaconRule", new Vector2(TrackSize.x / 2f - num3 / 2f, rowY), new Vector2(num3, 24f * _markerScale), Texts.Format(TextId.GaugeBeaconRuleFormat, s.BeaconChargePct), 16f * _markerScale, color, (TextAlignmentOptions)4100); _markerObjects.Add(((Component)val3).gameObject); } } } private void ClearMarkers() { for (int i = 0; i < _markerObjects.Count; i++) { if ((Object)(object)_markerObjects[i] != (Object)null) { Object.Destroy((Object)(object)_markerObjects[i]); } } _markerObjects.Clear(); } } public sealed class MeetingMapOverlay : IClientPanel { private const string DirtFinderCameraName = "Dirt Finder Map Camera"; private const float PanelWidth = 1200f; private const float PanelHeight = 720f; private const float ToggleButtonMargin = 24f; private static readonly Color MapTint = new Color(1f, 1f, 1f, 1f); private static readonly Color PanelFrameColor = new Color(0.02f, 0.02f, 0.05f, 0.95f); private static readonly IconToggleStyle ToggleStyle = new IconToggleStyle(96f, 240f, 32f, 24f, new Color(1f, 0.9f, 0.6f, 1f), "MAP", new Color(0.08f, 0.08f, 0.1f, 0.55f), new Color(0.95f, 0.95f, 0.95f, 0.95f)); private GameObject _root; private GameObject _mapPanel; private RectTransform _mapPanelRect; private RawImage _mapImage; private readonly IconToggleButton _toggle = new IconToggleButton(); private bool _open; private bool _prevMeetingActive; private KeyCode _lastLabelKey; private Camera _cachedCamera; private Vector3 _origCamLocalPosition; private Quaternion _origCamLocalRotation; private float _originalOrthoSize; private bool _cameraOverrideApplied; private RenderTexture _overrideRt; private RenderTexture _origTargetTexture; private bool _targetTextureSaved; private bool _boundsResolved; private Vector2 _boundsCenterXZ; private float _boundsHalfW; private float _boundsHalfH; private CameraClearFlags _origClearFlags; private Color _origBackgroundColor; private bool _origClearSaved; private Texture _placeholderTex; private bool _placeholderResolved; private const float GridLineThickness = 2f; private const float GridLabelFontSize = 26f; private const float GridLabelWidth = 48f; private const float GridLabelHeight = 30f; private static readonly Color GridLineColor = new Color(1f, 1f, 1f, 0.3f); private static readonly Color GridLabelColor = new Color(1f, 1f, 1f, 0.92f); private RectTransform _gridRoot; private bool _gridModuleRangeResolved; private MapGridCellRange? _gridModuleRange; private readonly List _gridVLines = new List(); private readonly List _gridHLines = new List(); private readonly List _gridColLabels = new List(); private readonly List _gridRowLabels = new List(); public string LayerName => "MeetingMap"; public bool Exists => (Object)(object)_root != (Object)null; public bool IsOpen => _open; public void Build(Transform layerRoot) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { RectTransform val = UiKit.CreateRect(layerRoot, "WW_MeetingMapOverlay", Vector2.zero, new Vector2(1920f, 1080f)); _root = ((Component)val).gameObject; RectTransform val2 = UiKit.CreateRect((Transform)(object)val, "MapPanel", Vector2.zero, new Vector2(1200f, 720f)); _mapPanel = ((Component)val2).gameObject; _mapPanelRect = val2; UiKit.CreateImage((Transform)(object)val2, "PanelBg", Vector2.zero, new Vector2(1212f, 732f), PanelFrameColor); GameObject val3 = new GameObject("MapImage", new Type[1] { typeof(RectTransform) }); RectTransform val4 = (RectTransform)val3.transform; ((Transform)val4).SetParent((Transform)(object)val2, false); Vector2 val5 = default(Vector2); ((Vector2)(ref val5))..ctor(0.5f, 0.5f); val4.anchorMax = val5; val4.anchorMin = val5; val4.anchoredPosition = Vector2.zero; val4.sizeDelta = new Vector2(1200f, 720f); _mapImage = val3.AddComponent(); ((Graphic)_mapImage).color = MapTint; ((Graphic)_mapImage).raycastTarget = false; _gridRoot = UiKit.CreateRect((Transform)(object)val2, "GridRoot", Vector2.zero, new Vector2(1200f, 720f)); ((Component)_gridRoot).gameObject.SetActive(false); _mapPanel.SetActive(false); RectTransform obj = _toggle.Build((Transform)(object)val, "ToggleButton", ToggleStyle, "map_overlay_icon", Texts.Format(TextId.MapOverlayToggleLabelFormat, "M")); Vector2 sizeDelta = obj.sizeDelta; obj.anchoredPosition = new Vector2(-960f + sizeDelta.x * 0.5f + 24f, -540f + sizeDelta.y * 0.5f + 24f); WLog.Line("mapoverlay_built", false, ("icon", _toggle.HasIcon ? 1 : 0)); } } public void Tick(MeetingClientState state, long nowUnixMs, KeyCode key, float orthoSize, int resolutionPreset, bool gridEnabled) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } bool flag = state != null && state.MeetingActive && state.VotingUiReady(nowUnixMs); if (_prevMeetingActive && !flag) { ForceClose(); } _prevMeetingActive = flag; if (key != _lastLabelKey) { _toggle.SetLabel(Texts.Format(TextId.MapOverlayToggleLabelFormat, FormatKey(key))); _lastLabelKey = key; } if (!flag) { _toggle.SetHover(over: false); return; } try { if (IsMapToolActive()) { _toggle.SetHover(over: false); if (_open) { CloseInternal(); } return; } _toggle.SetHover(_toggle.IsPointerOverOpaqueIcon()); bool num = (int)key != 0 && NoTextInputsActive() && Input.GetKeyDown(key); bool flag2 = _toggle.WasClicked(); if (num || flag2) { if (_open) { CloseInternal(); } else { OpenInternal(orthoSize, resolutionPreset); } } if (_open) { if ((Object)(object)Map.Instance != (Object)null && !Map.Instance.Active) { Map.Instance.ActiveSet(true); } RefreshMapTexture(); ApplyCameraOverride(orthoSize); UpdateGrid(gridEnabled); } } catch (Exception ex) { WLog.Line("mapoverlay_tick_error", false, ("err", ex.Message)); } } private static bool IsMapToolActive() { try { MapToolController instance = MapToolController.instance; if ((Object)(object)instance == (Object)null) { return false; } return GameRefs.MapToolController_Active.Invoke(instance); } catch { return false; } } public void ForceClose() { if (_open) { CloseInternal(); } } public void Destroy() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if (_open) { CloseInternal(); } if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _mapPanel = null; _mapPanelRect = null; _mapImage = null; _toggle.Clear(); _prevMeetingActive = false; _lastLabelKey = (KeyCode)0; _cachedCamera = null; _origCamLocalPosition = Vector3.zero; _origCamLocalRotation = Quaternion.identity; _originalOrthoSize = 0f; _cameraOverrideApplied = false; _origClearFlags = (CameraClearFlags)0; _origBackgroundColor = default(Color); _origClearSaved = false; _boundsResolved = false; _boundsCenterXZ = Vector2.zero; _boundsHalfW = 0f; _boundsHalfH = 0f; _placeholderTex = null; _placeholderResolved = false; _gridRoot = null; _gridModuleRangeResolved = false; _gridModuleRange = null; _gridVLines.Clear(); _gridHLines.Clear(); _gridColLabels.Clear(); _gridRowLabels.Clear(); } private void OpenInternal(float orthoSizeConfig, int resolutionPreset) { //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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) _open = true; if ((Object)(object)_mapPanel != (Object)null) { _mapPanel.SetActive(true); } EnsureCameraResolved(); if ((Object)(object)_cachedCamera != (Object)null && !_cameraOverrideApplied) { Transform transform = ((Component)_cachedCamera).transform; _origCamLocalPosition = transform.localPosition; _origCamLocalRotation = transform.localRotation; _originalOrthoSize = _cachedCamera.orthographicSize; _cameraOverrideApplied = true; } if ((Object)(object)_cachedCamera != (Object)null && !_origClearSaved) { _origClearFlags = _cachedCamera.clearFlags; _origBackgroundColor = _cachedCamera.backgroundColor; _origClearSaved = true; _cachedCamera.clearFlags = (CameraClearFlags)2; _cachedCamera.backgroundColor = new Color(0f, 0f, 0f, 1f); WLog.Line("mapoverlay_clear_override", false, ("origFlags", ((object)Unsafe.As(ref _origClearFlags)/*cast due to .constrained prefix*/).ToString()), ("origBgA", _origBackgroundColor.a)); } ApplyTargetTextureOverride(resolutionPreset); ApplyCameraOverride(orthoSizeConfig); RefreshMapTexture(); WLog.Line("mapoverlay_open", false, ("orthoConfig", orthoSizeConfig), ("orthoApplied", ((Object)(object)_cachedCamera != (Object)null) ? _cachedCamera.orthographicSize : (-1f)), ("rtW", ((Object)(object)_overrideRt != (Object)null) ? ((Texture)_overrideRt).width : (-1)), ("rtH", ((Object)(object)_overrideRt != (Object)null) ? ((Texture)_overrideRt).height : (-1))); } private void ApplyTargetTextureOverride(int resolutionPreset) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown if ((Object)(object)_cachedCamera == (Object)null || _targetTextureSaved) { return; } int num; int num2; switch (resolutionPreset) { case 0: num = 1280; num2 = 768; break; case 2: num = 1920; num2 = 1152; break; default: num = 1600; num2 = 960; break; } try { RenderTexture val = new RenderTexture(num, num2, 16, (RenderTextureFormat)0); ((Object)val).name = "WW_MeetingMapOverlay_RT"; val.antiAliasing = 1; ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; if (!val.Create()) { Object.Destroy((Object)(object)val); WLog.Line("mapoverlay_rt_create_failed", false, ("w", num), ("h", num2)); } else { _origTargetTexture = _cachedCamera.targetTexture; _cachedCamera.targetTexture = val; _overrideRt = val; _targetTextureSaved = true; } } catch (Exception ex) { WLog.Line("mapoverlay_rt_override_error", false, ("err", ex.Message)); } } private void RestoreTargetTexture() { if (!((Object)(object)_cachedCamera == (Object)null) && _targetTextureSaved) { try { _cachedCamera.targetTexture = _origTargetTexture; } catch (Exception ex) { WLog.Line("mapoverlay_rt_restore_error", false, ("err", ex.Message)); } if ((Object)(object)_overrideRt != (Object)null) { _overrideRt.Release(); Object.Destroy((Object)(object)_overrideRt); _overrideRt = null; } _origTargetTexture = null; _targetTextureSaved = false; } } private void CloseInternal() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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) _open = false; if ((Object)(object)_mapPanel != (Object)null) { _mapPanel.SetActive(false); } if ((Object)(object)_cachedCamera != (Object)null && _cameraOverrideApplied) { Transform transform = ((Component)_cachedCamera).transform; transform.localPosition = _origCamLocalPosition; transform.localRotation = _origCamLocalRotation; _cachedCamera.orthographicSize = _originalOrthoSize; } if ((Object)(object)_cachedCamera != (Object)null && _origClearSaved) { _cachedCamera.clearFlags = _origClearFlags; _cachedCamera.backgroundColor = _origBackgroundColor; } RestoreTargetTexture(); _origClearSaved = false; _cameraOverrideApplied = false; _boundsResolved = false; _gridModuleRangeResolved = false; _gridModuleRange = null; if ((Object)(object)_gridRoot != (Object)null) { ((Component)_gridRoot).gameObject.SetActive(false); } WLog.Line("mapoverlay_close", false); } private void ApplyCameraOverride(float orthoSizeConfig) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_cachedCamera == (Object)null) { return; } Transform transform = ((Component)_cachedCamera).transform; transform.rotation = Quaternion.Euler(90f, 0f, 0f); TryResolveMiniatureBounds(); if (_boundsResolved) { Vector3 position = transform.position; transform.position = new Vector3(_boundsCenterXZ.x, position.y, _boundsCenterXZ.y); } else { Map instance = Map.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.OverLayerParent != (Object)null) { Vector3 position2 = instance.OverLayerParent.position; Vector3 position3 = transform.position; transform.position = new Vector3(position2.x, position3.y, position2.z); } } _cachedCamera.orthographicSize = ComputeOrthoSize(orthoSizeConfig); } private float ComputeOrthoSize(float fallback) { if ((Object)(object)_cachedCamera == (Object)null) { return fallback; } float num = Mathf.Max(0.01f, _cachedCamera.aspect); if (_boundsResolved) { return Mathf.Max(_boundsHalfH, _boundsHalfW / num) * 1.05f; } try { LevelGenerator instance = LevelGenerator.Instance; Map instance2 = Map.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null) { return fallback; } if (instance.LevelWidth <= 0 || instance.LevelHeight <= 0) { return fallback; } float num2 = LevelGenerator.ModuleWidth * LevelGenerator.TileSize; float num3 = (float)instance.LevelWidth * num2; float num4 = (float)instance.LevelHeight * num2; float scale = instance2.Scale; float num5 = num3 * scale; return Mathf.Max(num4 * scale * 0.5f, num5 / (2f * num)) * 1.05f; } catch { return fallback; } } private void TryResolveMiniatureBounds() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if (_boundsResolved) { return; } try { Map instance = Map.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.OverLayerParent == (Object)null) { return; } bool has = false; float minX = 0f; float maxX = 0f; float minZ = 0f; float maxZ = 0f; int num = AccumulateBoundsXZ(instance.OverLayerParent, ref has, ref minX, ref maxX, ref minZ, ref maxZ); int num2 = 0; if (instance.Layers != null) { for (int i = 0; i < instance.Layers.Count; i++) { MapLayer val = instance.Layers[i]; if (!((Object)(object)val == (Object)null)) { num2 += AccumulateBoundsXZ(((Component)val).transform, ref has, ref minX, ref maxX, ref minZ, ref maxZ); } } } if (has) { _boundsCenterXZ = new Vector2((minX + maxX) * 0.5f, (minZ + maxZ) * 0.5f); _boundsHalfW = (maxX - minX) * 0.5f; _boundsHalfH = (maxZ - minZ) * 0.5f; _boundsResolved = true; WLog.Line("mapoverlay_bounds_resolved", false, ("cx", _boundsCenterXZ.x), ("cz", _boundsCenterXZ.y), ("hw", _boundsHalfW), ("hh", _boundsHalfH), ("rcOver", num), ("rcLayers", num2)); } } catch (Exception ex) { WLog.Line("mapoverlay_bounds_error", false, ("err", ex.Message)); } } private static int AccumulateBoundsXZ(Transform root, ref bool has, ref float minX, ref float maxX, ref float minZ, ref float maxZ) { //IL_004d: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)root == (Object)null) { return 0; } Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); if (componentsInChildren == null) { return 0; } int num = 0; foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null || !val.enabled || !((Component)val).gameObject.activeInHierarchy) { continue; } Bounds bounds = val.bounds; Vector3 min = ((Bounds)(ref bounds)).min; Vector3 max = ((Bounds)(ref bounds)).max; if (!has) { minX = min.x; maxX = max.x; minZ = min.z; maxZ = max.z; has = true; } else { if (min.x < minX) { minX = min.x; } if (max.x > maxX) { maxX = max.x; } if (min.z < minZ) { minZ = min.z; } if (max.z > maxZ) { maxZ = max.z; } } num++; } return num; } private void UpdateGrid(bool show) { if ((Object)(object)_gridRoot == (Object)null) { return; } if (!show || !_boundsResolved) { ((Component)_gridRoot).gameObject.SetActive(false); return; } MapGridLayout mapGridLayout = ComputeGridLayout(); if (mapGridLayout == null) { ((Component)_gridRoot).gameObject.SetActive(false); return; } ((Component)_gridRoot).gameObject.SetActive(true); ApplyGridLayout(mapGridLayout); } private MapGridLayout ComputeGridLayout() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) try { LevelGenerator instance = LevelGenerator.Instance; Map instance2 = Map.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)instance2.OverLayerParent == (Object)null || (Object)(object)_cachedCamera == (Object)null) { return null; } Vector3 position = instance2.OverLayerParent.position; Vector3 position2 = ((Component)_cachedCamera).transform.position; EnsureGridModuleRange(instance); MapGridCellRange? mapGridCellRange = _gridModuleRange ?? MapGridMath.RangeFromBounds(instance.LevelWidth, instance.LevelHeight, instance2.Scale, position.x, position.z, _boundsCenterXZ.x - _boundsHalfW, _boundsCenterXZ.x + _boundsHalfW, _boundsCenterXZ.y - _boundsHalfH, _boundsCenterXZ.y + _boundsHalfH); if (!mapGridCellRange.HasValue) { return null; } return MapGridMath.Compute(instance.LevelWidth, mapGridCellRange.Value, instance2.Scale, position.x, position.z, position2.x, position2.z, _cachedCamera.orthographicSize, Mathf.Max(0.01f, _cachedCamera.aspect), 1200f, 720f); } catch (Exception ex) { WLog.Line("mapoverlay_grid_error", false, ("err", ex.Message)); return null; } } private void EnsureGridModuleRange(LevelGenerator lg) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (_gridModuleRangeResolved) { return; } _gridModuleRangeResolved = true; try { Module[] array = Object.FindObjectsOfType(); List<(float, float)> list = new List<(float, float)>((array != null) ? array.Length : 0); if (array != null) { for (int i = 0; i < array.Length; i++) { if (!((Object)(object)array[i] == (Object)null)) { Vector3 position = ((Component)array[i]).transform.position; list.Add((position.x, position.z)); } } } _gridModuleRange = MapGridMath.RangeFromModuleCenters(list, lg.LevelWidth, lg.LevelHeight); WLog.Line("mapoverlay_grid_range", false, ("modules", list.Count), ("resolved", _gridModuleRange.HasValue ? 1 : 0), ("colMin", _gridModuleRange?.ColMin ?? (-1)), ("colMax", _gridModuleRange?.ColMax ?? (-1)), ("rowMin", _gridModuleRange?.RowMin ?? (-1)), ("rowMax", _gridModuleRange?.RowMax ?? (-1))); } catch (Exception ex) { WLog.Line("mapoverlay_grid_range_error", false, ("err", ex.Message)); _gridModuleRange = null; } } private void ApplyGridLayout(MapGridLayout g) { //IL_007a: 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) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: 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_01f8: Unknown result type (might be due to invalid IL or missing references) float num = g.RectRight - g.RectLeft; float num2 = g.RectTop - g.RectBottom; float num3 = (g.RectLeft + g.RectRight) * 0.5f; float num4 = (g.RectBottom + g.RectTop) * 0.5f; EnsureGridLines(_gridVLines, g.VerticalLineX.Length, "GridV"); for (int i = 0; i < g.VerticalLineX.Length; i++) { RectTransform obj = _gridVLines[i]; obj.anchoredPosition = new Vector2(g.VerticalLineX[i], num4); obj.sizeDelta = new Vector2(2f, num2); } EnsureGridLines(_gridHLines, g.HorizontalLineY.Length, "GridH"); for (int j = 0; j < g.HorizontalLineY.Length; j++) { RectTransform obj2 = _gridHLines[j]; obj2.anchoredPosition = new Vector2(num3, g.HorizontalLineY[j]); obj2.sizeDelta = new Vector2(num, 2f); } EnsureGridLabels(_gridColLabels, g.ColumnLabels.Length, "GridColLabel"); for (int k = 0; k < g.ColumnLabels.Length; k++) { TextMeshProUGUI val = _gridColLabels[k]; if (((TMP_Text)val).text != g.ColumnLabels[k]) { ((TMP_Text)val).text = g.ColumnLabels[k]; } ((TMP_Text)val).rectTransform.anchoredPosition = new Vector2(g.ColumnLabelX[k], g.ColumnLabelY); } EnsureGridLabels(_gridRowLabels, g.RowLabels.Length, "GridRowLabel"); for (int l = 0; l < g.RowLabels.Length; l++) { TextMeshProUGUI val2 = _gridRowLabels[l]; if (((TMP_Text)val2).text != g.RowLabels[l]) { ((TMP_Text)val2).text = g.RowLabels[l]; } ((TMP_Text)val2).rectTransform.anchoredPosition = new Vector2(g.RowLabelX, g.RowLabelY[l]); } } private void EnsureGridLines(List pool, int needed, string namePrefix) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) while (pool.Count < needed) { Image val = UiKit.CreateImage((Transform)(object)_gridRoot, namePrefix + pool.Count, Vector2.zero, new Vector2(2f, 2f), GridLineColor); ((Graphic)val).raycastTarget = false; pool.Add(((Graphic)val).rectTransform); } for (int i = 0; i < pool.Count; i++) { bool flag = i < needed; if (((Component)pool[i]).gameObject.activeSelf != flag) { ((Component)pool[i]).gameObject.SetActive(flag); } } } private void EnsureGridLabels(List pool, int needed, string namePrefix) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) while (pool.Count < needed) { TextMeshProUGUI val = UiKit.CreateText((Transform)(object)_gridRoot, namePrefix + pool.Count, Vector2.zero, new Vector2(48f, 30f), "", 26f, GridLabelColor, (TextAlignmentOptions)514); ((Graphic)val).raycastTarget = false; pool.Add(val); } for (int i = 0; i < pool.Count; i++) { bool flag = i < needed; if (((Component)pool[i]).gameObject.activeSelf != flag) { ((Component)pool[i]).gameObject.SetActive(flag); } } } public bool IsPointerOverPanel(Vector2 screenPoint) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!_open || (Object)(object)_mapPanelRect == (Object)null) { return false; } return RectTransformUtility.RectangleContainsScreenPoint(_mapPanelRect, screenPoint, (Camera)null); } private void EnsureCameraResolved() { if ((Object)(object)_cachedCamera != (Object)null) { return; } try { Camera[] array = Object.FindObjectsOfType(true); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null && ((Object)array[i]).name == "Dirt Finder Map Camera") { _cachedCamera = array[i]; break; } } if ((Object)(object)_cachedCamera == (Object)null) { WLog.Line("mapoverlay_camera_missing", false); } } catch (Exception ex) { WLog.Line("mapoverlay_camera_error", false, ("err", ex.Message)); _cachedCamera = null; } } private void RefreshMapTexture() { if (!((Object)(object)_mapImage == (Object)null)) { Texture val = null; if ((Object)(object)_cachedCamera != (Object)null) { val = (Texture)(object)_cachedCamera.activeTexture; } if ((Object)(object)val == (Object)null) { val = ResolvePlaceholder(); } _mapImage.texture = val; } } private Texture ResolvePlaceholder() { if (_placeholderResolved) { return _placeholderTex; } _placeholderResolved = true; try { _placeholderTex = (Texture)(object)AssetCatalog.GetTexture("mapoverlay_placeholder"); } catch (Exception ex) { WLog.Line("mapoverlay_placeholder_error", false, ("err", ex.Message)); _placeholderTex = null; } return _placeholderTex; } private static bool NoTextInputsActive() { try { return SemiFunc.NoTextInputsActive(); } catch { return true; } } private unsafe static string FormatKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)key != 0) { return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); } return "-"; } } public sealed class ModIntegrityHeader : IClientPanel { private static readonly Color NormalColor = new Color(0.12f, 0.15f, 0.2f, 0.94f); private static readonly Color CautionColor = new Color(0.42f, 0.3f, 0.04f, 0.96f); private static readonly Color SevereColor = new Color(0.42f, 0.06f, 0.06f, 0.96f); private GameObject _root; private Image _background; private TextMeshProUGUI _summary; private TextMeshProUGUI _selfWarning; private RectTransform _rect; public Action OnOpenPanel; public string LayerName => "ModIntegrity"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: 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_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0117: 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_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } _rect = UiKit.CreateRect(layerRoot, "WW_ModIntegrityHeader", new Vector2(0f, -24f), new Vector2(760f, 94f)); RectTransform rect = _rect; RectTransform rect2 = _rect; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0.5f, 1f); rect2.anchorMax = val; rect.anchorMin = val; _rect.pivot = new Vector2(0.5f, 1f); _root = ((Component)_rect).gameObject; _background = UiKit.CreateImage((Transform)(object)_rect, "Bg", Vector2.zero, new Vector2(760f, 94f), NormalColor); _summary = UiKit.CreateText((Transform)(object)_rect, "Summary", new Vector2(0f, 18f), new Vector2(730f, 42f), string.Empty, 28f, Color.white, (TextAlignmentOptions)514); _selfWarning = UiKit.CreateText((Transform)(object)_rect, "SelfWarning", new Vector2(0f, -22f), new Vector2(730f, 34f), string.Empty, 21f, new Color(1f, 0.82f, 0.35f), (TextAlignmentOptions)514); ((TMP_Text)_summary).richText = false; ((TMP_Text)_selfWarning).richText = false; _root.SetActive(false); } } public void SetSnapshot(ModIntegritySnapshot snapshot, int localActor) { //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null || snapshot == null) { return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; ModParticipantRecord modParticipantRecord = null; for (int i = 0; i < snapshot.Records.Count; i++) { ModParticipantRecord modParticipantRecord2 = snapshot.Records[i]; if (modParticipantRecord2.Actor == localActor) { modParticipantRecord = modParticipantRecord2; } switch (modParticipantRecord2.Status) { case ModIntegrityStatus.Baseline: num++; break; case ModIntegrityStatus.Match: num2++; break; case ModIntegrityStatus.Difference: num3++; break; case ModIntegrityStatus.Unavailable: num4++; break; case ModIntegrityStatus.Pending: num5++; break; } } bool flag = Math.Max(0, snapshot.Records.Count - num) == num2 && num3 == 0 && num4 == 0 && num5 == 0; ((TMP_Text)_summary).text = (flag ? Texts.Format(TextId.ModIntegrityHeaderAllMatchFormat, snapshot.Records.Count, snapshot.Records.Count) : Texts.Format(TextId.ModIntegrityHeaderCountsFormat, num, num2, num3, num4, num5)); ((TMP_Text)_selfWarning).text = string.Empty; if (modParticipantRecord != null && modParticipantRecord.Status == ModIntegrityStatus.Difference) { ((TMP_Text)_selfWarning).text = Texts.Format(TextId.ModIntegritySelfDifferenceFormat, modParticipantRecord.Summary.Missing, modParticipantRecord.Summary.Extra, modParticipantRecord.Summary.Version, modParticipantRecord.Summary.Content); } else if (modParticipantRecord != null && modParticipantRecord.Status == ModIntegrityStatus.Unavailable) { ((TMP_Text)_selfWarning).text = Texts.Get(TextId.ModIntegritySelfUnavailable); } ((Graphic)_background).color = ((num4 > 0 || num5 > 0) ? SevereColor : ((num3 > 0) ? CautionColor : NormalColor)); } public void Tick(bool active) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null)) { if (_root.activeSelf != active) { _root.SetActive(active); } if (active && !((Object)(object)_rect == (Object)null) && Input.GetMouseButtonDown(0) && RectTransformUtility.RectangleContainsScreenPoint(_rect, Vector2.op_Implicit(Input.mousePosition), (Camera)null)) { OnOpenPanel?.Invoke(); } } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _rect = null; _background = null; _summary = null; _selfWarning = null; } } public enum ModIntegrityPanelFilter { NeedsReview, Match, All } public sealed class ModIntegrityPanel : IClientPanel { private sealed class RowSlot { public RectTransform Rect; public Image Background; public TextMeshProUGUI Label; public int Actor; } private const float RowHeight = 44f; private const float RowStep = 46f; private readonly List _rowPool = new List(100); private readonly List _allRows = new List(100); private readonly Dictionary _byActor = new Dictionary(); private GameObject _root; private RectTransform _panelRect; private RectTransform _content; private ScrollRect _rowScroll; private TextMeshProUGUI _detailText; private RectTransform _detailViewport; private RectTransform _detailContent; private ScrollRect _detailScroll; private RectTransform _filterNeedsRect; private RectTransform _filterMatchRect; private RectTransform _filterAllRect; private RectTransform _closeRect; private readonly List _visibleRows = new List(100); private ModIntegrityPanelFilter _filter; private int _selectedActor = -1; private int _detailFailedActor = -1; private int _lastRenderedDetailActor = -1; private int _lastRenderedDetailRevision = -1; public Action OnDetailRequested; public Action OnClosed; public string LayerName => "ModIntegrity"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: 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_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_ModIntegrityPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Canvas obj = val.AddComponent(); obj.overrideSorting = true; obj.sortingOrder = 9999; val.AddComponent(); _root = val; Image obj2 = val.AddComponent(); ((Graphic)obj2).color = new Color(0f, 0f, 0f, 0.62f); ((Graphic)obj2).raycastTarget = true; _panelRect = UiKit.CreateRect((Transform)(object)val2, "Panel", Vector2.zero, new Vector2(1480f, 850f)); UiKit.CreateImage((Transform)(object)_panelRect, "Bg", Vector2.zero, _panelRect.sizeDelta, new Color(0.025f, 0.03f, 0.055f, 0.98f)); UiKit.CreateText((Transform)(object)_panelRect, "Title", new Vector2(0f, 392f), new Vector2(900f, 50f), Texts.Get(TextId.ModIntegrityPanelTitle), 34f, new Color(1f, 0.9f, 0.58f), (TextAlignmentOptions)514); _filterNeedsRect = BuildButton((Transform)(object)_panelRect, "FilterNeeds", new Vector2(-600f, 335f), new Vector2(170f, 46f), Texts.Get(TextId.ModIntegrityFilterNeedsReview)); _filterMatchRect = BuildButton((Transform)(object)_panelRect, "FilterMatch", new Vector2(-410f, 335f), new Vector2(150f, 46f), Texts.Get(TextId.ModIntegrityFilterMatch)); _filterAllRect = BuildButton((Transform)(object)_panelRect, "FilterAll", new Vector2(-245f, 335f), new Vector2(150f, 46f), Texts.Get(TextId.ModIntegrityFilterAll)); _closeRect = BuildButton((Transform)(object)_panelRect, "Close", new Vector2(650f, 392f), new Vector2(120f, 44f), Texts.Get(TextId.ModIntegrityButtonClose)); BuildRowsArea(_panelRect); BuildDetailArea(_panelRect); ((TMP_Text)UiKit.CreateText((Transform)(object)_panelRect, "Disclaimer", new Vector2(0f, -398f), new Vector2(1400f, 36f), Texts.Get(TextId.ModIntegrityDisclaimer), 18f, new Color(0.74f, 0.74f, 0.8f), (TextAlignmentOptions)514)).richText = false; _root.SetActive(false); } } public void SetRows(IReadOnlyList rows) { _allRows.Clear(); _byActor.Clear(); if (rows != null) { for (int i = 0; i < rows.Count; i++) { if (_allRows.Count >= 100) { break; } ModParticipantView modParticipantView = rows[i]; if (modParticipantView != null && !_byActor.ContainsKey(modParticipantView.Record.Actor)) { _allRows.Add(modParticipantView); _byActor[modParticipantView.Record.Actor] = modParticipantView; } } } _allRows.Sort(CompareRows); if (_selectedActor >= 0 && !_byActor.ContainsKey(_selectedActor)) { _selectedActor = -1; } ApplyFilter(); RefreshSelectedSummary(); if (Visible && _byActor.TryGetValue(_selectedActor, out var value) && value.Record.Status == ModIntegrityStatus.Difference) { OnDetailRequested?.Invoke(_selectedActor); } } public void Open(bool preferNeedsReview, int selectedActor = -1) { if (!((Object)(object)_root == (Object)null)) { _filter = ((!preferNeedsReview || !HasNeedsReview()) ? ModIntegrityPanelFilter.All : ModIntegrityPanelFilter.NeedsReview); _selectedActor = ((selectedActor >= 0 && _byActor.ContainsKey(selectedActor)) ? selectedActor : (-1)); _detailFailedActor = -1; _lastRenderedDetailActor = -1; _lastRenderedDetailRevision = -1; ApplyFilter(); if (_selectedActor < 0 && _visibleRows.Count > 0) { _selectedActor = _visibleRows[0].Record.Actor; } RefreshSelectedSummary(); _root.SetActive(true); if (_byActor.TryGetValue(_selectedActor, out var value) && value.Record.Status == ModIntegrityStatus.Difference) { OnDetailRequested?.Invoke(_selectedActor); } } } public void Close() { if (!((Object)(object)_root == (Object)null) && _root.activeSelf) { _root.SetActive(false); OnClosed?.Invoke(); } } public void Tick(ModIntegrityClientState clientState) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) if (!Visible) { return; } UiKit.KeepCursorFree(); Vector2 point = Vector2.op_Implicit(Input.mousePosition); bool mouseButtonDown = Input.GetMouseButtonDown(0); if (mouseButtonDown && Hit(_closeRect, point)) { Close(); return; } if (mouseButtonDown && Hit(_filterNeedsRect, point)) { SetFilter(ModIntegrityPanelFilter.NeedsReview); return; } if (mouseButtonDown && Hit(_filterMatchRect, point)) { SetFilter(ModIntegrityPanelFilter.Match); return; } if (mouseButtonDown && Hit(_filterAllRect, point)) { SetFilter(ModIntegrityPanelFilter.All); return; } if (mouseButtonDown) { for (int i = 0; i < _visibleRows.Count && i < _rowPool.Count; i++) { RowSlot rowSlot = _rowPool[i]; if (((Component)rowSlot.Rect).gameObject.activeInHierarchy && Hit(rowSlot.Rect, point)) { SelectActor(rowSlot.Actor); break; } } } RenderDetail(clientState, mouseButtonDown && Hit(_detailViewport, point)); } public void SetDetailTimedOut(int actor) { _detailFailedActor = actor; _lastRenderedDetailActor = -1; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _panelRect = null; _content = null; _rowScroll = null; _detailText = null; _detailViewport = null; _detailContent = null; _detailScroll = null; _rowPool.Clear(); _allRows.Clear(); _visibleRows.Clear(); _byActor.Clear(); _selectedActor = -1; } private void BuildRowsArea(RectTransform parent) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_008d: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: 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_016c: Expected O, but got Unknown //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Expected O, but got Unknown //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Expected O, but got Unknown //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) RectTransform val = UiKit.CreateRect((Transform)(object)parent, "RowsArea", new Vector2(-385f, -25f), new Vector2(680f, 680f)); UiKit.CreateImage((Transform)(object)val, "Bg", Vector2.zero, val.sizeDelta, new Color(0.055f, 0.065f, 0.095f, 0.96f)); GameObject val2 = new GameObject("Scroll", new Type[1] { typeof(RectTransform) }); RectTransform val3 = (RectTransform)val2.transform; ((Transform)val3).SetParent((Transform)(object)val, false); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.offsetMin = new Vector2(14f, 14f); val3.offsetMax = new Vector2(-14f, -14f); Image obj = val2.AddComponent(); ((Graphic)obj).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj).raycastTarget = true; _rowScroll = val2.AddComponent(); _rowScroll.horizontal = false; _rowScroll.vertical = true; _rowScroll.inertia = false; _rowScroll.movementType = (MovementType)2; _rowScroll.scrollSensitivity = 46f; GameObject val4 = new GameObject("Viewport", new Type[1] { typeof(RectTransform) }); RectTransform val5 = (RectTransform)val4.transform; ((Transform)val5).SetParent((Transform)(object)val3, false); val5.anchorMin = Vector2.zero; val5.anchorMax = Vector2.one; val5.offsetMin = Vector2.zero; val5.offsetMax = Vector2.zero; val4.AddComponent(); Image obj2 = val4.AddComponent(); ((Graphic)obj2).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj2).raycastTarget = true; _rowScroll.viewport = val5; GameObject val6 = new GameObject("Content", new Type[1] { typeof(RectTransform) }); _content = (RectTransform)val6.transform; ((Transform)_content).SetParent((Transform)(object)val5, false); _content.anchorMin = new Vector2(0f, 1f); _content.anchorMax = new Vector2(1f, 1f); _content.pivot = new Vector2(0.5f, 1f); _content.anchoredPosition = Vector2.zero; _content.sizeDelta = new Vector2(0f, 4600f); _rowScroll.content = _content; Vector2 val8 = default(Vector2); for (int i = 0; i < 100; i++) { RectTransform val7 = UiKit.CreateRect((Transform)(object)_content, $"Row_{i}", new Vector2(0f, -22f - (float)i * 46f), new Vector2(630f, 44f)); ((Vector2)(ref val8))..ctor(0.5f, 1f); val7.anchorMax = val8; val7.anchorMin = val8; val7.pivot = new Vector2(0.5f, 0.5f); Image background = UiKit.CreateImage((Transform)(object)val7, "Bg", Vector2.zero, val7.sizeDelta, new Color(0.1f, 0.11f, 0.15f, 0.96f)); TextMeshProUGUI val9 = UiKit.CreateText((Transform)(object)val7, "Label", Vector2.zero, new Vector2(610f, 44f), string.Empty, 20f, Color.white, (TextAlignmentOptions)513); ((TMP_Text)val9).richText = false; ((Component)val7).gameObject.SetActive(false); _rowPool.Add(new RowSlot { Rect = val7, Background = background, Label = val9, Actor = -1 }); } } private void BuildDetailArea(RectTransform parent) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_008d: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Expected O, but got Unknown //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Expected O, but got Unknown //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) RectTransform val = UiKit.CreateRect((Transform)(object)parent, "DetailArea", new Vector2(370f, -25f), new Vector2(720f, 680f)); UiKit.CreateImage((Transform)(object)val, "Bg", Vector2.zero, val.sizeDelta, new Color(0.045f, 0.05f, 0.08f, 0.98f)); GameObject val2 = new GameObject("DetailScroll", new Type[1] { typeof(RectTransform) }); RectTransform val3 = (RectTransform)val2.transform; ((Transform)val3).SetParent((Transform)(object)val, false); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.offsetMin = new Vector2(20f, 20f); val3.offsetMax = new Vector2(-20f, -20f); Image obj = val2.AddComponent(); ((Graphic)obj).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj).raycastTarget = true; _detailScroll = val2.AddComponent(); _detailScroll.horizontal = false; _detailScroll.vertical = true; _detailScroll.inertia = false; _detailScroll.movementType = (MovementType)2; _detailScroll.scrollSensitivity = 42f; GameObject val4 = new GameObject("Viewport", new Type[1] { typeof(RectTransform) }); _detailViewport = (RectTransform)val4.transform; ((Transform)_detailViewport).SetParent((Transform)(object)val3, false); _detailViewport.anchorMin = Vector2.zero; _detailViewport.anchorMax = Vector2.one; _detailViewport.offsetMin = Vector2.zero; _detailViewport.offsetMax = Vector2.zero; val4.AddComponent(); Image obj2 = val4.AddComponent(); ((Graphic)obj2).color = new Color(0f, 0f, 0f, 0.001f); ((Graphic)obj2).raycastTarget = true; _detailScroll.viewport = _detailViewport; GameObject val5 = new GameObject("Content", new Type[1] { typeof(RectTransform) }); _detailContent = (RectTransform)val5.transform; ((Transform)_detailContent).SetParent((Transform)(object)_detailViewport, false); _detailContent.anchorMin = new Vector2(0f, 1f); _detailContent.anchorMax = new Vector2(1f, 1f); _detailContent.pivot = new Vector2(0.5f, 1f); _detailContent.anchoredPosition = Vector2.zero; _detailContent.sizeDelta = new Vector2(0f, 610f); _detailScroll.content = _detailContent; _detailText = UiKit.CreateText((Transform)(object)_detailContent, "Detail", Vector2.zero, new Vector2(650f, 610f), string.Empty, 20f, Color.white, (TextAlignmentOptions)257); RectTransform rectTransform = ((TMP_Text)_detailText).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 1f); rectTransform.anchorMax = new Vector2(0.5f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = Vector2.zero; ((TMP_Text)_detailText).richText = false; ((TMP_Text)_detailText).enableWordWrapping = true; ((TMP_Text)_detailText).overflowMode = (TextOverflowModes)0; } private RectTransform BuildButton(Transform parent, string name, Vector2 position, Vector2 size, string text) { //IL_0002: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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) Image obj = UiKit.CreateImage(parent, name, position, size, new Color(0.18f, 0.2f, 0.28f, 0.98f)); ((Graphic)obj).raycastTarget = false; ((TMP_Text)UiKit.CreateText((Transform)(object)((Graphic)obj).rectTransform, "Label", Vector2.zero, size, text, 20f, Color.white, (TextAlignmentOptions)514)).richText = false; return ((Graphic)obj).rectTransform; } private void SetFilter(ModIntegrityPanelFilter filter) { if (_filter == filter) { return; } _filter = filter; ApplyFilter(); bool flag = false; for (int i = 0; i < _visibleRows.Count; i++) { if (_visibleRows[i].Record.Actor == _selectedActor) { flag = true; break; } } if (!flag) { _selectedActor = ((_visibleRows.Count > 0) ? _visibleRows[0].Record.Actor : (-1)); } ApplyFilter(); RefreshSelectedSummary(); if (_byActor.TryGetValue(_selectedActor, out var value) && value.Record.Status == ModIntegrityStatus.Difference) { OnDetailRequested?.Invoke(_selectedActor); } } private void ApplyFilter() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) _visibleRows.Clear(); for (int i = 0; i < _allRows.Count; i++) { ModParticipantView modParticipantView = _allRows[i]; if (MatchesFilter(modParticipantView.Record.Status)) { _visibleRows.Add(modParticipantView); } } for (int j = 0; j < _rowPool.Count; j++) { RowSlot rowSlot = _rowPool[j]; if (j >= _visibleRows.Count) { rowSlot.Actor = -1; ((Component)rowSlot.Rect).gameObject.SetActive(false); continue; } ModParticipantView modParticipantView2 = _visibleRows[j]; rowSlot.Actor = modParticipantView2.Record.Actor; ((TMP_Text)rowSlot.Label).text = FormatRow(modParticipantView2); ((Graphic)rowSlot.Background).color = RowColor(modParticipantView2.Record.Status, modParticipantView2.Record.Actor == _selectedActor); ((Component)rowSlot.Rect).gameObject.SetActive(true); } if ((Object)(object)_content != (Object)null) { _content.sizeDelta = new Vector2(0f, Math.Max(640f, (float)_visibleRows.Count * 46f)); } if ((Object)(object)_rowScroll != (Object)null) { _rowScroll.verticalNormalizedPosition = 1f; } } private bool MatchesFilter(ModIntegrityStatus status) { switch (_filter) { case ModIntegrityPanelFilter.NeedsReview: if (status != ModIntegrityStatus.Pending && status != ModIntegrityStatus.Difference) { return status == ModIntegrityStatus.Unavailable; } return true; case ModIntegrityPanelFilter.Match: if (status != ModIntegrityStatus.Baseline) { return status == ModIntegrityStatus.Match; } return true; default: return true; } } private void SelectActor(int actor) { _selectedActor = actor; _detailFailedActor = -1; _lastRenderedDetailActor = -1; ApplyFilter(); RefreshSelectedSummary(); if (_byActor.TryGetValue(actor, out var value) && value.Record.Status == ModIntegrityStatus.Difference) { OnDetailRequested?.Invoke(actor); } } private void RefreshSelectedSummary() { if (!((Object)(object)_detailText == (Object)null)) { if (!_byActor.TryGetValue(_selectedActor, out var value)) { SetDetailText(string.Empty); } else { SetDetailText(FormatSummary(value)); } } } private void RenderDetail(ModIntegrityClientState state, bool detailClicked) { if (!_byActor.TryGetValue(_selectedActor, out var value)) { return; } ModIntegrityDetail detail; if (value.Record.Status != ModIntegrityStatus.Difference) { if (_lastRenderedDetailActor != _selectedActor) { SetDetailText(FormatSummary(value)); _lastRenderedDetailActor = _selectedActor; } } else if (state != null && state.TryGetDetail(_selectedActor, out detail)) { if (_lastRenderedDetailActor == detail.Actor && _lastRenderedDetailRevision == detail.Revision) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(value.DisplayName).Append('\n').Append(FormatStatus(value.Record.Status)) .Append("\n\n"); for (int i = 0; i < detail.Differences.Count; i++) { ModDifference modDifference = detail.Differences[i]; switch (modDifference.Kind) { case ModDifferenceKind.Missing: stringBuilder.AppendLine(Texts.Format(TextId.ModIntegrityDetailMissingFormat, modDifference.Name, modDifference.Guid)); break; case ModDifferenceKind.Extra: stringBuilder.AppendLine(Texts.Format(TextId.ModIntegrityDetailExtraFormat, modDifference.Name, modDifference.Guid)); break; case ModDifferenceKind.Version: stringBuilder.AppendLine(Texts.Format(TextId.ModIntegrityDetailVersionFormat, modDifference.Name, modDifference.BaselineValue, modDifference.ParticipantValue)); break; case ModDifferenceKind.Content: stringBuilder.AppendLine(Texts.Format(TextId.ModIntegrityDetailContentFormat, modDifference.Name, modDifference.BaselineValue, modDifference.ParticipantValue)); break; } } SetDetailText(stringBuilder.ToString()); _lastRenderedDetailActor = detail.Actor; _lastRenderedDetailRevision = detail.Revision; } else if (_detailFailedActor == _selectedActor) { SetDetailText(FormatSummary(value) + "\n\n" + Texts.Get(TextId.ModIntegrityDetailFailed)); if (detailClicked) { _detailFailedActor = -1; SetDetailText(FormatSummary(value) + "\n\n" + Texts.Get(TextId.ModIntegrityDetailLoading)); OnDetailRequested?.Invoke(_selectedActor); } } else { SetDetailText(FormatSummary(value) + "\n\n" + Texts.Get(TextId.ModIntegrityDetailLoading)); } } private void SetDetailText(string text) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_detailText == (Object)null)) { ((TMP_Text)_detailText).text = text ?? string.Empty; ((TMP_Text)_detailText).ForceMeshUpdate(false, false); float num = Math.Max(610f, ((TMP_Text)_detailText).preferredHeight + 24f); ((TMP_Text)_detailText).rectTransform.sizeDelta = new Vector2(650f, num); if ((Object)(object)_detailContent != (Object)null) { _detailContent.sizeDelta = new Vector2(0f, num); } if ((Object)(object)_detailScroll != (Object)null) { _detailScroll.verticalNormalizedPosition = 1f; } } } private bool HasNeedsReview() { for (int i = 0; i < _allRows.Count; i++) { ModIntegrityStatus status = _allRows[i].Record.Status; if (status == ModIntegrityStatus.Pending || status == ModIntegrityStatus.Difference || status == ModIntegrityStatus.Unavailable) { return true; } } return false; } private static int CompareRows(ModParticipantView a, ModParticipantView b) { int num = StatusRank(a.Record.Status); int value = StatusRank(b.Record.Status); int num2 = num.CompareTo(value); if (num2 == 0) { return a.Record.Actor.CompareTo(b.Record.Actor); } return num2; } private static int StatusRank(ModIntegrityStatus status) { return status switch { ModIntegrityStatus.Baseline => 0, ModIntegrityStatus.Unavailable => 1, ModIntegrityStatus.Difference => 2, ModIntegrityStatus.Pending => 3, _ => 4, }; } private static string FormatRow(ModParticipantView row) { ModParticipantRecord record = row.Record; string text = StatusIcon(record.Status); string text2 = FormatStatus(record.Status); string text3 = ((record.Status == ModIntegrityStatus.Difference) ? $" -{record.Summary.Missing} +{record.Summary.Extra} V{record.Summary.Version} C{record.Summary.Content}" : ((record.Status == ModIntegrityStatus.Unavailable) ? (" " + FormatReason(record.UnavailableReason)) : string.Empty)); return text + " " + row.DisplayName + " [" + text2 + "]" + text3; } private static string FormatSummary(ModParticipantView row) { ModParticipantRecord record = row.Record; string text = row.DisplayName + "\n" + StatusIcon(record.Status) + " " + FormatStatus(record.Status); if (record.Status == ModIntegrityStatus.Unavailable) { text = text + "\n" + FormatReason(record.UnavailableReason); } if (record.Status == ModIntegrityStatus.Difference) { text += $"\n不足 {record.Summary.Missing} / 追加 {record.Summary.Extra} / Version {record.Summary.Version} / 内容 {record.Summary.Content}"; } return text; } private static string StatusIcon(ModIntegrityStatus status) { return status switch { ModIntegrityStatus.Baseline => "◆", ModIntegrityStatus.Match => "✓", ModIntegrityStatus.Difference => "!", ModIntegrityStatus.Unavailable => "×", _ => "?", }; } private static string FormatStatus(ModIntegrityStatus status) { return status switch { ModIntegrityStatus.Baseline => Texts.Get(TextId.ModIntegrityStatusBaseline), ModIntegrityStatus.Match => Texts.Get(TextId.ModIntegrityStatusMatch), ModIntegrityStatus.Difference => Texts.Get(TextId.ModIntegrityStatusDifference), ModIntegrityStatus.Unavailable => Texts.Get(TextId.ModIntegrityStatusUnavailable), _ => Texts.Get(TextId.ModIntegrityStatusPending), }; } private static string FormatReason(ModUnavailableReason reason) { return reason switch { ModUnavailableReason.NoResponse => Texts.Get(TextId.ModIntegrityReasonNoResponse), ModUnavailableReason.UnsupportedProtocol => Texts.Get(TextId.ModIntegrityReasonUnsupportedProtocol), ModUnavailableReason.TooLarge => Texts.Get(TextId.ModIntegrityReasonTooLarge), ModUnavailableReason.ManifestCollectionFailed => Texts.Get(TextId.ModIntegrityReasonCollectionFailed), _ => Texts.Get(TextId.ModIntegrityReasonInvalidPayload), }; } private static Color RowColor(ModIntegrityStatus status, bool selected) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) Color val = default(Color); switch (status) { case ModIntegrityStatus.Baseline: ((Color)(ref val))..ctor(0.18f, 0.18f, 0.28f, 0.98f); break; case ModIntegrityStatus.Match: ((Color)(ref val))..ctor(0.06f, 0.22f, 0.13f, 0.98f); break; case ModIntegrityStatus.Difference: ((Color)(ref val))..ctor(0.35f, 0.24f, 0.04f, 0.98f); break; case ModIntegrityStatus.Unavailable: ((Color)(ref val))..ctor(0.35f, 0.05f, 0.07f, 0.98f); break; default: ((Color)(ref val))..ctor(0.15f, 0.16f, 0.2f, 0.98f); break; } if (!selected) { return val; } return Color.Lerp(val, Color.white, 0.24f); } private static bool Hit(RectTransform rect, Vector2 point) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)rect != (Object)null && ((Component)rect).gameObject.activeInHierarchy) { return RectTransformUtility.RectangleContainsScreenPoint(rect, point, (Camera)null); } return false; } } public sealed class ModIntegrityPlayerBadge : MonoBehaviour { private const float BadgeX = 135f; private const float BadgeYOffsetFromName = 5f; private TextMeshProUGUI _label; private RectTransform _rect; private int _lastRevision = -1; private ModIntegrityStatus _lastStatus; public static ModIntegrityPlayerBadge GetOrCreate(MenuPlayerListed row) { if ((Object)(object)row == (Object)null) { return null; } ModIntegrityPlayerBadge modIntegrityPlayerBadge = ((Component)row).GetComponent(); if ((Object)(object)modIntegrityPlayerBadge == (Object)null) { modIntegrityPlayerBadge = ((Component)row).gameObject.AddComponent(); } modIntegrityPlayerBadge.EnsureBuilt(); return modIntegrityPlayerBadge; } public void SetRecord(ModParticipantRecord record, int revision) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) EnsureBuilt(); if (!((Object)(object)_label == (Object)null)) { if (record == null) { ((Component)_label).gameObject.SetActive(false); } else if (_lastRevision != revision || _lastStatus != record.Status || !((Component)_label).gameObject.activeSelf) { _lastRevision = revision; _lastStatus = record.Status; ((TMP_Text)_label).text = Icon(record.Status); ((Graphic)_label).color = ColorFor(record.Status); ((Component)_label).gameObject.SetActive(true); } } } public void Hide() { if ((Object)(object)_label != (Object)null) { ((Component)_label).gameObject.SetActive(false); } } private void EnsureBuilt() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_label != (Object)null) { AlignWithPlayerName(); return; } _rect = UiKit.CreateRect(((Component)this).transform, "WW_ModIntegrityBadge", new Vector2(135f, 0f), new Vector2(34f, 30f)); _label = UiKit.CreateText((Transform)(object)_rect, "Label", Vector2.zero, _rect.sizeDelta, "?", 22f, Color.white, (TextAlignmentOptions)514); ((TMP_Text)_label).richText = false; ((Graphic)_label).raycastTarget = false; ((Component)_label).gameObject.SetActive(false); AlignWithPlayerName(); } private void AlignWithPlayerName() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_rect == (Object)null)) { MenuPlayerListed component = ((Component)this).GetComponent(); RectTransform val = (((Object)(object)component != (Object)null && (Object)(object)component.playerName != (Object)null) ? ((TMP_Text)component.playerName).rectTransform : null); if (!((Object)(object)val == (Object)null)) { Transform transform = ((Component)this).transform; Rect rect = val.rect; Vector3 val2 = transform.InverseTransformPoint(((Transform)val).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center))); Vector2 anchoredPosition = _rect.anchoredPosition; anchoredPosition.x = 135f; _rect.anchoredPosition = anchoredPosition; Vector3 localPosition = ((Transform)_rect).localPosition; localPosition.y = val2.y + 5f; ((Transform)_rect).localPosition = localPosition; } } } private static string Icon(ModIntegrityStatus status) { return status switch { ModIntegrityStatus.Baseline => "◆", ModIntegrityStatus.Match => "✓", ModIntegrityStatus.Difference => "!", ModIntegrityStatus.Unavailable => "×", _ => "?", }; } private static Color ColorFor(ModIntegrityStatus status) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) return (Color)(status switch { ModIntegrityStatus.Baseline => new Color(0.78f, 0.72f, 1f), ModIntegrityStatus.Match => new Color(0.4f, 1f, 0.55f), ModIntegrityStatus.Difference => new Color(1f, 0.75f, 0.2f), ModIntegrityStatus.Unavailable => new Color(1f, 0.28f, 0.3f), _ => new Color(0.85f, 0.85f, 0.85f), }); } } public sealed class ResultScreen : IClientPanel { private sealed class Slot { private readonly GameObject _go; private readonly TextMeshProUGUI _mark; private readonly RectTransform _avatarHolder; private readonly TextMeshProUGUI _name; private readonly TextMeshProUGUI _role; private readonly TextMeshProUGUI _status; private MenuPlayerListed _listed; internal Slot(GameObject go, TextMeshProUGUI mark, RectTransform avatarHolder, TextMeshProUGUI name, TextMeshProUGUI role, TextMeshProUGUI status) { _go = go; _mark = mark; _avatarHolder = avatarHolder; _name = name; _role = role; _status = status; } internal void SetTop(float yFromTop) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown PlaceTop((RectTransform)_go.transform, yFromTop, 56f); } internal void Show(ResultRow row, PlayerAvatar avatar) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (row == null) { Hide(); return; } ((TMP_Text)_mark).text = (row.IsWinningSide ? "★" : string.Empty); SetAvatar(avatar, row.Name); ((Component)_name).gameObject.SetActive((Object)(object)_listed == (Object)null); ((TMP_Text)_name).text = row.Name ?? string.Empty; ((TMP_Text)_role).text = RoleLabel(row.Role); ((Graphic)_role).color = RoleColor(row.Role, row.Alive); ((TMP_Text)_status).text = StatusLabel(row.Status); Color color = (row.Alive ? new Color(1f, 1f, 1f, 1f) : new Color(0.7f, 0.7f, 0.7f, 0.9f)); ((Graphic)_name).color = color; if (!_go.activeSelf) { _go.SetActive(true); } } internal void Hide() { SetAvatar(null, null); if (_go.activeSelf) { _go.SetActive(false); } } private void SetAvatar(PlayerAvatar avatar, string playerName) { if ((Object)(object)_listed != (Object)null) { Object.Destroy((Object)(object)((Component)_listed).gameObject); _listed = null; } if ((Object)(object)avatar != (Object)null) { _listed = ListedAvatar.TryCreate((Transform)(object)_avatarHolder, avatar, playerName); } } } private const float ContentWidth = 1100f; private const float PanelWidth = 900f; private const float BannerHeight = 100f; private const float BannerY = 430f; private const float ViewportHeight = 760f; private const float ViewportCenterY = -20f; private const float FooterY = -470f; private const float RowHeight = 56f; private const float RowSpacing = 4f; private const float RowInnerMargin = 24f; private const float BannerFontSize = 52f; private const float RowFontSize = 26f; private const float FooterFontSize = 30f; private const float DigestHeaderHeight = 44f; private const float DigestLineHeight = 32f; private const float DigestFontSize = 24f; private const float SectionGap = 24f; private const float ContentTopPad = 8f; private const float ContentBottomPad = 16f; private const float ScrollStep = 90f; private const float AvatarScale = 1.8f; private const float AvatarHolderX = -330f; private const float AvatarHolderY = -29f; private GameObject _root; private TextMeshProUGUI _bannerText; private Image _bannerImage; private RectTransform _content; private TextMeshProUGUI _footerText; private readonly List _slots = new List(); private readonly List _digestObjects = new List(); private float _contentHeight; private float _scrollY; public string LayerName => "Result"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_013e: 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_0179: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_ResultScreen", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; Image obj = UiKit.CreateImage((Transform)(object)val2, "Bg", Vector2.zero, new Vector2(1920f, 1080f), new Color(0f, 0f, 0f, 0.82f)); RectTransform rectTransform = ((Graphic)obj).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; ((Graphic)obj).raycastTarget = false; _bannerImage = UiKit.CreateImage((Transform)(object)val2, "Banner", new Vector2(0f, 430f), new Vector2(900f, 100f), new Color(0.2f, 0.2f, 0.25f, 0.9f)); ((Graphic)_bannerImage).raycastTarget = false; _bannerText = UiKit.CreateText((Transform)(object)((Graphic)_bannerImage).rectTransform, "BannerText", Vector2.zero, new Vector2(900f, 100f), string.Empty, 52f, new Color(1f, 0.95f, 0.6f, 1f), (TextAlignmentOptions)514); RectTransform rectTransform2 = ((TMP_Text)_bannerText).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.offsetMin = Vector2.zero; rectTransform2.offsetMax = Vector2.zero; RectTransform val3 = UiKit.CreateRect((Transform)(object)val2, "Viewport", new Vector2(0f, -20f), new Vector2(1100f, 760f)); ((Component)val3).gameObject.AddComponent(); _content = UiKit.CreateRect((Transform)(object)val3, "Content", Vector2.zero, new Vector2(1100f, 760f)); _content.anchorMin = new Vector2(0.5f, 1f); _content.anchorMax = new Vector2(0.5f, 1f); _content.pivot = new Vector2(0.5f, 1f); _content.anchoredPosition = Vector2.zero; _footerText = UiKit.CreateText((Transform)(object)val2, "Footer", new Vector2(0f, -470f), new Vector2(1100f, 48f), string.Empty, 30f, new Color(1f, 0.95f, 0.6f, 1f), (TextAlignmentOptions)514); _root.SetActive(false); WLog.Line("result_screen_built", false); } } public void Show(Team winningTeam, IReadOnlyList rows, Func resolveAvatar = null, IReadOnlyList digestLines = null, string footerText = null) { //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0120: 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_0154: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } ((TMP_Text)_bannerText).text = FormatBanner(winningTeam); ((TMP_Text)_footerText).text = footerText ?? string.Empty; int num = rows?.Count ?? 0; EnsureSlots(num); float num2 = 8f; for (int i = 0; i < _slots.Count; i++) { if (i < num) { PlayerAvatar avatar = null; if (resolveAvatar != null && rows[i] != null && rows[i].ActorNumber > 0) { try { avatar = resolveAvatar(rows[i].ActorNumber); } catch { avatar = null; } } _slots[i].SetTop(num2); _slots[i].Show(rows[i], avatar); num2 += 60f; } else { _slots[i].Hide(); } } ClearDigestObjects(); int num3 = digestLines?.Count ?? 0; if (num3 > 0) { num2 += 24f; TextMeshProUGUI val = UiKit.CreateText((Transform)(object)_content, "DigestHeader", Vector2.zero, new Vector2(1060f, 44f), Texts.Get(TextId.ResultDigestHeader), 24f, new Color(1f, 0.9f, 0.55f, 1f), (TextAlignmentOptions)514); PlaceTop(((TMP_Text)val).rectTransform, num2, 44f); _digestObjects.Add(((Component)val).gameObject); num2 += 44f; for (int j = 0; j < num3; j++) { TextMeshProUGUI val2 = UiKit.CreateText((Transform)(object)_content, "DigestLine" + j, Vector2.zero, new Vector2(980f, 32f), digestLines[j] ?? string.Empty, 24f, new Color(0.9f, 0.92f, 0.95f, 1f), (TextAlignmentOptions)4097); PlaceTop(((TMP_Text)val2).rectTransform, num2, 32f); _digestObjects.Add(((Component)val2).gameObject); num2 += 32f; } } _contentHeight = num2 + 16f; _content.sizeDelta = new Vector2(1100f, Mathf.Max(_contentHeight, 760f)); _scrollY = 0f; _content.anchoredPosition = Vector2.zero; if (!_root.activeSelf) { _root.SetActive(true); } WLog.Line("result_screen_show", false, ("team", winningTeam), ("rows", num), ("digest", num3)); } public void Tick() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (Visible && !((Object)(object)_content == (Object)null)) { float y = Input.mouseScrollDelta.y; if (y != 0f) { float num = Mathf.Max(0f, _contentHeight - 760f); _scrollY = Mathf.Clamp(_scrollY - y * 90f, 0f, num); _content.anchoredPosition = new Vector2(0f, _scrollY); } } } public void SetFooter(string footerText) { if ((Object)(object)_footerText != (Object)null) { ((TMP_Text)_footerText).text = footerText ?? string.Empty; } } public void Hide() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _slots.Clear(); _digestObjects.Clear(); _bannerText = null; _bannerImage = null; _content = null; _footerText = null; } private void EnsureSlots(int count) { while (_slots.Count < count) { _slots.Add(BuildSlot(_content, _slots.Count)); } } private void ClearDigestObjects() { foreach (GameObject digestObject in _digestObjects) { if ((Object)(object)digestObject != (Object)null) { Object.Destroy((Object)(object)digestObject); } } _digestObjects.Clear(); } private static void PlaceTop(RectTransform rt, float yFromTop, float height) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) rt.anchorMin = new Vector2(0.5f, 1f); rt.anchorMax = new Vector2(0.5f, 1f); rt.anchoredPosition = new Vector2(0f, 0f - (yFromTop + height * 0.5f)); } private static string FormatBanner(Team team) { return team switch { Team.Villagers => Texts.Get(TextId.ResultBannerVillagerWin), Team.Werewolves => Texts.Get(TextId.ResultBannerWerewolfWin), _ => Texts.Get(TextId.ResultBannerDefault), }; } private static string StatusLabel(ResultRowStatus status) { return status switch { ResultRowStatus.Alive => Texts.Get(TextId.ResultStatusAlive), ResultRowStatus.Executed => Texts.Get(TextId.ResultStatusExecuted), ResultRowStatus.Disconnected => Texts.Get(TextId.ResultStatusDisconnected), _ => Texts.Get(TextId.ResultStatusDead), }; } private static string RoleLabel(Role role) { return role switch { Role.Werewolf => Texts.Get(TextId.RoleNameWerewolf), Role.BlackCat => Texts.Get(TextId.RoleNameBlackCat), Role.Villager => Texts.Get(TextId.RoleNameVillager), Role.Bomber => Texts.Get(TextId.RoleNameBomber), Role.Shaman => Texts.Get(TextId.RoleNameShaman), _ => role.ToString(), }; } private static Color RoleColor(Role role, bool alive) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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) Color result = ((RoleDistribution.TeamOf(role) == Team.Werewolves) ? new Color(1f, 0.45f, 0.45f, 1f) : new Color(0.55f, 0.85f, 1f, 1f)); if (!alive) { result.r *= 0.75f; result.g *= 0.75f; result.b *= 0.75f; result.a = 0.9f; } return result; } private static Slot BuildSlot(RectTransform parent, int index) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0120: 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_0152: 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_0181: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: 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_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) Image obj = UiKit.CreateImage((Transform)(object)parent, $"Row{index}", Vector2.zero, new Vector2(900f, 56f), new Color(1f, 1f, 1f, 0.06f)); ((Graphic)obj).raycastTarget = false; RectTransform rectTransform = ((Graphic)obj).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 1f); rectTransform.anchorMax = new Vector2(0.5f, 1f); TextMeshProUGUI mark = UiKit.CreateText((Transform)(object)rectTransform, "Mark", new Vector2(-414f, 0f), new Vector2(32f, 56f), string.Empty, 26f, new Color(1f, 0.9f, 0.4f, 1f), (TextAlignmentOptions)514); RectTransform val = UiKit.CreateRect((Transform)(object)rectTransform, "AvatarHolder", new Vector2(-330f, -29f), new Vector2(220f, 56f)); ((Transform)val).localScale = new Vector3(1.8f, 1.8f, 1f); TextMeshProUGUI name = UiKit.CreateText((Transform)(object)rectTransform, "Name", new Vector2(-225f, 0f), new Vector2(405f, 56f), string.Empty, 26f, new Color(1f, 1f, 1f, 1f), (TextAlignmentOptions)4097); TextMeshProUGUI role = UiKit.CreateText((Transform)(object)rectTransform, "Role", new Vector2(135f, 0f), new Vector2(180f, 56f), string.Empty, 26f, new Color(1f, 0.95f, 0.75f, 1f), (TextAlignmentOptions)514); TextMeshProUGUI status = UiKit.CreateText((Transform)(object)rectTransform, "Status", new Vector2(386f, 0f), new Vector2(80f, 56f), string.Empty, 26f, new Color(0.85f, 0.85f, 0.9f, 1f), (TextAlignmentOptions)4100); ((Component)obj).gameObject.SetActive(false); return new Slot(((Component)obj).gameObject, mark, val, name, role, status); } } public sealed class RoleRevealCinematic : IClientPanel { public const float MoonUiWaitTimeoutSec = 10f; private static readonly Vector2 IconSize = new Vector2(256f, 256f); private const float TitleFontSize = 64f; private const float BodyFontSize = 34f; private const float BodyLineHeight = 44f; private const float PageCrossfadeSec = 0.3f; private const float SkipFadeOutSec = 0.2f; private const float SkipHintFontSize = 24f; private const string HeadingColorTag = ""; private GameObject _root; private CanvasGroup _group; private Image _background; private Image _iconImage; private TextMeshProUGUI _titleText; private TextMeshProUGUI _bodyText; private CanvasGroup _bodyGroup; private TextMeshProUGUI _skipHintText; private bool _skipRequested; public string LayerName => "RoleReveal"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_RoleReveal", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _group = val.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; _background = UiKit.CreateImage((Transform)(object)val2, "Bg", Vector2.zero, new Vector2(1920f, 1080f), new Color(0f, 0f, 0f, 0.85f)); RectTransform rectTransform = ((Graphic)_background).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; ((Graphic)_background).raycastTarget = false; _iconImage = UiKit.CreateImage((Transform)(object)val2, "Icon", new Vector2(0f, 180f), IconSize, Color.white); ((Graphic)_iconImage).raycastTarget = false; ((Behaviour)_iconImage).enabled = false; _titleText = UiKit.CreateText((Transform)(object)val2, "Title", new Vector2(0f, -40f), new Vector2(1600f, 100f), "", 64f, new Color(1f, 0.95f, 0.7f), (TextAlignmentOptions)514); ((TMP_Text)_titleText).enableWordWrapping = false; _bodyText = UiKit.CreateText((Transform)(object)val2, "Body", new Vector2(0f, -220f), new Vector2(1600f, 400f), "", 34f, new Color(1f, 1f, 1f, 0.95f), (TextAlignmentOptions)514); ((TMP_Text)_bodyText).enableWordWrapping = true; _bodyGroup = ((Component)_bodyText).gameObject.AddComponent(); _bodyGroup.alpha = 1f; _bodyGroup.blocksRaycasts = false; _bodyGroup.interactable = false; _skipHintText = UiKit.CreateText((Transform)(object)val2, "SkipHint", new Vector2(0f, -470f), new Vector2(1200f, 40f), "", 24f, new Color(1f, 1f, 1f, 0.65f), (TextAlignmentOptions)514); _root.SetActive(false); WLog.Line("reveal_built", false); } } private void Populate(RevealContent content) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && content != null) { ((TMP_Text)_titleText).text = content.Title ?? string.Empty; ((TMP_Text)_skipHintText).text = BuildSkipHintText(); Sprite sprite = AssetCatalog.GetSprite(IconKey(content.Icon)); if ((Object)(object)sprite != (Object)null) { _iconImage.sprite = sprite; _iconImage.preserveAspect = true; ((Graphic)_iconImage).color = Color.white; ((Behaviour)_iconImage).enabled = true; } else { _iconImage.sprite = null; ((Behaviour)_iconImage).enabled = false; } } } private void SetPage(RevealPage page) { if (page?.BodyLines == null) { ((TMP_Text)_bodyText).text = string.Empty; return; } StringBuilder stringBuilder = new StringBuilder(); string[] bodyLines = page.BodyLines; foreach (string text in bodyLines) { if (stringBuilder.Length > 0) { stringBuilder.Append('\n'); } if (text != null && text.StartsWith("◆")) { stringBuilder.Append("").Append(text).Append(""); } else { stringBuilder.Append(text); } } ((TMP_Text)_bodyText).text = stringBuilder.ToString(); } private static string IconKey(RoleIcon icon) { return icon switch { RoleIcon.Villager => "role_villager", RoleIcon.Werewolf => "role_werewolf", RoleIcon.BlackCat => "role_blackcat", RoleIcon.Bomber => "role_bomber", RoleIcon.Shaman => "role_shaman", _ => null, }; } public IEnumerator Play(RevealContent content) { if ((Object)(object)_root == (Object)null || content == null || content.Pages == null || content.Pages.Length == 0) { WLog.Line("reveal_skip", false, ("reason", ((Object)(object)_root == (Object)null) ? "not_built" : "no_content")); yield break; } yield return WaitForMoonUiFinish(10f); _skipRequested = false; Populate(content); SetPage(content.Pages[0]); _group.alpha = 0f; _bodyGroup.alpha = 1f; _root.SetActive(true); WLog.Line("reveal_show", true, ("icon", content.Icon), ("pages", content.Pages.Length)); yield return DriveWithSkip(UiTween.Fade(_group, 0f, 1f, content.FadeInSec, UiTween.EaseInOut())); for (int i = 0; i < content.Pages.Length; i++) { if (_skipRequested) { break; } if (i > 0) { yield return DriveWithSkip(UiTween.Fade(_bodyGroup, 1f, 0f, 0.3f, UiTween.EaseInOut())); if (_skipRequested) { break; } SetPage(content.Pages[i]); yield return DriveWithSkip(UiTween.Fade(_bodyGroup, 0f, 1f, 0.3f, UiTween.EaseInOut())); if (_skipRequested) { break; } } yield return DriveWithSkip(UiTween.Hold(content.Pages[i].HoldSec)); } float duration = (_skipRequested ? 0.2f : content.FadeOutSec); yield return DriveSuppressOnly(UiTween.Fade(_group, _group.alpha, 0f, duration, UiTween.EaseInOut())); HideNow(); WLog.Line("reveal_end", false, ("skipped", _skipRequested)); } private IEnumerator DriveWithSkip(IEnumerator step) { while (true) { SuppressEscMenu(); if (!_skipRequested && SkipInputDown()) { _skipRequested = true; WLog.Line("reveal_skip_input", false); } if (_skipRequested) { break; } bool flag; try { flag = step?.MoveNext() ?? false; } catch (Exception ex) { WLog.Line("reveal_tick_error", false, ("err", ex.Message)); flag = false; } if (!flag) { break; } yield return step.Current; } } private static IEnumerator DriveSuppressOnly(IEnumerator step) { while (true) { SuppressEscMenu(); bool flag; try { flag = step?.MoveNext() ?? false; } catch (Exception ex) { WLog.Line("reveal_tick_error", false, ("err", ex.Message)); flag = false; } if (!flag) { break; } yield return step.Current; } } private static void SuppressEscMenu() { try { GameDirector instance = GameDirector.instance; if ((Object)(object)instance != (Object)null) { instance.SetDisableEscMenu(1f); } } catch { } } private static bool SkipInputDown() { try { return SemiFunc.NoTextInputsActive() && SemiFunc.InputDown((InputKey)6); } catch { return false; } } private static string BuildSkipHintText() { string text = Texts.Get(TextId.RevealSkipHint); try { InputManager instance = InputManager.instance; if ((Object)(object)instance != (Object)null) { text = instance.InputDisplayReplaceTags(text, "", ""); } } catch { } return text; } private static IEnumerator WaitForMoonUiFinish(float timeoutSec) { MoonUI val; try { val = MoonUI.instance; } catch { val = null; } if ((Object)(object)val == (Object)null || (Object)(object)val.objectActive == (Object)null || !val.objectActive.activeSelf) { yield break; } float elapsed = 0f; while (elapsed < timeoutSec) { MoonUI val2; try { val2 = MoonUI.instance; } catch { val2 = null; } if ((Object)(object)val2 == (Object)null || (Object)(object)val2.objectActive == (Object)null || !val2.objectActive.activeSelf) { WLog.Line("reveal_moon_ok", false, ("waitedSec", elapsed)); yield break; } elapsed += Time.unscaledDeltaTime; yield return null; } WLog.Line("reveal_moon_timeout", false, ("timeoutSec", timeoutSec)); } public void HideNow() { if (!((Object)(object)_root == (Object)null)) { _group.alpha = 0f; _root.SetActive(false); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _group = null; _background = null; _iconImage = null; _titleText = null; _bodyText = null; _bodyGroup = null; _skipHintText = null; } } public sealed class SfxPlayer { private AudioSource _source; private AudioSource _stoppableSource; private AudioSource _loopSource; private bool _mixerResolved; public bool Exists => (Object)(object)_source != (Object)null; public bool CanPlay { get { if ((Object)(object)_source != (Object)null) { return (Object)(object)_source.outputAudioMixerGroup != (Object)null; } return false; } } public void Build(GameObject host) { if (!((Object)(object)_source != (Object)null) && !((Object)(object)host == (Object)null)) { _source = CreateSource(host); _stoppableSource = CreateSource(host); _loopSource = CreateSource(host); _loopSource.loop = true; TryResolveMixerGroup(); WLog.Line("sfx_built", false, ("mixer", ((Object)(object)_source.outputAudioMixerGroup != (Object)null) ? ((Object)_source.outputAudioMixerGroup).name : "none")); } } public void Play(string clipKey, float volumeScale = 1f) { if ((Object)(object)_source == (Object)null) { return; } if ((Object)(object)_source.outputAudioMixerGroup == (Object)null && !_mixerResolved) { TryResolveMixerGroup(); } if ((Object)(object)_source.outputAudioMixerGroup == (Object)null) { return; } AudioClip clip = AssetCatalog.GetClip(clipKey); if ((Object)(object)clip == (Object)null) { return; } try { _source.PlayOneShot(clip, volumeScale); } catch (Exception ex) { WLog.Line("sfx_play_error", false, ("key", clipKey ?? ""), ("err", ex.GetType().Name)); } } public void PlayStoppable(string clipKey, float volumeScale = 1f) { if ((Object)(object)_stoppableSource == (Object)null) { return; } if ((Object)(object)_stoppableSource.outputAudioMixerGroup == (Object)null && !_mixerResolved) { TryResolveMixerGroup(); } if ((Object)(object)_stoppableSource.outputAudioMixerGroup == (Object)null) { return; } AudioClip clip = AssetCatalog.GetClip(clipKey); if ((Object)(object)clip == (Object)null) { return; } try { _stoppableSource.Stop(); _stoppableSource.clip = clip; _stoppableSource.volume = volumeScale; _stoppableSource.Play(); } catch (Exception ex) { WLog.Line("sfx_play_error", false, ("key", clipKey ?? ""), ("err", ex.GetType().Name)); } } public void PlayLoop(string clipKey, float volume) { if ((Object)(object)_loopSource == (Object)null) { return; } if ((Object)(object)_loopSource.outputAudioMixerGroup == (Object)null && !_mixerResolved) { TryResolveMixerGroup(); } if ((Object)(object)_loopSource.outputAudioMixerGroup == (Object)null) { return; } AudioClip clip = AssetCatalog.GetClip(clipKey); if ((Object)(object)clip == (Object)null) { return; } try { _loopSource.volume = Mathf.Clamp01(volume); if ((Object)(object)_loopSource.clip != (Object)(object)clip) { _loopSource.Stop(); _loopSource.clip = clip; } if (!_loopSource.isPlaying) { _loopSource.Play(); } } catch (Exception ex) { WLog.Line("sfx_play_error", false, ("key", clipKey ?? ""), ("err", ex.GetType().Name)); } } public void StopLoop() { if ((Object)(object)_loopSource == (Object)null) { return; } try { if (_loopSource.isPlaying) { _loopSource.Stop(); } } catch (Exception ex) { WLog.Line("sfx_stop_error", false, ("err", ex.GetType().Name)); } } public void StopStoppable() { if ((Object)(object)_stoppableSource == (Object)null) { return; } try { if (_stoppableSource.isPlaying) { _stoppableSource.Stop(); } } catch (Exception ex) { WLog.Line("sfx_stop_error", false, ("err", ex.GetType().Name)); } } public void Destroy() { if ((Object)(object)_source != (Object)null) { Object.Destroy((Object)(object)_source); _source = null; } if ((Object)(object)_stoppableSource != (Object)null) { Object.Destroy((Object)(object)_stoppableSource); _stoppableSource = null; } if ((Object)(object)_loopSource != (Object)null) { Object.Destroy((Object)(object)_loopSource); _loopSource = null; } _mixerResolved = false; } private static AudioSource CreateSource(GameObject host) { AudioSource obj = host.AddComponent(); obj.playOnAwake = false; obj.loop = false; obj.spatialBlend = 0f; obj.dopplerLevel = 0f; obj.bypassEffects = false; obj.bypassListenerEffects = false; obj.bypassReverbZones = false; obj.volume = 1f; return obj; } private void TryResolveMixerGroup() { if ((Object)(object)_source == (Object)null) { return; } try { AudioSource[] array = Resources.FindObjectsOfTypeAll(); if (array == null || array.Length == 0) { return; } foreach (AudioSource val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)_source || (Object)(object)val == (Object)(object)_stoppableSource || (Object)(object)val == (Object)(object)_loopSource) { continue; } AudioMixerGroup outputAudioMixerGroup = val.outputAudioMixerGroup; if (!((Object)(object)outputAudioMixerGroup == (Object)null) && !IsVoiceLikeGroup(((Object)outputAudioMixerGroup).name)) { _source.outputAudioMixerGroup = outputAudioMixerGroup; if ((Object)(object)_stoppableSource != (Object)null) { _stoppableSource.outputAudioMixerGroup = outputAudioMixerGroup; } if ((Object)(object)_loopSource != (Object)null) { _loopSource.outputAudioMixerGroup = outputAudioMixerGroup; } _mixerResolved = true; return; } } _mixerResolved = true; } catch (Exception ex) { WLog.Line("sfx_mixer_resolve_error", false, ("err", ex.GetType().Name)); _mixerResolved = true; } } private static bool IsVoiceLikeGroup(string name) { if (string.IsNullOrEmpty(name)) { return false; } string text = name.ToLowerInvariant(); if (!text.Contains("microphone") && !text.Contains("voice") && !text.Contains("tts")) { return text.Contains("music"); } return true; } } public sealed class ShamanSensePanel : IClientPanel { private const float StormAlphaWeak = 0.08f; private const float StormAlphaMedium = 0.11f; private const float StormAlphaStrong = 0.15f; private const float GhostBurstAlpha = 0.45f; private const int NoiseFrames = 4; private const int NoiseSize = 192; private const float NoiseFrameSec = 0.06f; private const float NoiseTilesX = 2.5f; private const float NoiseTilesY = 1.40625f; private const float TranceSaturation = -80f; private const float PostProcessRefreshSec = 0.1f; private const float VignetteMaxAlpha = 0.45f; private const float TranceFadeInSec = 1.2f; private const float TranceFadeOutSec = 0.25f; private const int VignetteSize = 256; private const int RippleTextureSize = 256; private const float RippleDurationSec = 0.9f; private const float RippleStartSize = 80f; private const float RippleEndSize = 1400f; private const float RippleMaxAlpha = 0.38f; private static readonly Color RippleColor = new Color(0.72f, 0.86f, 1f); private GameObject _root; private RawImage _stormImage; private RawImage _vignetteImage; private Texture2D _vignetteTexture; private RawImage _rippleImage; private Texture2D _rippleTexture; private float _rippleRemainingSec; private float _tranceFade; private Texture2D[] _noiseTextures; private int _noiseFrame; private float _noiseClock; public string LayerName => "ShamanSense"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00cd: 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_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0148: 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_017c: Expected O, but got Unknown //IL_0182: 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_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: 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) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Expected O, but got Unknown //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_ShamanSense", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; CanvasGroup obj = val.AddComponent(); obj.blocksRaycasts = false; obj.interactable = false; GameObject val3 = new GameObject("Vignette", new Type[1] { typeof(RectTransform) }); RectTransform val4 = (RectTransform)val3.transform; ((Transform)val4).SetParent((Transform)(object)val2, false); val4.anchorMin = Vector2.zero; val4.anchorMax = Vector2.one; val4.offsetMin = Vector2.zero; val4.offsetMax = Vector2.zero; _vignetteImage = val3.AddComponent(); ((Graphic)_vignetteImage).raycastTarget = false; _vignetteImage.texture = (Texture)(object)EnsureVignetteTexture(); ((Graphic)_vignetteImage).color = new Color(1f, 1f, 1f, 0f); ((Behaviour)_vignetteImage).enabled = false; GameObject val5 = new GameObject("DripRipple", new Type[1] { typeof(RectTransform) }); RectTransform val6 = (RectTransform)val5.transform; ((Transform)val6).SetParent((Transform)(object)val2, false); val6.anchorMin = new Vector2(0.5f, 0.5f); val6.anchorMax = new Vector2(0.5f, 0.5f); val6.pivot = new Vector2(0.5f, 0.5f); val6.anchoredPosition = Vector2.zero; val6.sizeDelta = new Vector2(80f, 80f); _rippleImage = val5.AddComponent(); ((Graphic)_rippleImage).raycastTarget = false; _rippleImage.texture = (Texture)(object)EnsureRippleTexture(); ((Graphic)_rippleImage).color = new Color(RippleColor.r, RippleColor.g, RippleColor.b, 0f); ((Behaviour)_rippleImage).enabled = false; GameObject val7 = new GameObject("Storm", new Type[1] { typeof(RectTransform) }); RectTransform val8 = (RectTransform)val7.transform; ((Transform)val8).SetParent((Transform)(object)val2, false); val8.anchorMin = Vector2.zero; val8.anchorMax = Vector2.one; val8.offsetMin = Vector2.zero; val8.offsetMax = Vector2.zero; _stormImage = val7.AddComponent(); ((Graphic)_stormImage).raycastTarget = false; _stormImage.texture = (Texture)(object)EnsureNoiseTextures()[0]; _stormImage.uvRect = new Rect(0f, 0f, 2.5f, 1.40625f); ((Graphic)_stormImage).color = new Color(1f, 1f, 1f, 0f); ((Behaviour)_stormImage).enabled = false; _root.SetActive(false); WLog.Line("shaman_sense_built", false); } } public void PlayDripRipple() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !((Object)(object)_rippleImage == (Object)null)) { _rippleRemainingSec = 0.9f; ((Graphic)_rippleImage).rectTransform.sizeDelta = new Vector2(80f, 80f); ((Graphic)_rippleImage).color = new Color(RippleColor.r, RippleColor.g, RippleColor.b, 0f); ((Behaviour)_rippleImage).enabled = true; if (!_root.activeSelf) { _root.SetActive(true); } } } public void Tick(ShamanStormTier tier, bool ghostBurst, bool gazeArmed, float deltaSeconds) { //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } float num = (ghostBurst ? 0.45f : StormAlpha(tier)); TickDripRipple(deltaSeconds); if (gazeArmed && (Object)(object)PostProcessing.Instance != (Object)null) { PostProcessing.Instance.SaturationOverride(-80f, 5f / 6f, 4f, 0.1f, _root); } float num2 = (gazeArmed ? (5f / 6f) : 4f); _tranceFade = Mathf.MoveTowards(_tranceFade, gazeArmed ? 1f : 0f, num2 * deltaSeconds); if (num <= 0f && _tranceFade <= 0f && _rippleRemainingSec <= 0f) { if (_root.activeSelf) { _root.SetActive(false); } return; } if (!_root.activeSelf) { _root.SetActive(true); } float num3 = _tranceFade * 0.45f; ((Behaviour)_vignetteImage).enabled = num3 > 0f; ((Graphic)_vignetteImage).color = new Color(1f, 1f, 1f, num3); ((Behaviour)_stormImage).enabled = num > 0f; if (num > 0f) { ((Graphic)_stormImage).color = new Color(1f, 1f, 1f, num); _noiseClock += deltaSeconds; if (_noiseClock >= 0.06f) { _noiseClock = 0f; _noiseFrame = (_noiseFrame + 1) % 4; _stormImage.texture = (Texture)(object)EnsureNoiseTextures()[_noiseFrame]; _stormImage.uvRect = new Rect(Random.value, Random.value, 2.5f, 1.40625f); } } } private void TickDripRipple(float deltaSeconds) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_rippleImage == (Object)null) && !(_rippleRemainingSec <= 0f)) { _rippleRemainingSec = Mathf.Max(0f, _rippleRemainingSec - deltaSeconds); float num = 1f - _rippleRemainingSec / 0.9f; float num2 = 1f - (1f - num) * (1f - num); float num3 = Mathf.Lerp(80f, 1400f, num2); ((Graphic)_rippleImage).rectTransform.sizeDelta = new Vector2(num3, num3); float num4 = Mathf.Clamp01(num / 0.08f); float num5 = 1f - Mathf.SmoothStep(0f, 1f, num); float num6 = 0.38f * num4 * num5; ((Graphic)_rippleImage).color = new Color(RippleColor.r, RippleColor.g, RippleColor.b, num6); if (_rippleRemainingSec <= 0f) { ((Behaviour)_rippleImage).enabled = false; } } } public void Hide() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } _tranceFade = 0f; _rippleRemainingSec = 0f; if ((Object)(object)_rippleImage != (Object)null) { ((Graphic)_rippleImage).color = new Color(RippleColor.r, RippleColor.g, RippleColor.b, 0f); ((Behaviour)_rippleImage).enabled = false; } if ((Object)(object)_vignetteImage != (Object)null) { ((Graphic)_vignetteImage).color = new Color(1f, 1f, 1f, 0f); ((Behaviour)_vignetteImage).enabled = false; } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _stormImage = null; _vignetteImage = null; _rippleImage = null; _tranceFade = 0f; _rippleRemainingSec = 0f; if ((Object)(object)_vignetteTexture != (Object)null) { Object.Destroy((Object)(object)_vignetteTexture); _vignetteTexture = null; } if ((Object)(object)_rippleTexture != (Object)null) { Object.Destroy((Object)(object)_rippleTexture); _rippleTexture = null; } if (_noiseTextures == null) { return; } Texture2D[] noiseTextures = _noiseTextures; foreach (Texture2D val in noiseTextures) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } _noiseTextures = null; } private static float StormAlpha(ShamanStormTier tier) { return tier switch { ShamanStormTier.Strong => 0.15f, ShamanStormTier.Medium => 0.11f, ShamanStormTier.Weak => 0.08f, _ => 0f, }; } private Texture2D EnsureVignetteTexture() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_vignetteTexture != (Object)null) { return _vignetteTexture; } Texture2D val = new Texture2D(256, 256, (TextureFormat)4, false); ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; Color32[] array = (Color32[])(object)new Color32[65536]; for (int i = 0; i < 256; i++) { float num = (float)i / 255f * 2f - 1f; for (int j = 0; j < 256; j++) { float num2 = (float)j / 255f * 2f - 1f; float num3 = Mathf.Clamp01((Mathf.Sqrt(num2 * num2 + num * num) - 0.55f) / 0.7f); num3 = num3 * num3 * (3f - 2f * num3); array[i * 256 + j] = new Color32((byte)0, (byte)0, (byte)0, (byte)(num3 * 255f)); } } val.SetPixels32(array); val.Apply(false, false); ((Object)val).name = "WW_ShamanVignette"; _vignetteTexture = val; return val; } private Texture2D EnsureRippleTexture() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rippleTexture != (Object)null) { return _rippleTexture; } Texture2D val = new Texture2D(256, 256, (TextureFormat)4, false); ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; Color32[] array = (Color32[])(object)new Color32[65536]; for (int i = 0; i < 256; i++) { float num = (float)i / 255f * 2f - 1f; for (int j = 0; j < 256; j++) { float num2 = (float)j / 255f * 2f - 1f; float num3 = Mathf.Sqrt(num2 * num2 + num * num); float num4 = Mathf.Clamp01(1f - Mathf.Abs(num3 - 0.78f) / 0.025f); float num5 = Mathf.Clamp01(1f - Mathf.Abs(num3 - 0.61f) / 0.018f) * 0.45f; byte b = (byte)(Mathf.Clamp01(num4 + num5) * 255f); array[i * 256 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b); } } val.SetPixels32(array); val.Apply(false, false); ((Object)val).name = "WW_ShamanDripRipple"; _rippleTexture = val; return val; } private Texture2D[] EnsureNoiseTextures() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (_noiseTextures != null) { return _noiseTextures; } _noiseTextures = (Texture2D[])(object)new Texture2D[4]; for (int i = 0; i < 4; i++) { Texture2D val = new Texture2D(192, 192, (TextureFormat)4, false); ((Texture)val).wrapMode = (TextureWrapMode)0; ((Texture)val).filterMode = (FilterMode)0; Color32[] array = (Color32[])(object)new Color32[36864]; for (int j = 0; j < array.Length; j++) { byte b = (byte)Random.Range(0, 256); array[j] = new Color32(b, b, b, byte.MaxValue); } val.SetPixels32(array); val.Apply(false, false); ((Object)val).name = "WW_ShamanNoise_" + i; _noiseTextures[i] = val; } return _noiseTextures; } } public sealed class StartHoldOverlay : IClientPanel { private const float BackdropAlpha = 0.85f; private const float FadeOutPerSec = 1.25f; private GameObject _root; private CanvasGroup _group; public string LayerName => "StartHold"; public bool Exists => (Object)(object)_root != (Object)null; public bool Visible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_StartHold", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; _root = val; _group = val.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; RectTransform rectTransform = ((Graphic)UiKit.CreateImage((Transform)val2, "Backdrop", Vector2.zero, new Vector2(1920f, 1080f), new Color(0f, 0f, 0f, 0.85f))).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; UiKit.CreateText((Transform)val2, "Message", new Vector2(0f, 0f), new Vector2(1600f, 100f), Texts.Get(TextId.StartHoldWaitingOthers), 44f, Color.white, (TextAlignmentOptions)514); _root.SetActive(false); } } public void Tick(bool holding) { if ((Object)(object)_root == (Object)null) { return; } if (holding) { if (!_root.activeSelf) { _root.SetActive(true); } _group.alpha = 1f; } else if (_root.activeSelf) { CanvasGroup obj = _group; obj.alpha -= 1.25f * Time.unscaledDeltaTime; if (_group.alpha <= 0f) { _group.alpha = 0f; _root.SetActive(false); } } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _group = null; } } public sealed class ToastPanel : IClientPanel { private sealed class Slot { private readonly GameObject _go; private readonly TextMeshProUGUI _text; internal Slot(GameObject go, TextMeshProUGUI text) { _go = go; _text = text; } internal void Show(string message) { if (((TMP_Text)_text).text != message) { ((TMP_Text)_text).text = message ?? string.Empty; } if (!_go.activeSelf) { _go.SetActive(true); } } internal void Hide() { if (_go.activeSelf) { _go.SetActive(false); } } } public const int SlotCount = 5; private const float RightMargin = 24f; private const float TopMargin = 24f; private const float SlotWidth = 960f; private const float SlotHeight = 88f; private const float SlotSpacing = 16f; private const float FontSize = 44f; private GameObject _root; private readonly List _slots = new List(5); public string LayerName => "Toast"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } GameObject val = new GameObject("WW_ToastPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(1f, 1f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(1f, 1f); val2.anchoredPosition = new Vector2(-24f, -24f); val2.sizeDelta = new Vector2(960f, 504f); _root = val; for (int i = 0; i < 5; i++) { _slots.Add(BuildSlot((Transform)(object)val2, i)); } WLog.Line("toast_panel_built", false, ("slots", 5)); } } public void Tick(ToastQueue queue, long nowUnixMs) { if ((Object)(object)_root == (Object)null || queue == null) { return; } IReadOnlyList readOnlyList = queue.Visible(nowUnixMs); int num = readOnlyList?.Count ?? 0; for (int i = 0; i < _slots.Count; i++) { if (i < num) { _slots[i].Show(readOnlyList[i].Message); } else { _slots[i].Hide(); } } } public void Hide() { for (int i = 0; i < _slots.Count; i++) { _slots[i].Hide(); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _slots.Clear(); } private static Slot BuildSlot(Transform parent, int index) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) float num = 0f - (float)index * 104f; Image val = UiKit.CreateImage(parent, $"Slot{index}", new Vector2(0f, num), new Vector2(960f, 88f), new Color(0f, 0f, 0f, 0.55f)); RectTransform rectTransform = ((Graphic)val).rectTransform; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(1f, 1f); rectTransform.anchorMax = val2; rectTransform.anchorMin = val2; rectTransform.pivot = new Vector2(1f, 1f); rectTransform.anchoredPosition = new Vector2(0f, num); ((Graphic)val).raycastTarget = false; TextMeshProUGUI val3 = UiKit.CreateText((Transform)(object)rectTransform, "Text", new Vector2(0f, 0f), new Vector2(940f, 82f), string.Empty, 44f, new Color(1f, 0.95f, 0.8f, 0.98f), (TextAlignmentOptions)4100); RectTransform rectTransform2 = ((TMP_Text)val3).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.offsetMin = new Vector2(10f, 3f); rectTransform2.offsetMax = new Vector2(-10f, -3f); ((TMP_Text)val3).enableWordWrapping = false; ((TMP_Text)val3).overflowMode = (TextOverflowModes)1; ((Component)val).gameObject.SetActive(false); return new Slot(((Component)val).gameObject, val3); } } public sealed class TutorialPresenter { private const float DisplaySeconds = 15f; private const float TextMaxWidth = 400f; private const float TextMinHeight = 42f; private const float FontRestoreDelaySeconds = 2f; private readonly List<(TutorialId Id, string Message)> _queue = new List<(TutorialId, string)>(); private float _remaining; private float _fontRestoreRemaining; private float _originalFontSizeMax = -1f; private TutorialUI _elevatedUi; public float FontScale { get; set; } = 1f; public void Enqueue(TutorialId id, string message) { if (!string.IsNullOrEmpty(message)) { _queue.Add((id, message)); } } public bool IsPending(TutorialId id) { for (int i = 0; i < _queue.Count; i++) { if (_queue[i].Id == id) { return true; } } return false; } public TutorialId? Tick() { TutorialUI instance = TutorialUI.instance; if (_remaining > 0f) { if ((Object)(object)instance == (Object)null) { _remaining = 0f; _fontRestoreRemaining = 0f; return null; } ((SemiUI)instance).Show(); _remaining -= Time.deltaTime; if (_remaining <= 0f) { _fontRestoreRemaining = 2f; } return null; } if (_queue.Count > 0) { if ((Object)(object)instance == (Object)null) { return null; } var (value, message) = _queue[0]; _queue.RemoveAt(0); Display(instance, message); _remaining = 15f; return value; } if (_fontRestoreRemaining > 0f) { _fontRestoreRemaining -= Time.deltaTime; if (_fontRestoreRemaining <= 0f && (Object)(object)instance != (Object)null && _originalFontSizeMax > 0f) { ((TMP_Text)instance.Text).fontSizeMax = _originalFontSizeMax; } } return null; } public void Cancel() { _queue.Clear(); if (_remaining > 0f) { _remaining = 0f; _fontRestoreRemaining = 2f; } } private void Display(TutorialUI ui, string message) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0093: Unknown result type (might be due to invalid IL or missing references) EnsureOnTop(ui); TextMeshProUGUI text = ui.Text; if (_originalFontSizeMax < 0f) { _originalFontSizeMax = ((TMP_Text)text).fontSizeMax; } ((TMP_Text)text).fontSizeMax = _originalFontSizeMax * Mathf.Clamp(FontScale, 0.1f, 2f); _fontRestoreRemaining = 0f; ((TMP_Text)text).text = message; Vector2 val = SemiFunc.PreferredAutoscaledTextSize(text, 400f, float.PositiveInfinity, 5); ui.mainTextContainer.sizeDelta = new Vector2(ui.mainTextContainer.sizeDelta.x, Mathf.Max(val.y, 42f)); ((SemiUI)ui).SemiUISpringShakeY(20f, 10f, 0.3f); ((SemiUI)ui).SemiUISpringScale(0.4f, 5f, 0.2f); } private void EnsureOnTop(TutorialUI ui) { if (!((Object)(object)ui == (Object)null) && !((Object)(object)_elevatedUi == (Object)(object)ui)) { GameObject gameObject = ((Component)ui).gameObject; Canvas val = gameObject.GetComponent(); if ((Object)(object)val == (Object)null) { val = gameObject.AddComponent(); } val.overrideSorting = true; val.sortingOrder = 10000; _elevatedUi = ui; } } } public static class UiTween { public static AnimationCurve Linear() { return AnimationCurve.Linear(0f, 0f, 1f, 1f); } public static AnimationCurve EaseInOut() { return AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); } public static AnimationCurve EaseOut() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_008e: Expected O, but got Unknown //IL_008f: Expected O, but got Unknown AnimationCurve val = new AnimationCurve((Keyframe[])(object)new Keyframe[5] { new Keyframe(0f, 0f), new Keyframe(0.25f, EaseOutValue(0.25f)), new Keyframe(0.5f, EaseOutValue(0.5f)), new Keyframe(0.75f, EaseOutValue(0.75f)), new Keyframe(1f, 1f) }); SmoothAll(val); return val; } public static AnimationCurve EaseIn() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_0080: Expected O, but got Unknown AnimationCurve val = new AnimationCurve((Keyframe[])(object)new Keyframe[5] { new Keyframe(0f, 0f), new Keyframe(0.25f, 0.0625f), new Keyframe(0.5f, 0.25f), new Keyframe(0.75f, 0.5625f), new Keyframe(1f, 1f) }); SmoothAll(val); return val; } private static float EaseOutValue(float t) { return 1f - (1f - t) * (1f - t); } private static void SmoothAll(AnimationCurve curve) { for (int i = 0; i < curve.length; i++) { curve.SmoothTangents(i, 0f); } } public static IEnumerator Scale(Transform target, Vector3 from, Vector3 to, float duration, AnimationCurve curve = null) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { WLog.Line("uitween_skip", false, ("op", "scale"), ("reason", "null_target")); yield break; } if (curve == null) { curve = Linear(); } if (duration <= 0f) { target.localScale = to; yield break; } float elapsed = 0f; while (elapsed < duration) { if ((Object)(object)target == (Object)null) { WLog.Line("uitween_skip", false, ("op", "scale"), ("reason", "destroyed")); yield break; } elapsed += Time.unscaledDeltaTime; float num = curve.Evaluate(Mathf.Clamp01(elapsed / duration)); target.localScale = Vector3.LerpUnclamped(from, to, num); yield return null; } if ((Object)(object)target != (Object)null) { target.localScale = to; } } public static IEnumerator Scale(Transform target, float from, float to, float duration, AnimationCurve curve = null) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) return Scale(target, Vector3.one * from, Vector3.one * to, duration, curve); } public static IEnumerator Fade(CanvasGroup group, float from, float to, float duration, AnimationCurve curve = null) { if ((Object)(object)group == (Object)null) { WLog.Line("uitween_skip", false, ("op", "fade"), ("reason", "null_group")); yield break; } if (curve == null) { curve = Linear(); } if (duration <= 0f) { group.alpha = to; yield break; } float elapsed = 0f; while (elapsed < duration) { if ((Object)(object)group == (Object)null) { WLog.Line("uitween_skip", false, ("op", "fade"), ("reason", "destroyed")); yield break; } elapsed += Time.unscaledDeltaTime; float num = curve.Evaluate(Mathf.Clamp01(elapsed / duration)); group.alpha = Mathf.LerpUnclamped(from, to, num); yield return null; } if ((Object)(object)group != (Object)null) { group.alpha = to; } } public static IEnumerator Move(RectTransform target, Vector2 from, Vector2 to, float duration, AnimationCurve curve = null) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { WLog.Line("uitween_skip", false, ("op", "move"), ("reason", "null_target")); yield break; } if (curve == null) { curve = Linear(); } if (duration <= 0f) { target.anchoredPosition = to; yield break; } float elapsed = 0f; while (elapsed < duration) { if ((Object)(object)target == (Object)null) { WLog.Line("uitween_skip", false, ("op", "move"), ("reason", "destroyed")); yield break; } elapsed += Time.unscaledDeltaTime; float num = curve.Evaluate(Mathf.Clamp01(elapsed / duration)); target.anchoredPosition = Vector2.LerpUnclamped(from, to, num); yield return null; } if ((Object)(object)target != (Object)null) { target.anchoredPosition = to; } } public static IEnumerator Hold(float duration) { if (!(duration <= 0f)) { float elapsed = 0f; while (elapsed < duration) { elapsed += Time.unscaledDeltaTime; yield return null; } } } public static IEnumerator Sequence(params IEnumerator[] steps) { if (steps == null) { yield break; } foreach (IEnumerator step in steps) { if (step == null) { continue; } while (true) { bool flag; try { flag = step.MoveNext(); } catch (Exception ex) { WLog.Line("uitween_tick_error", false, ("op", "sequence"), ("err", ex.Message)); flag = false; } if (!flag) { break; } yield return step.Current; } } } public static IEnumerator Parallel(params IEnumerator[] steps) { if (steps == null || steps.Length == 0) { yield break; } bool[] finished = new bool[steps.Length]; int remaining = 0; for (int i = 0; i < steps.Length; i++) { if (steps[i] == null) { finished[i] = true; } else { remaining++; } } while (remaining > 0) { for (int j = 0; j < steps.Length; j++) { if (!finished[j]) { bool flag; try { flag = steps[j].MoveNext(); } catch (Exception ex) { WLog.Line("uitween_tick_error", false, ("op", "parallel"), ("index", j), ("err", ex.Message)); flag = false; } if (!flag) { finished[j] = true; remaining--; } } } if (remaining > 0) { yield return null; } } } } public sealed class ValuableRecordHudPanel : IClientPanel { private const string IconKey = "icon_valuable_record"; private const float RightMargin = 25f; private const float IconSize = 112f; private const float PlatePadding = 8f; private const float LabelHeight = 34f; private const float LabelGap = 4f; private const float LabelFontSize = 26f; private const float StackGap = 12f; private const float LabelExtraWidth = 300f; private static readonly Color IconTint = new Color(1f, 0.95f, 0.85f, 1f); private static readonly Color GrayOverlayTint = new Color(0.25f, 0.25f, 0.25f, 0.82f); private static readonly Color IdleLabelColor = new Color(0.55f, 0.55f, 0.55f, 0.9f); private static readonly Color ActiveLabelColor = new Color(0.55f, 0.9f, 1f, 1f); private GameObject _root; private RectTransform _slotRect; private Image _icon; private Image _grayOverlay; private Sprite _iconSpriteColor; private Sprite _iconSpriteGray; private TextMeshProUGUI _iconFallback; private TextMeshProUGUI _keyLabel; private float _lastY = float.NaN; private bool _lastOn; private string _lastKeyName; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } float num = 120f; float num2 = num; float num3 = num + 4f + 34f; GameObject val = new GameObject("WW_ValuableRecordHudPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(1f, 0f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(1f, 0f); val2.anchoredPosition = new Vector2(-25f, CorpseReportHudPanel.BaseY(CorpseReportHudPanel.Layout.AboveMiniGauge)); val2.sizeDelta = new Vector2(num2, num3); _root = val; float num4 = num2 / 2f; float num5 = 38f + num / 2f; _slotRect = UiKit.CreateRect((Transform)(object)val2, "Slot", Vector2.zero, new Vector2(num, num)); SetAnchorsBottomLeft(_slotRect, new Vector2(num4, num5), new Vector2(num, num)); _icon = UiKit.CreateImage((Transform)(object)_slotRect, "Icon", Vector2.zero, new Vector2(112f, 112f), IconTint); Sprite sprite = AssetCatalog.GetSprite("icon_valuable_record"); if ((Object)(object)sprite != (Object)null) { _iconSpriteColor = sprite; _iconSpriteGray = AssetCatalog.GetGraySprite("icon_valuable_record"); _icon.sprite = _iconSpriteColor; _icon.preserveAspect = false; _grayOverlay = UiKit.CreateImage((Transform)(object)_slotRect, "RecordGray", Vector2.zero, new Vector2(112f, 112f), GrayOverlayTint); _grayOverlay.sprite = _iconSpriteGray ?? _iconSpriteColor; _grayOverlay.type = (Type)3; _grayOverlay.fillMethod = (FillMethod)1; _grayOverlay.fillOrigin = 1; _grayOverlay.fillAmount = 1f; ((Component)_grayOverlay).transform.SetSiblingIndex(((Component)_icon).transform.GetSiblingIndex() + 1); } else { ((Behaviour)_icon).enabled = false; _iconFallback = UiKit.CreateText((Transform)(object)_slotRect, "IconFallback", Vector2.zero, new Vector2(112f, 112f), "記録", 26f, IdleLabelColor, (TextAlignmentOptions)514); } _keyLabel = UiKit.CreateText((Transform)(object)val2, "KeyLabel", new Vector2(num4, 17f), new Vector2(num2 + 300f, 34f), string.Empty, 26f, IdleLabelColor, (TextAlignmentOptions)514); SetAnchorsBottomLeft(((TMP_Text)_keyLabel).rectTransform, new Vector2(num4, 17f), new Vector2(num2 + 300f, 34f)); _root.SetActive(false); WLog.Line("valuable_record_hud_built", false); } } public void Tick(bool visible, bool recordOn, float holdRatio, bool holdCharging, string keyName, CorpseReportHudPanel.Layout corpseLayout, bool corpseVisible) { //IL_0072: 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_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { if (_root.activeSelf != visible) { _root.SetActive(visible); } if (!visible) { _lastKeyName = null; return; } float num = CorpseReportHudPanel.BaseY(corpseLayout) + (corpseVisible ? 170f : 0f); if (!Mathf.Approximately(num, _lastY)) { _lastY = num; ((RectTransform)_root.transform).anchoredPosition = new Vector2(-25f, num); } float num2 = (holdCharging ? Mathf.Clamp01(holdRatio) : 0f); float num3 = (recordOn ? num2 : (1f - num2)); if ((Object)(object)_grayOverlay != (Object)null) { _grayOverlay.fillAmount = num3; } if ((Object)(object)_iconFallback != (Object)null) { ((Graphic)_iconFallback).color = Color.Lerp(ActiveLabelColor, IdleLabelColor, num3); } if ((Object)(object)_keyLabel != (Object)null) { ((Graphic)_keyLabel).color = Color.Lerp(ActiveLabelColor, IdleLabelColor, num3); } if (recordOn != _lastOn || !(keyName == _lastKeyName)) { _lastOn = recordOn; _lastKeyName = keyName; if ((Object)(object)_keyLabel != (Object)null) { ((TMP_Text)_keyLabel).text = Texts.Format(recordOn ? TextId.HudValuableRecordOnFormat : TextId.HudValuableRecordOffFormat, keyName ?? "?"); } } } catch (Exception ex) { WLog.Line("valuable_record_hud_tick_error", false, ("err", ex.Message)); } } public void Hide() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } _lastKeyName = null; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _slotRect = null; _icon = null; _grayOverlay = null; _iconFallback = null; _keyLabel = null; _lastY = float.NaN; _lastOn = false; _lastKeyName = null; } private static void SetAnchorsBottomLeft(RectTransform rect, Vector2 centerPos, Vector2 size) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0f, 0f); rect.anchorMax = val; rect.anchorMin = val; rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = centerPos; rect.sizeDelta = size; } } public interface ITargetSelectionSource { IReadOnlyList TargetActors { get; } bool AllowSkip { get; } int CurrentSelection { get; } bool CanConfirm(int localActor); void Confirm(int localActor, int targetActor); } public sealed class VotePanel : IClientPanel { private sealed class DefaultVoteTargetProvider : ITargetSelectionSource { private readonly VotePanel _panel; private readonly List _targets = new List(); public IReadOnlyList TargetActors { get { _targets.Clear(); MeetingClientState lastState = _panel._lastState; if (lastState != null) { foreach (VoteRow row in _panel._rows) { if (lastState.GetRowStatus(row.ActorNumber) == RowStatus.Alive) { _targets.Add(row.ActorNumber); } } } return _targets; } } public bool AllowSkip => true; public int CurrentSelection => -1; internal DefaultVoteTargetProvider(VotePanel panel) { _panel = panel; } public bool CanConfirm(int localActor) { MeetingClientState lastState = _panel._lastState; if (lastState == null || localActor < 0) { return false; } if (lastState.GetRowStatus(localActor) != RowStatus.Alive) { return false; } if (ContainsInt(lastState.VotedActors, localActor)) { return false; } if (_panel._pendingSend) { return false; } return true; } public void Confirm(int localActor, int targetActor) { _panel._pendingSend = true; _panel._myVoteTarget = targetActor; WLog.Line("vote_submit", true, ("target", targetActor)); _panel.OnVoteSubmit?.Invoke(targetActor); } } public const int SkipTarget = -1; private const float GridTopY = 230f; private static readonly Vector2 PanelSize = new Vector2(1280f, 790f); private static readonly Vector2 PanelPos = new Vector2(0f, -50f); private const float SlideStartOffsetY = -1000f; private static readonly Color PanelBgColor = new Color(0.02f, 0.02f, 0.05f, 0.85f); private static readonly Color HeaderBgColor = new Color(0.2f, 0.2f, 0.25f, 0.9f); private static readonly Color TitleTextColor = new Color(1f, 0.9f, 0.6f, 1f); private static readonly Color TimeNormalColor = new Color(0.95f, 0.95f, 1f, 0.95f); private static readonly Color TimeUrgentColor = new Color(1f, 0.35f, 0.35f); private static readonly Color ResultExecutedColor = new Color(1f, 0.4f, 0.4f); private static readonly Color ResultNoneColor = new Color(0.7f, 0.85f, 1f); private static readonly Color NeutralButtonEnabledColor = new Color(0.26f, 0.26f, 0.3f, 0.9f); private static readonly Color NeutralButtonDisabledColor = new Color(0.15f, 0.15f, 0.17f, 0.6f); private static readonly Color NeutralLabelDisabledColor = new Color(0.55f, 0.55f, 0.55f); private static readonly Color NeutralButtonHoverColor = new Color(0.42f, 0.42f, 0.48f, 1f); private static readonly ButtonPalette NeutralPalette = new ButtonPalette(NeutralButtonEnabledColor, NeutralButtonHoverColor, NeutralButtonDisabledColor, NeutralLabelDisabledColor); private readonly List _rows = new List(); private readonly Dictionary _rowsByActor = new Dictionary(); private GameObject _root; private RectTransform _rootRect; private TextMeshProUGUI _timeText; private TextMeshProUGUI _resultBanner; private Image _skipButtonBg; private TextMeshProUGUI _skipLabel; private TextMeshProUGUI _skipCountLabel; private Image _pagePrevBg; private Image _pageNextBg; private TextMeshProUGUI _pageLabel; private ITargetSelectionSource _selectionSource; private DefaultVoteTargetProvider _defaultProvider; private Func _teamMarkerRole; private MeetingClientState _lastState; private int _localActor = -1; private int _page; private bool _pendingSend; private bool _resultShown; private bool _countsApplied; private long _timeOverrideEndUnixMs; private const int ArmedNone = int.MinValue; private int _armedTarget = int.MinValue; private bool _skipHover; private bool _skipEnabled; private int _myVoteTarget = int.MinValue; private bool _slideActive; private bool _reorderApplied; public Func IsPointerBlocked; public bool Exists => (Object)(object)_root != (Object)null; public string LayerName => "Meeting"; public int CurrentPage => _page; private int PageCount { get { if (_rows.Count != 0) { return (_rows.Count + 14 - 1) / 14; } return 1; } } private bool SelectionLocked { get { if (_resultShown) { return _defaultProvider != null; } return false; } } public event Action OnVoteSubmit; public void Build(Transform layerRoot) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0182: 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_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_038c: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { RectTransform val = UiKit.CreateRect(layerRoot, "WW_VotePanel", PanelPos, PanelSize); _root = ((Component)val).gameObject; _rootRect = val; UiKit.CreateImage((Transform)(object)val, "Bg", Vector2.zero, PanelSize, PanelBgColor); UiKit.CreateImage((Transform)(object)val, "HeaderBg", new Vector2(0f, 358f), new Vector2(960f, 56f), HeaderBgColor); UiKit.CreateText((Transform)(object)val, "Title", new Vector2(0f, 358f), new Vector2(400f, 44f), Texts.Get(TextId.VoteMeetingTitle), 34f, TitleTextColor, (TextAlignmentOptions)514); _timeText = UiKit.CreateText((Transform)(object)val, "Time", new Vector2(390f, 358f), new Vector2(200f, 44f), "--:--", 30f, TimeNormalColor, (TextAlignmentOptions)514); _resultBanner = UiKit.CreateText((Transform)(object)val, "Result", new Vector2(0f, 318f), new Vector2(960f, 36f), "", 26f, ResultExecutedColor, (TextAlignmentOptions)514); ((Component)_resultBanner).gameObject.SetActive(false); _skipButtonBg = UiKit.CreateImage((Transform)(object)val, "SkipButton", new Vector2(0f, -352f), new Vector2(220f, 50f), NeutralButtonEnabledColor); _skipLabel = UiKit.CreateText((Transform)(object)((Graphic)_skipButtonBg).rectTransform, "Label", Vector2.zero, new Vector2(220f, 50f), Texts.Get(TextId.VoteSkipLabel), 24f, Color.white, (TextAlignmentOptions)514); Sprite sprite = AssetCatalog.GetSprite("icon_btn_skip"); if ((Object)(object)sprite != (Object)null) { Image obj = UiKit.CreateImage((Transform)(object)((Graphic)_skipButtonBg).rectTransform, "Icon", new Vector2(-87f, 0f), new Vector2(30f, 30f), Color.white); obj.sprite = sprite; obj.preserveAspect = true; } _skipCountLabel = UiKit.CreateText((Transform)(object)val, "SkipCount", new Vector2(175f, -352f), new Vector2(110f, 50f), "", 22f, TitleTextColor, (TextAlignmentOptions)4097); ((Component)_skipCountLabel).gameObject.SetActive(false); _pagePrevBg = UiKit.CreateImage((Transform)(object)val, "PagePrev", new Vector2(-420f, -352f), new Vector2(60f, 50f), NeutralButtonEnabledColor); UiKit.CreateText((Transform)(object)((Graphic)_pagePrevBg).rectTransform, "Label", Vector2.zero, new Vector2(60f, 50f), "◀", 24f, Color.white, (TextAlignmentOptions)514); _pageNextBg = UiKit.CreateImage((Transform)(object)val, "PageNext", new Vector2(420f, -352f), new Vector2(60f, 50f), NeutralButtonEnabledColor); UiKit.CreateText((Transform)(object)((Graphic)_pageNextBg).rectTransform, "Label", Vector2.zero, new Vector2(60f, 50f), "▶", 24f, Color.white, (TextAlignmentOptions)514); _pageLabel = UiKit.CreateText((Transform)(object)val, "PageLabel", new Vector2(-340f, -352f), new Vector2(90f, 50f), "", 22f, Color.white, (TextAlignmentOptions)514); SetPagerVisible(visible: false); WLog.Line("vote_panel_built", false); } } public void BeginMeeting(IReadOnlyList roster, Func resolveAvatar, int localActor) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } ClearRows(); _localActor = localActor; _page = 0; _pendingSend = false; _resultShown = false; _countsApplied = false; _armedTarget = int.MinValue; _skipHover = false; _myVoteTarget = int.MinValue; _slideActive = true; _reorderApplied = false; if ((Object)(object)_rootRect != (Object)null) { _rootRect.anchoredPosition = new Vector2(PanelPos.x, PanelPos.y + -1000f); } ((Component)_resultBanner).gameObject.SetActive(false); ((Component)_skipCountLabel).gameObject.SetActive(false); if (_selectionSource == null || _defaultProvider != null) { _defaultProvider = new DefaultVoteTargetProvider(this); _selectionSource = _defaultProvider; } if (roster != null) { foreach (WPlayer item in roster) { if (item != null) { VoteRow voteRow = VoteRow.Build(_root.transform, item, resolveAvatar, VoteRowGrid.RowSize); _rows.Add(voteRow); _rowsByActor[item.ActorNumber] = voteRow; } } } LayoutRows(); WLog.Line("vote_panel_begin", false, ("rows", _rows.Count), ("localActor", localActor), ("pages", PageCount)); } public void SetSelectionSource(ITargetSelectionSource source) { if (source == null) { _defaultProvider = new DefaultVoteTargetProvider(this); _selectionSource = _defaultProvider; } else { _selectionSource = source; _defaultProvider = null; } } public void SetTeamMarkerProvider(Func markedRole) { _teamMarkerRole = markedRole; if (markedRole != null) { return; } foreach (VoteRow row in _rows) { row.SetTeamMarker(null); } } public void ShowStatusBanner(string text) { _resultShown = true; if (!((Object)(object)_resultBanner == (Object)null)) { ((TMP_Text)_resultBanner).text = text ?? string.Empty; ((Component)_resultBanner).gameObject.SetActive(true); } } public void SetTimeOverride(long endUnixMs) { _timeOverrideEndUnixMs = endUnixMs; } public void NotifyVoteProgress() { if (_pendingSend && _lastState != null && !ContainsInt(_lastState.VotedActors, _localActor)) { _pendingSend = false; WLog.Line("vote_panel_reenabled", false, ("localActor", _localActor)); } } public void Tick(MeetingClientState state, long nowUnixMs) { if ((Object)(object)_root == (Object)null || state == null) { return; } _lastState = state; try { UpdateSlide(state, nowUnixMs); ApplyInitialSortIfNeeded(state); UpdateTime(state, nowUnixMs); UpdateRows(state); UpdateResult(state); HandleInput(state); } catch (Exception ex) { WLog.Line("vote_panel_tick_error", false, ("err", ex.Message)); } } public void EndMeeting() { ClearRows(); _pendingSend = false; _resultShown = false; _countsApplied = false; _armedTarget = int.MinValue; _skipHover = false; _myVoteTarget = int.MinValue; _slideActive = false; _reorderApplied = false; _timeOverrideEndUnixMs = 0L; _lastState = null; if ((Object)(object)_resultBanner != (Object)null) { ((Component)_resultBanner).gameObject.SetActive(false); } if ((Object)(object)_skipCountLabel != (Object)null) { ((Component)_skipCountLabel).gameObject.SetActive(false); } } public void Destroy() { ClearRows(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _lastState = null; } private void ClearRows() { foreach (VoteRow row in _rows) { row.Destroy(); } _rows.Clear(); _rowsByActor.Clear(); } private void LayoutRows() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) int pageCount = PageCount; if (_page >= pageCount) { _page = pageCount - 1; } if (_page < 0) { _page = 0; } for (int i = 0; i < _rows.Count; i++) { RectTransform root = _rows[i].Root; if (!((Object)(object)root == (Object)null)) { bool flag = i / 14 == _page; ((Component)root).gameObject.SetActive(flag); if (flag) { root.anchoredPosition = VoteRowGrid.Position(i % 14, 230f); } } } bool flag2 = pageCount > 1; SetPagerVisible(flag2); if (flag2 && (Object)(object)_pageLabel != (Object)null) { ((TMP_Text)_pageLabel).text = $"{_page + 1}/{pageCount}"; } } private void SetPagerVisible(bool visible) { if ((Object)(object)_pagePrevBg != (Object)null) { ((Component)_pagePrevBg).gameObject.SetActive(visible); } if ((Object)(object)_pageNextBg != (Object)null) { ((Component)_pageNextBg).gameObject.SetActive(visible); } if ((Object)(object)_pageLabel != (Object)null) { ((Component)_pageLabel).gameObject.SetActive(visible); } } private void UpdateSlide(MeetingClientState state, long nowUnixMs) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (_slideActive && !((Object)(object)_rootRect == (Object)null)) { float num = (float)state.GaugeMoveProgress(nowUnixMs); if (num >= 1f) { num = 1f; _slideActive = false; } float num2 = PanelPos.y + -1000f; float num3 = num2 + (PanelPos.y - num2) * num; _rootRect.anchoredPosition = new Vector2(PanelPos.x, num3); } } private void ApplyInitialSortIfNeeded(MeetingClientState state) { if (_reorderApplied) { return; } _reorderApplied = true; if (_rows.Count > 1) { _rows.Sort((VoteRow left, VoteRow right) => state.CompareRowOrder(left.ActorNumber, right.ActorNumber)); LayoutRows(); } } private void UpdateTime(MeetingClientState state, long nowUnixMs) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_timeText == (Object)null)) { long num = (((_timeOverrideEndUnixMs > 0) ? Math.Max(0L, _timeOverrideEndUnixMs - nowUnixMs) : state.RemainingMs(nowUnixMs)) + 999) / 1000; ((TMP_Text)_timeText).text = $"{num / 60:00}:{num % 60:00}"; ((Graphic)_timeText).color = ((num <= 10) ? TimeUrgentColor : TimeNormalColor); } } private void UpdateRows(MeetingClientState state) { bool flag = _selectionSource != null && _selectionSource.CanConfirm(_localActor); IReadOnlyList list = ((_selectionSource != null) ? _selectionSource.TargetActors : null); int num = ((_selectionSource != null) ? _selectionSource.CurrentSelection : (-1)); if (_armedTarget != int.MinValue && (!(_defaultProvider != null && flag) || SelectionLocked || !((_armedTarget == -1) ? _selectionSource.AllowSkip : ContainsActor(list, _armedTarget)))) { _armedTarget = int.MinValue; } bool flag2 = _myVoteTarget != int.MinValue && (_pendingSend || ContainsInt(state.VotedActors, _localActor)); foreach (VoteRow row in _rows) { RowStatus rowStatus = state.GetRowStatus(row.ActorNumber); row.SetStatus(rowStatus); row.SetVoted(ContainsInt(state.VotedActors, row.ActorNumber)); bool flag3 = rowStatus == RowStatus.Alive && !SelectionLocked && ContainsActor(list, row.ActorNumber); row.SetVoteButtonVisible(flag3); if (flag3) { row.SetVoteButtonEnabled(flag); } row.SetSelected(flag3 && row.ActorNumber == num); row.SetArmed(flag3 && row.ActorNumber == _armedTarget); row.SetMyVoteMarker(flag2 && row.ActorNumber == _myVoteTarget); row.SetTeamMarker((_teamMarkerRole != null) ? _teamMarkerRole(row.ActorNumber) : ((Role?)null)); row.SetHostMarker(state.CallerActor == row.ActorNumber); row.Tick(); } _skipEnabled = !SelectionLocked && flag && _selectionSource != null && _selectionSource.AllowSkip; RefreshSkipVisual(); } private void RefreshSkipVisual() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_skipButtonBg == (Object)null)) { bool flag = _skipEnabled && _defaultProvider != null && _armedTarget == -1; ButtonVisual.Resolve(NeutralPalette, flag, _skipHover, selected: false, _skipEnabled, out var bg, out var label); ((Graphic)_skipButtonBg).color = bg; if ((Object)(object)_skipLabel != (Object)null) { ((TMP_Text)_skipLabel).text = (flag ? Texts.Get(TextId.VoteConfirmLabel) : Texts.Get(TextId.VoteSkipLabel)); ((Graphic)_skipLabel).color = label; } } } private static bool ContainsInt(IReadOnlyCollection set, int value) { if (set == null) { return false; } foreach (int item in set) { if (item == value) { return true; } } return false; } private static bool ContainsActor(IReadOnlyList list, int actor) { if (list == null) { return false; } for (int i = 0; i < list.Count; i++) { if (list[i] == actor) { return true; } } return false; } private void UpdateResult(MeetingClientState state) { //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) if (state.Result == null) { return; } MeetingOutcome result = state.Result; int num = 0; if (result.TargetActors != null && result.VoteCounts != null) { for (int i = 0; i < result.TargetActors.Length && i < result.VoteCounts.Length; i++) { if (result.TargetActors[i] == -1) { num = result.VoteCounts[i]; break; } } } if (!_countsApplied) { _countsApplied = true; foreach (VoteRow row in _rows) { row.SetVoteCount(0); } if (result.TargetActors != null && result.VoteCounts != null) { for (int j = 0; j < result.TargetActors.Length && j < result.VoteCounts.Length; j++) { int num2 = result.TargetActors[j]; if (num2 != -1 && _rowsByActor.TryGetValue(num2, out var value)) { value.SetVoteCount(result.VoteCounts[j]); } } } if ((Object)(object)_skipCountLabel != (Object)null) { ((TMP_Text)_skipCountLabel).text = Texts.Format(TextId.VoteCountFormat, num); ((Component)_skipCountLabel).gameObject.SetActive(true); } } if (!_resultShown) { _resultShown = true; string text2; if (result.ExecutedActor != -1) { VoteRow value2; string text = (_rowsByActor.TryGetValue(result.ExecutedActor, out value2) ? value2.PlayerName : $"#{result.ExecutedActor}"); text2 = Texts.Format(TextId.VoteExecutedFormat, text); ((Graphic)_resultBanner).color = ResultExecutedColor; } else { text2 = Texts.Get(TextId.VoteNoExecution); ((Graphic)_resultBanner).color = ResultNoneColor; } if (num > 0) { text2 += Texts.Format(TextId.VoteSkipSuffixFormat, num); } ((TMP_Text)_resultBanner).text = text2; ((Component)_resultBanner).gameObject.SetActive(true); WLog.Line("vote_panel_result", false, ("executed", result.ExecutedActor), ("skipVotes", num)); } } private void HandleInput(MeetingClientState state) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (_slideActive) { return; } bool flag = !SelectionLocked && _selectionSource != null && _selectionSource.CanConfirm(_localActor); bool flag2 = PageCount > 1; if (!flag && !flag2) { return; } Vector2 val = Vector2.op_Implicit(Input.mousePosition); bool flag3 = Input.GetMouseButtonDown(0) || SemiFunc.InputDown((InputKey)9); bool flag4 = IsPointerBlocked != null && IsPointerBlocked(val); if (flag4) { flag3 = false; } bool flag5 = false; if (flag) { foreach (VoteRow row in _rows) { if (!row.VoteButtonActive) { continue; } RectTransform voteButtonRect = row.VoteButtonRect; if (!((Object)(object)voteButtonRect == (Object)null) && ((Component)voteButtonRect).gameObject.activeInHierarchy) { bool flag6 = !flag4 && RectTransformUtility.RectangleContainsScreenPoint(voteButtonRect, val, (Camera)null); row.SetHover(flag6); if (flag6 && flag3) { HandleTargetClick(row.ActorNumber); return; } } } if (!flag4 && _selectionSource.AllowSkip && (Object)(object)_skipButtonBg != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(((Graphic)_skipButtonBg).rectTransform, val, (Camera)null)) { flag5 = true; if (flag3) { HandleTargetClick(-1); return; } } } if (_skipHover != flag5) { _skipHover = flag5; RefreshSkipVisual(); } if (flag2 && flag3 && !flag4) { if (RectTransformUtility.RectangleContainsScreenPoint(((Graphic)_pagePrevBg).rectTransform, val, (Camera)null)) { _page = (_page - 1 + PageCount) % PageCount; LayoutRows(); WLog.Line("vote_panel_page", false, ("page", _page)); } else if (RectTransformUtility.RectangleContainsScreenPoint(((Graphic)_pageNextBg).rectTransform, val, (Camera)null)) { _page = (_page + 1) % PageCount; LayoutRows(); WLog.Line("vote_panel_page", false, ("page", _page)); } } } private void HandleTargetClick(int targetActor) { if (_defaultProvider == null) { SubmitVote(targetActor); } else if (_armedTarget != targetActor) { _armedTarget = targetActor; WLog.Line("vote_armed", true, ("target", targetActor)); } else { _armedTarget = int.MinValue; SubmitVote(targetActor); } } private void SubmitVote(int targetActor) { try { _selectionSource.Confirm(_localActor, targetActor); } catch (Exception ex) { WLog.Line("vote_panel_confirm_error", false, ("err", ex.Message)); } } } public sealed class VoteRow { private const float PrefabScale = 2.5f; private static readonly Color BgAliveColor = new Color(0f, 0f, 0f, 0.55f); private static readonly Color BgDeadColor = new Color(0.05f, 0.05f, 0.05f, 0.7f); private static readonly Color BgExecutedColor = new Color(0.25f, 0.02f, 0.02f, 0.7f); private static readonly Color BgDisconnectedColor = new Color(0.1f, 0.1f, 0.15f, 0.7f); private static readonly Color DeadTextColor = new Color(0.7f, 0.7f, 0.7f, 0.9f); private static readonly Color VotedMarkColor = new Color(0.35f, 0.95f, 0.35f, 0.95f); private static readonly Color VoteButtonEnabledColor = new Color(0.55f, 0.12f, 0.12f, 0.9f); private static readonly Color VoteButtonDisabledColor = new Color(0.25f, 0.2f, 0.2f, 0.6f); private static readonly Color VoteButtonLabelDisabledColor = new Color(0.6f, 0.6f, 0.6f); private static readonly Color VoteButtonHoverColor = new Color(0.8f, 0.2f, 0.2f, 1f); private static readonly ButtonPalette VoteButtonPalette = new ButtonPalette(VoteButtonEnabledColor, VoteButtonHoverColor, VoteButtonDisabledColor, VoteButtonLabelDisabledColor); private static readonly Color VoteCountColor = new Color(1f, 0.9f, 0.6f, 1f); private static readonly Color WerewolfMarkerBgColor = new Color(0.75f, 0.1f, 0.1f, 0.95f); private static readonly Color BomberMarkerBgColor = new Color(0.8f, 0.45f, 0.08f, 0.95f); private static readonly Color TalkIdleColor = new Color(0.35f, 0.35f, 0.35f); private static readonly Color TalkActiveColor = new Color(0.35f, 0.95f, 0.35f); private const int DeadCueSuppressWaitFrames = 120; private static readonly FieldRef _voiceChatRef = GameRefs.PlayerAvatar_voiceChat; private static readonly FieldRef _voiceChatFetchedRef = GameRefs.PlayerAvatar_voiceChatFetched; private static readonly FieldRef _isTalkingRef = GameRefs.PlayerVoiceChat_isTalking; private static Func s_showDeadCues; private readonly PlayerAvatar _avatar; private GameObject _root; private Image _background; private TextMeshProUGUI _fallbackName; private Image _talkIndicator; private TextMeshProUGUI _votedMark; private Image _voteButtonBg; private TextMeshProUGUI _voteButtonLabel; private TextMeshProUGUI _voteCountLabel; private Image _teamMarkerBg; private TextMeshProUGUI _teamMarkerLabel; private Image _statusIcon; private Image _hostIcon; private Image _myVoteIcon; private MenuPlayerListed _listed; private bool _deadCuesHidden; private int _deadCueWaitFrames; private bool _prefabUsed; private bool _voteButtonVisible; private bool _voteButtonEnabled; private bool _selected; private bool _armed; public int ActorNumber { get; } public string PlayerName { get; } public bool IsBot { get; } public RectTransform Root { get { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!((Object)(object)_root != (Object)null)) { return null; } return (RectTransform)_root.transform; } } public RectTransform VoteButtonRect { get { if (!((Object)(object)_voteButtonBg != (Object)null)) { return null; } return ((Graphic)_voteButtonBg).rectTransform; } } public bool VoteButtonActive { get { if ((Object)(object)_root != (Object)null && _voteButtonVisible) { return _voteButtonEnabled; } return false; } } public bool PrefabUsed => _prefabUsed; private VoteRow(WPlayer player, PlayerAvatar avatar) { ActorNumber = player.ActorNumber; PlayerName = player.Name ?? $"#{player.ActorNumber}"; IsBot = player.IsBot; _avatar = avatar; } public static VoteRow Build(Transform parent, WPlayer player, Func resolveAvatar, Vector2 size) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) VoteRow voteRow = new VoteRow(player, ResolveAvatarSafe(player, resolveAvatar)); voteRow.BuildInternal(parent, size); return voteRow; } private static PlayerAvatar ResolveAvatarSafe(WPlayer player, Func resolveAvatar) { if (player.IsBot || resolveAvatar == null) { return null; } try { return resolveAvatar(player.ActorNumber); } catch { return null; } } private void BuildInternal(Transform parent, Vector2 size) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0110: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) RectTransform val = UiKit.CreateRect(parent, $"WW_VoteRow_{ActorNumber}", Vector2.zero, size); _root = ((Component)val).gameObject; _background = UiKit.CreateImage((Transform)(object)val, "Bg", Vector2.zero, size, BgAliveColor); _prefabUsed = !IsBot && (Object)(object)_avatar != (Object)null && TryBuildFromPrefab(val, size); if (!_prefabUsed) { BuildFallbackVisual(val, size); } float num = size.x * 0.5f; _votedMark = UiKit.CreateText((Transform)(object)val, "Voted", new Vector2(num - 120f, 0f), new Vector2(36f, size.y), "✓", 26f, VotedMarkColor, (TextAlignmentOptions)514); ((Component)_votedMark).gameObject.SetActive(false); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(84f, size.y - 16f); _voteButtonBg = UiKit.CreateImage((Transform)(object)val, "VoteButton", new Vector2(num - 55f, 0f), val2, VoteButtonEnabledColor); _voteButtonLabel = UiKit.CreateText((Transform)(object)((Graphic)_voteButtonBg).rectTransform, "Label", Vector2.zero, val2, Texts.Get(TextId.VoteVoteLabel), 22f, Color.white, (TextAlignmentOptions)514); Sprite sprite = AssetCatalog.GetSprite("icon_btn_vote"); if ((Object)(object)sprite != (Object)null) { Image obj = UiKit.CreateImage((Transform)(object)((Graphic)_voteButtonBg).rectTransform, "Icon", new Vector2((0f - val2.x) / 2f + 13f + 4f, 0f), new Vector2(26f, 26f), Color.white); obj.sprite = sprite; obj.preserveAspect = true; } _voteCountLabel = UiKit.CreateText((Transform)(object)val, "Count", new Vector2(num - 120f, 0f), new Vector2(56f, size.y), "", 22f, VoteCountColor, (TextAlignmentOptions)514); ((Component)_voteCountLabel).gameObject.SetActive(false); float num2 = (0f - size.x) * 0.5f; _teamMarkerBg = UiKit.CreateImage((Transform)(object)val, "WwMarker", new Vector2(num2 + 40f, size.y * 0.5f - 14f), new Vector2(24f, 24f), WerewolfMarkerBgColor); _teamMarkerLabel = UiKit.CreateText((Transform)(object)((Graphic)_teamMarkerBg).rectTransform, "Label", Vector2.zero, new Vector2(24f, 24f), Texts.Get(TextId.VoteWerewolfMarkerLabel), 18f, Color.white, (TextAlignmentOptions)514); ((Component)_teamMarkerBg).gameObject.SetActive(false); _statusIcon = UiKit.CreateImage((Transform)(object)val, "StatusIcon", new Vector2(num2 + 82f, 0f), new Vector2(70f, 70f), Color.white); ((Component)_statusIcon).gameObject.SetActive(false); _hostIcon = UiKit.CreateImage((Transform)(object)val, "HostIcon", new Vector2(num - 156f, 0f), new Vector2(32f, 32f), Color.white); Sprite sprite2 = AssetCatalog.GetSprite("icon_host_megaphone"); if ((Object)(object)sprite2 != (Object)null) { _hostIcon.sprite = sprite2; } ((Component)_hostIcon).gameObject.SetActive(false); Sprite sprite3 = AssetCatalog.GetSprite("icon_my_vote"); if ((Object)(object)sprite3 != (Object)null) { _myVoteIcon = UiKit.CreateImage((Transform)(object)val, "MyVoteIcon", new Vector2(num - 212f, 0f), new Vector2(70f, 70f), Color.white); _myVoteIcon.sprite = sprite3; ((Component)_myVoteIcon).gameObject.SetActive(false); } SetVoteButtonVisible(visible: false); SetStatus(RowStatus.Alive); WLog.Line("vote_row_built", false, ("actor", ActorNumber), ("bot", IsBot), ("mode", _prefabUsed ? "prefab" : "fallback")); } private bool TryBuildFromPrefab(RectTransform rowRect, Vector2 size) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) RectTransform val = UiKit.CreateRect((Transform)(object)rowRect, "AvatarHolder", new Vector2((0f - size.x) * 0.5f + 85f, -40f), new Vector2(220f, size.y)); ((Transform)val).localScale = new Vector3(2.5f, 2.5f, 1f); MenuPlayerListed val2 = ListedAvatar.TryCreate((Transform)(object)val, _avatar, PlayerName); if ((Object)(object)val2 == (Object)null) { WLog.Line("vote_row_prefab_fallback", false, ("actor", ActorNumber)); Object.Destroy((Object)(object)((Component)val).gameObject); return false; } _listed = val2; return true; } private void BuildFallbackVisual(RectTransform rowRect, Vector2 size) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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) float num = (0f - size.x) * 0.5f; if (!IsBot && (Object)(object)_avatar != (Object)null) { _talkIndicator = UiKit.CreateImage((Transform)(object)rowRect, "Talk", new Vector2(num + 22f, 0f), new Vector2(14f, 14f), TalkIdleColor); } _fallbackName = UiKit.CreateText((Transform)(object)rowRect, "Name", new Vector2(num + 130f, 0f), new Vector2(190f, size.y), PlayerName, 24f, Color.white, (TextAlignmentOptions)4097); } public void SetStatus(RowStatus status) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } Color color = (Color)(status switch { RowStatus.Dead => BgDeadColor, RowStatus.Executed => BgExecutedColor, RowStatus.Disconnected => BgDisconnectedColor, _ => BgAliveColor, }); ((Graphic)_background).color = color; SetStatusIcon(status); if (status != RowStatus.Alive) { SetVoteButtonVisible(visible: false); if ((Object)(object)_fallbackName != (Object)null) { ((Graphic)_fallbackName).color = DeadTextColor; } } else if ((Object)(object)_fallbackName != (Object)null) { ((Graphic)_fallbackName).color = Color.white; } } private void SetStatusIcon(RowStatus status) { if ((Object)(object)_statusIcon == (Object)null) { return; } string text = status switch { RowStatus.Dead => "icon_status_dead", RowStatus.Executed => "icon_status_executed", RowStatus.Disconnected => "icon_status_disconnected", _ => null, }; if (text == null) { ((Component)_statusIcon).gameObject.SetActive(false); return; } Sprite sprite = AssetCatalog.GetSprite(text); if ((Object)(object)sprite == (Object)null) { ((Component)_statusIcon).gameObject.SetActive(false); return; } _statusIcon.sprite = sprite; ((Component)_statusIcon).gameObject.SetActive(true); } public void SetHostMarker(bool isHost) { if (!((Object)(object)_hostIcon == (Object)null) && ((Component)_hostIcon).gameObject.activeSelf != isHost) { ((Component)_hostIcon).gameObject.SetActive(isHost); } } public void SetMyVoteMarker(bool visible) { if (!((Object)(object)_myVoteIcon == (Object)null) && ((Component)_myVoteIcon).gameObject.activeSelf != visible) { ((Component)_myVoteIcon).gameObject.SetActive(visible); } } public void SetVoted(bool voted) { if (!((Object)(object)_votedMark == (Object)null)) { bool flag = (Object)(object)_voteCountLabel != (Object)null && ((Component)_voteCountLabel).gameObject.activeSelf; ((Component)_votedMark).gameObject.SetActive(voted && !flag); } } public void SetVoteButtonVisible(bool visible) { _voteButtonVisible = visible; if ((Object)(object)_voteButtonBg != (Object)null) { ((Component)_voteButtonBg).gameObject.SetActive(visible); } } public void SetVoteButtonEnabled(bool enabled) { _voteButtonEnabled = enabled; RefreshVoteButtonVisual(hover: false); } public void SetSelected(bool selected) { if (_selected != selected) { _selected = selected; RefreshVoteButtonVisual(hover: false); } } public void SetArmed(bool armed) { if (_armed != armed) { _armed = armed; if ((Object)(object)_voteButtonLabel != (Object)null) { ((TMP_Text)_voteButtonLabel).text = (armed ? Texts.Get(TextId.VoteConfirmLabel) : Texts.Get(TextId.VoteVoteLabel)); } RefreshVoteButtonVisual(hover: false); } } private void RefreshVoteButtonVisual(bool hover) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_voteButtonBg == (Object)null)) { ButtonVisual.Resolve(VoteButtonPalette, _armed && _voteButtonEnabled, hover, _selected, _voteButtonEnabled, out var bg, out var label); ((Graphic)_voteButtonBg).color = bg; if ((Object)(object)_voteButtonLabel != (Object)null) { ((Graphic)_voteButtonLabel).color = label; } } } public void SetTeamMarker(Role? role) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_teamMarkerBg == (Object)null) { return; } bool hasValue = role.HasValue; if (hasValue) { bool flag = role.Value == Role.Bomber; ((Graphic)_teamMarkerBg).color = (flag ? BomberMarkerBgColor : WerewolfMarkerBgColor); if ((Object)(object)_teamMarkerLabel != (Object)null) { ((TMP_Text)_teamMarkerLabel).text = Texts.Get(flag ? TextId.VoteBomberMarkerLabel : TextId.VoteWerewolfMarkerLabel); } } if (((Component)_teamMarkerBg).gameObject.activeSelf != hasValue) { ((Component)_teamMarkerBg).gameObject.SetActive(hasValue); } } public void SetHover(bool hover) { if (!((Object)(object)_voteButtonBg == (Object)null) && _voteButtonVisible) { RefreshVoteButtonVisual(hover); } } public void SetVoteCount(int count) { if ((Object)(object)_voteCountLabel == (Object)null) { return; } if (count < 0) { ((Component)_voteCountLabel).gameObject.SetActive(false); return; } ((TMP_Text)_voteCountLabel).text = Texts.Format(TextId.VoteCountFormat, count); ((Component)_voteCountLabel).gameObject.SetActive(true); if ((Object)(object)_votedMark != (Object)null) { ((Component)_votedMark).gameObject.SetActive(false); } } public static void SetDeadCueProvider(Func provider) { s_showDeadCues = provider; } public void Tick() { try { bool showCues = s_showDeadCues == null || s_showDeadCues(ActorNumber); TickDeadCues(showCues); TickFallbackTalk(showCues); } catch (Exception ex) { WLog.Line("vote_row_tick_error", false, ("actor", ActorNumber), ("err", ex.Message)); } } private void TickDeadCues(bool showCues) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_listed == (Object)null || showCues == !_deadCuesHidden) { return; } if (showCues) { ListedAvatar.ResumeSpeech(_listed); } else { if (!((Component)_listed).gameObject.activeInHierarchy) { return; } if (!ListedAvatar.IsReady(_listed) && _deadCueWaitFrames < 120) { _deadCueWaitFrames++; return; } ListedAvatar.SuppressSpeech(_listed, DeadTextColor); } _deadCuesHidden = !showCues; } private void TickFallbackTalk(bool showCues) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_talkIndicator == (Object)null) && !((Object)(object)_avatar == (Object)null)) { bool flag = false; if (showCues && HasVoiceRefs() && _voiceChatFetchedRef.Invoke(_avatar)) { PlayerVoiceChat val = _voiceChatRef.Invoke(_avatar); flag = (Object)(object)val != (Object)null && _isTalkingRef.Invoke(val); } ((Graphic)_talkIndicator).color = (flag ? TalkActiveColor : TalkIdleColor); } } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } } private static bool HasVoiceRefs() { if (_voiceChatRef != null && _voiceChatFetchedRef != null) { return _isTalkingRef != null; } return false; } } internal static class VoteRowGrid { public static readonly Vector2 RowSize = new Vector2(572f, 76f); public const int RowsPerColumn = 7; public const int Columns = 2; public const int RowsPerPage = 14; private const float ColumnX = 301f; private const float RowStepY = 76f; public static Vector2 Position(int index, float topY) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return Position(index % 2, index / 2, topY); } public static Vector2 Position(int column, int rowInColumn, float topY) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) return new Vector2((column == 0) ? (-301f) : 301f, topY - (float)rowInColumn * 76f); } } public sealed class WerewolfUIManager { public const string MeetingLayer = "Meeting"; public const string RoleGaugeLayer = "RoleGauge"; public const string LobbyLayer = "Lobby"; public const string MeetingMapLayer = "MeetingMap"; public const string ModIntegrityLayer = "ModIntegrity"; public const string ModIntegrityModalLayer = "ModIntegrityModal"; public const string ManualLayer = "Manual"; internal const int PanelSortingOrder = 9999; internal const int ManualSortingOrder = 10000; internal const int TutorialSortingOrder = 10000; internal const int CursorSortingOrder = 10001; private GameObject _canvasRoot; private readonly Dictionary _layers = new Dictionary(); public bool Exists => (Object)(object)_canvasRoot != (Object)null; public bool EnsureCreated(GameObject host) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_canvasRoot != (Object)null) { return true; } if ((Object)(object)host == (Object)null) { WLog.Line("ui_overlay_error", false, ("reason", "no_host")); return false; } try { _canvasRoot = new GameObject("WW_OverlayCanvas"); _canvasRoot.transform.SetParent(host.transform, false); Canvas obj = _canvasRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 9999; CanvasScaler obj2 = _canvasRoot.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; WLog.Line("ui_overlay_created", false, ("sortingOrder", 9999)); return true; } catch (Exception ex) { WLog.Line("ui_overlay_error", false, ("reason", "create_failed"), ("err", ex.Message)); Destroy(); return false; } } public Transform GetLayerRoot(string layerName) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_canvasRoot == (Object)null || string.IsNullOrEmpty(layerName)) { return null; } if (_layers.TryGetValue(layerName, out var value) && (Object)(object)value != (Object)null) { return value.transform; } GameObject val = new GameObject("WW_Layer_" + layerName, new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(_canvasRoot.transform, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; val.SetActive(false); _layers[layerName] = val; WLog.Line("ui_layer_registered", false, ("layer", layerName)); return val.transform; } public void SetLayerVisible(string layerName, bool visible) { if (_layers.TryGetValue(layerName, out var value) && !((Object)(object)value == (Object)null) && value.activeSelf != visible) { value.SetActive(visible); WLog.Line("ui_layer_visible", false, ("layer", layerName), ("visible", visible)); } } public bool IsLayerVisible(string layerName) { if (_layers.TryGetValue(layerName, out var value) && (Object)(object)value != (Object)null) { return value.activeSelf; } return false; } public void Tick(MeetingClientState state, long nowUnixMs) { if ((Object)(object)_canvasRoot == (Object)null) { return; } try { bool visible = state != null && state.MeetingActive && state.VotingUiReady(nowUnixMs); SetLayerVisible("Meeting", visible); SetLayerVisible("MeetingMap", visible); } catch (Exception ex) { WLog.Line("ui_tick_error", false, ("err", ex.Message)); } } public void Destroy() { if ((Object)(object)_canvasRoot != (Object)null) { Object.Destroy((Object)(object)_canvasRoot); _canvasRoot = null; } _layers.Clear(); } } internal static class UiKit { private static TMP_FontAsset _cachedTmpFont; private static bool _tmpFontResolved; private static Sprite _whiteSprite; private const float CursorKeepAliveSeconds = 0.2f; private static Sprite _ringSprite; private static Sprite _roundedSprite; public static void KeepCursorFree() { SemiFunc.CursorUnlock(0.2f); } private static Sprite WhiteSprite() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_whiteSprite == (Object)null) { Texture2D whiteTexture = Texture2D.whiteTexture; _whiteSprite = Sprite.Create(whiteTexture, new Rect(0f, 0f, (float)((Texture)whiteTexture).width, (float)((Texture)whiteTexture).height), new Vector2(0.5f, 0.5f)); ((Object)_whiteSprite).name = "WW_WhiteSprite"; } return _whiteSprite; } internal static RectTransform CreateRect(Transform parent, string name, Vector2 anchoredPos, Vector2 size) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_004a: 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_0051: 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_0059: Expected O, but got Unknown RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) }).transform; ((Transform)val).SetParent(parent, false); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 0.5f); val.anchorMax = val2; val.anchorMin = val2; val.anchoredPosition = anchoredPos; val.sizeDelta = size; return val; } internal static Image CreateImage(Transform parent, string name, Vector2 anchoredPos, Vector2 size, Color color) { //IL_0002: 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_0014: Unknown result type (might be due to invalid IL or missing references) Image obj = ((Component)CreateRect(parent, name, anchoredPos, size)).gameObject.AddComponent(); ((Graphic)obj).color = color; ((Graphic)obj).raycastTarget = false; return obj; } internal static Image CreateFilledImage(Transform parent, string name, Vector2 anchoredPos, Vector2 size, Color color) { //IL_0002: 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_0004: Unknown result type (might be due to invalid IL or missing references) Image obj = CreateImage(parent, name, anchoredPos, size, color); obj.sprite = WhiteSprite(); obj.type = (Type)3; obj.fillMethod = (FillMethod)0; obj.fillOrigin = 0; obj.fillAmount = 0f; return obj; } internal static Sprite RingSprite() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_ringSprite != (Object)null) { return _ringSprite; } Texture2D val = new Texture2D(128, 128, (TextureFormat)5, false); ((Texture)val).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[16384]; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(63.5f, 63.5f); float num = 64f; float num2 = 46.08f; Color val3 = default(Color); for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { float num3 = Vector2.Distance(new Vector2((float)j, (float)i), val2); if (num3 > num || num3 < num2) { ((Color)(ref val3))..ctor(0f, 0f, 0f, 0f); } else { ((Color)(ref val3))..ctor(1f, 1f, 1f, 1f); } array[i * 128 + j] = val3; } } val.SetPixels(array); val.Apply(); _ringSprite = Sprite.Create(val, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f)); ((Object)_ringSprite).name = "WW_RingSprite"; return _ringSprite; } internal static Image CreateRadialImage(Transform parent, string name, Vector2 anchoredPos, Vector2 size, Color color) { //IL_0002: 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_0004: Unknown result type (might be due to invalid IL or missing references) Image obj = CreateImage(parent, name, anchoredPos, size, color); obj.sprite = RingSprite(); obj.type = (Type)3; obj.fillMethod = (FillMethod)4; obj.fillOrigin = 2; obj.fillClockwise = true; obj.fillAmount = 0f; return obj; } internal static Sprite RoundedRectSprite() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_roundedSprite != (Object)null) { return _roundedSprite; } Texture2D val = new Texture2D(64, 64, (TextureFormat)5, false); ((Texture)val).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[4096]; for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num = Mathf.Clamp((float)j + 0.5f, 16f, 48f); float num2 = Mathf.Clamp((float)i + 0.5f, 16f, 48f); float num3 = Vector2.Distance(new Vector2((float)j + 0.5f, (float)i + 0.5f), new Vector2(num, num2)); float num4 = Mathf.Clamp01(16f - num3 + 0.5f); array[i * 64 + j] = new Color(1f, 1f, 1f, num4); } } val.SetPixels(array); val.Apply(); _roundedSprite = Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(16f, 16f, 16f, 16f)); ((Object)_roundedSprite).name = "WW_RoundedRectSprite"; return _roundedSprite; } internal static Image CreateRoundedImage(Transform parent, string name, Vector2 anchoredPos, Vector2 size, Color color) { //IL_0002: 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_0004: Unknown result type (might be due to invalid IL or missing references) Image obj = CreateImage(parent, name, anchoredPos, size, color); obj.sprite = RoundedRectSprite(); obj.type = (Type)1; return obj; } internal static TextMeshProUGUI CreateText(Transform parent, string name, Vector2 anchoredPos, Vector2 size, string text, float fontSize, Color color, TextAlignmentOptions alignment) { //IL_0002: 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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI obj = ((Component)CreateRect(parent, name, anchoredPos, size)).gameObject.AddComponent(); ((TMP_Text)obj).font = ResolveTmpFont(); ((TMP_Text)obj).text = text; ((TMP_Text)obj).fontSize = fontSize; ((Graphic)obj).color = color; ((TMP_Text)obj).alignment = alignment; ((Graphic)obj).raycastTarget = false; ((TMP_Text)obj).enableWordWrapping = false; ((TMP_Text)obj).overflowMode = (TextOverflowModes)0; return obj; } private static TMP_FontAsset ResolveTmpFont() { if (_tmpFontResolved) { return _cachedTmpFont; } try { if ((Object)(object)ChatUI.instance != (Object)null && (Object)(object)ChatUI.instance.chatText != (Object)null) { _cachedTmpFont = ((TMP_Text)ChatUI.instance.chatText).font; _tmpFontResolved = true; WLog.Line("ui_font", false, ("mode", "chatui"), ("font", ((Object)(object)_cachedTmpFont != (Object)null) ? ((Object)_cachedTmpFont).name : "null")); return _cachedTmpFont; } } catch (Exception ex) { WLog.Line("ui_font_error", false, ("src", "chatui"), ("err", ex.Message)); } _tmpFontResolved = true; _cachedTmpFont = TMP_Settings.defaultFontAsset; WLog.Line("ui_font", false, ("mode", "tmp_fallback"), ("font", ((Object)(object)_cachedTmpFont != (Object)null) ? ((Object)_cachedTmpFont).name : "null")); return _cachedTmpFont; } } public sealed class WolfStatusPanel : IClientPanel { private sealed class Slot { public RectTransform Root; public Image Icon; public Sprite ColorSprite; public Sprite GraySprite; public TextMeshProUGUI Fallback; } private const float LeftMargin = 48f; private const float BottomMargin = 150f; private const float MainIconSize = 112f; private const float PerkIconSize = 84f; private const float SlotPadding = 8f; private const float SlotGap = 10f; private const float BeaconGap = 22f; private const float LabelHeight = 34f; private const float LabelFontSize = 26f; private const float LabelGap = 4f; private const float CooldownFontSize = 44f; private const float ChargesFontSize = 34f; private const float JumpChargesFontSize = 34f; private static readonly Color LockedIconTint = new Color(0.45f, 0.45f, 0.45f, 0.8f); private static readonly Color ReadyIconTint = new Color(0.95f, 0.95f, 0.95f, 0.95f); private static readonly Color ActiveIconTint = Color.white; private static readonly Color LockedLabelColor = new Color(0.55f, 0.55f, 0.55f, 0.9f); private static readonly Color ReadyLabelColor = new Color(1f, 0.95f, 0.85f, 0.95f); private static readonly Color ActiveLabelColor = new Color(1f, 0.55f, 0.45f, 1f); private static readonly Color BeaconGrayOverlayTint = new Color(0.25f, 0.25f, 0.25f, 0.82f); private static readonly Color ChargesTextColor = new Color(0.35f, 0.95f, 0.35f, 1f); private const float GlowSizePx = 260f; private static readonly Color GlowColor = new Color(1f, 0.25f, 0.12f, 1f); private const float GlowBaseAlpha = 0.62f; private const float GlowPulseAmplitude = 0.33f; private const float GlowBreathScale = 0.08f; private const float GlowStretchAmplitude = 0.07f; private const float GlowSwayRangePx = 6f; private const float GlowFlickerSpeed = 2.5f; private const float GlowSwaySpeed = 0.7f; private const float RaySizePx = 330f; private const int RayCount = 7; private const float RaySharpness = 16f; private const float RayBaseAlpha = 0.35f; private const float RayFlickerAmplitude = 0.55f; private const float RayFlickerSpeed = 3.5f; private const float RayFastSpinDegPerSec = 16f; private const float RaySlowSpinDegPerSec = -9f; private static readonly Color RaySlowTint = new Color(1f, 0.75f, 0.55f, 1f); private static Sprite _glowSprite; private static Sprite _raySprite; private GameObject _root; private Slot _mainSlot; private Slot[] _perkSlots; private TextMeshProUGUI _jumpCharges; private TextMeshProUGUI _keyLabel; private RectTransform _glowRoot; private Vector2 _glowCenter; private Image _glowBall; private Image _rayFast; private Image _raySlow; private Slot _beaconSlot; private Image _beaconGrayOverlay; private TextMeshProUGUI _beaconCooldown; private TextMeshProUGUI _beaconCharges; private TextMeshProUGUI _beaconKeyLabel; private WolfStatusState _lastState; private bool _hasLastState; private string _lastWolfKeyName; private string _lastBeaconKeyName; public string LayerName => "Hud"; public bool Exists => (Object)(object)_root != (Object)null; public void Build(Transform layerRoot) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: 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_015c: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null) && !((Object)(object)layerRoot == (Object)null)) { if (!((Component)layerRoot).gameObject.activeSelf) { ((Component)layerRoot).gameObject.SetActive(true); } float num = 120f; float num2 = 92f; float num3 = num2 * 2f + 10f; float num4 = num2 * 2f + 10f; float num5 = num + 10f + num3 + 22f + num; float num6 = 38f + num4; GameObject val = new GameObject("WW_WolfStatusPanel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(layerRoot, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 0f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(0f, 0f); val2.anchoredPosition = new Vector2(48f, 150f); val2.sizeDelta = new Vector2(num5, num6); _root = val; float num7 = 38f; float num8 = num7 + num4 / 2f; float num9 = num / 2f; _glowCenter = new Vector2(num9, num8); GameObject val4 = new GameObject("MainGlow", new Type[1] { typeof(RectTransform) }); _glowRoot = (RectTransform)val4.transform; ((Transform)_glowRoot).SetParent((Transform)(object)val2, false); SetAnchorsBottomLeft(_glowRoot, _glowCenter, Vector2.zero); _glowBall = UiKit.CreateImage((Transform)(object)_glowRoot, "GlowBall", Vector2.zero, new Vector2(260f, 260f), GlowColor); _glowBall.sprite = GlowSprite(); _raySlow = UiKit.CreateImage((Transform)(object)_glowRoot, "RaySlow", Vector2.zero, new Vector2(264f, 264f), RaySlowTint); _raySlow.sprite = RaySprite(); _rayFast = UiKit.CreateImage((Transform)(object)_glowRoot, "RayFast", Vector2.zero, new Vector2(330f, 330f), Color.white); _rayFast.sprite = RaySprite(); ((Component)_glowRoot).gameObject.SetActive(false); _mainSlot = BuildSlot(val2, "Main", new Vector2(num9, num8), 112f, "role_werewolf", Texts.Get(TextId.RoleNameWerewolf), 30f); _keyLabel = BuildKeyLabel(val2, "KeyLabel", num9); string[] array = new string[4] { "perk_stamina", "perk_jump", "perk_enemy_ignore", "perk_heal" }; string[] array2 = new string[4] { Texts.Get(TextId.GaugePerkStaminaLabel), Texts.Get(TextId.GaugePerkJumpLabel), Texts.Get(TextId.GaugePerkEnemyIgnoreLabel), Texts.Get(TextId.GaugePerkHealLabel) }; _perkSlots = new Slot[array.Length]; float num10 = num + 10f; for (int i = 0; i < array.Length; i++) { int num11 = i % 2; int num12 = i / 2; float num13 = num10 + num2 / 2f + (float)num11 * (num2 + 10f); float num14 = num7 + num4 - num2 / 2f - (float)num12 * (num2 + 10f); _perkSlots[i] = BuildSlot(val2, "Perk" + i, new Vector2(num13, num14), 84f, array[i], array2[i], 14f); } _jumpCharges = UiKit.CreateText((Transform)(object)_perkSlots[1].Root, "JumpCharges", new Vector2(0f, -21f), new Vector2(84f, 42f), string.Empty, 34f, ChargesTextColor, (TextAlignmentOptions)514); ((TMP_Text)_jumpCharges).outlineWidth = 0.25f; ((TMP_Text)_jumpCharges).outlineColor = Color32.op_Implicit(Color.black); float num15 = num10 + num3 + 22f + num / 2f; BuildBeaconSlot(val2, new Vector2(num15, num8)); _beaconKeyLabel = BuildKeyLabel(val2, "BeaconKeyLabel", num15); _root.SetActive(false); WLog.Line("wolf_status_panel_built", false); } } public void Tick(WolfStatusState state, string wolfKeyName, string beaconKeyName) { //IL_017b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } try { if (_root.activeSelf != state.Visible) { _root.SetActive(state.Visible); } if (!state.Visible) { _hasLastState = false; return; } TickGlow(state.Toggle == WolfPerkVisual.Active); if (!_hasLastState || !SameVisuals(_lastState, state) || !(_lastWolfKeyName == wolfKeyName) || !(_lastBeaconKeyName == beaconKeyName)) { _lastState = state; _hasLastState = true; _lastWolfKeyName = wolfKeyName; _lastBeaconKeyName = beaconKeyName; ApplySlot(_mainSlot, state.Toggle); ApplySlot(_perkSlots[0], state.Stamina); ApplySlot(_perkSlots[1], state.Jump); ApplySlot(_perkSlots[2], state.EnemyIgnore); ApplySlot(_perkSlots[3], state.Heal); if ((Object)(object)_jumpCharges != (Object)null) { ((TMP_Text)_jumpCharges).text = ((state.JumpCharges >= 0) ? state.JumpCharges.ToString() : string.Empty); } ((TMP_Text)_keyLabel).text = Texts.Format(TextId.HudWolfToggleFormat, wolfKeyName ?? "?"); ((Graphic)_keyLabel).color = LabelColor(state.Toggle); ApplyBeacon(state, beaconKeyName); } } catch (Exception ex) { WLog.Line("wolf_status_tick_error", false, ("err", ex.Message)); } } public void Hide() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } if ((Object)(object)_glowRoot != (Object)null && ((Component)_glowRoot).gameObject.activeSelf) { ((Component)_glowRoot).gameObject.SetActive(false); } _hasLastState = false; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } _mainSlot = null; _perkSlots = null; _jumpCharges = null; _keyLabel = null; _glowRoot = null; _glowBall = null; _rayFast = null; _raySlow = null; _beaconSlot = null; _beaconGrayOverlay = null; _beaconCooldown = null; _beaconCharges = null; _beaconKeyLabel = null; _hasLastState = false; _lastWolfKeyName = null; _lastBeaconKeyName = null; } private void TickGlow(bool active) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_glowRoot == (Object)null)) { if (((Component)_glowRoot).gameObject.activeSelf != active) { ((Component)_glowRoot).gameObject.SetActive(active); WLog.Line("wolf_glow", false, ("visible", active)); } if (active) { float unscaledTime = Time.unscaledTime; float num = Mathf.PerlinNoise(unscaledTime * 2.5f, 0f); float num2 = Mathf.PerlinNoise(unscaledTime * 0.7f, 10f) * 2f - 1f; float num3 = Mathf.PerlinNoise(unscaledTime * 0.7f, 20f) * 2f - 1f; _glowRoot.anchoredPosition = _glowCenter + new Vector2(num2, num3) * 6f; Color glowColor = GlowColor; glowColor.a = 0.62f + 0.33f * num; ((Graphic)_glowBall).color = glowColor; ((Transform)((Graphic)_glowBall).rectTransform).localScale = new Vector3(1f + 0.08f * num + 0.07f * num2, 1f + 0.08f * num + 0.07f * num3, 1f); ((Transform)((Graphic)_rayFast).rectTransform).localRotation = Quaternion.Euler(0f, 0f, unscaledTime * 16f); ((Transform)((Graphic)_raySlow).rectTransform).localRotation = Quaternion.Euler(0f, 0f, unscaledTime * -9f); SetAlpha(_rayFast, 0.35f + 0.55f * Mathf.PerlinNoise(unscaledTime * 3.5f, 40f)); SetAlpha(_raySlow, 0.35f + 0.55f * Mathf.PerlinNoise(unscaledTime * 3.5f, 60f)); } } } private static void SetAlpha(Image image, float alpha) { //IL_0001: 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_0010: Unknown result type (might be due to invalid IL or missing references) Color color = ((Graphic)image).color; color.a = alpha; ((Graphic)image).color = color; } private static Sprite GlowSprite() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_glowSprite != (Object)null) { return _glowSprite; } Texture2D val = new Texture2D(128, 128, (TextureFormat)5, false); ((Texture)val).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[16384]; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(63.5f, 63.5f); float num = 64f; for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { float num2 = Vector2.Distance(new Vector2((float)j, (float)i), val2) / num; float num3 = Mathf.Pow(Mathf.Clamp01(1f - num2), 2.2f); array[i * 128 + j] = new Color(1f, 1f, 1f, num3); } } val.SetPixels(array); val.Apply(); _glowSprite = Sprite.Create(val, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f)); ((Object)_glowSprite).name = "WW_WolfGlowSprite"; return _glowSprite; } private static Sprite RaySprite() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_raySprite != (Object)null) { return _raySprite; } Texture2D val = new Texture2D(256, 256, (TextureFormat)5, false); ((Texture)val).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[65536]; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(127.5f, 127.5f); float num = 128f; Color val3 = default(Color); ((Color)(ref val3))..ctor(1f, 0.3f, 0.08f); for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { float num2 = (float)j - val2.x; float num3 = (float)i - val2.y; float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3) / num; float num5 = Mathf.Atan2(num3, num2); float num6 = Mathf.Pow(0.5f + 0.5f * Mathf.Cos(7f * num5), 16f); float num7 = 0.78f + 0.22f * Mathf.Sin(3f * num5 + 1.3f); float num8 = Mathf.Pow(Mathf.Clamp01(1f - num4 / num7), 1.4f); Color val4 = Color.Lerp(Color.white, val3, Mathf.SmoothStep(0.1f, 0.75f, num4)); val4.a = num8 * num6; array[i * 256 + j] = val4; } } val.SetPixels(array); val.Apply(); _raySprite = Sprite.Create(val, new Rect(0f, 0f, 256f, 256f), new Vector2(0.5f, 0.5f)); ((Object)_raySprite).name = "WW_WolfRaySprite"; return _raySprite; } private static bool SameVisuals(WolfStatusState a, WolfStatusState b) { if (a.Toggle == b.Toggle && a.Stamina == b.Stamina && a.Jump == b.Jump && a.JumpCharges == b.JumpCharges && a.EnemyIgnore == b.EnemyIgnore && a.Heal == b.Heal && a.BeaconCharges == b.BeaconCharges && a.BeaconCooldownSec == b.BeaconCooldownSec) { return (int)(a.BeaconGrayFraction * 100f) == (int)(b.BeaconGrayFraction * 100f); } return false; } private static Color LabelColor(WolfPerkVisual visual) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) return (Color)(visual switch { WolfPerkVisual.Active => ActiveLabelColor, WolfPerkVisual.Locked => LockedLabelColor, _ => ReadyLabelColor, }); } private static void ApplySlot(Slot slot, WolfPerkVisual visual) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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) if (slot == null) { return; } Color color = (Color)(visual switch { WolfPerkVisual.Active => ActiveIconTint, WolfPerkVisual.Ready => ReadyIconTint, _ => LockedIconTint, }); if ((Object)(object)slot.Icon != (Object)null && ((Behaviour)slot.Icon).enabled) { ((Graphic)slot.Icon).color = color; Sprite val = ((visual == WolfPerkVisual.Locked) ? (slot.GraySprite ?? slot.ColorSprite) : slot.ColorSprite); if ((Object)(object)val != (Object)null && (Object)(object)slot.Icon.sprite != (Object)(object)val) { slot.Icon.sprite = val; } } if ((Object)(object)slot.Fallback != (Object)null) { ((Graphic)slot.Fallback).color = color; } } private void ApplyBeacon(WolfStatusState state, string beaconKeyName) { //IL_0104: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) bool flag = state.BeaconCooldownSec <= 0 && state.BeaconCharges > 0; if (_beaconSlot != null) { if ((Object)(object)_beaconSlot.Icon != (Object)null && ((Behaviour)_beaconSlot.Icon).enabled) { bool flag2 = state.BeaconGrayFraction >= 1f; ((Graphic)_beaconSlot.Icon).color = (flag2 ? LockedIconTint : ReadyIconTint); Sprite val = (flag2 ? (_beaconSlot.GraySprite ?? _beaconSlot.ColorSprite) : _beaconSlot.ColorSprite); if ((Object)(object)val != (Object)null && (Object)(object)_beaconSlot.Icon.sprite != (Object)(object)val) { _beaconSlot.Icon.sprite = val; } } if ((Object)(object)_beaconSlot.Fallback != (Object)null) { ((Graphic)_beaconSlot.Fallback).color = (flag ? ReadyIconTint : LockedIconTint); } } if ((Object)(object)_beaconGrayOverlay != (Object)null) { float num = ((state.BeaconGrayFraction < 1f) ? state.BeaconGrayFraction : 1f); _beaconGrayOverlay.fillAmount = ((num > 0f) ? num : 0f); } if ((Object)(object)_beaconCooldown != (Object)null) { ((TMP_Text)_beaconCooldown).text = ((state.BeaconCooldownSec > 0) ? state.BeaconCooldownSec.ToString() : string.Empty); } if ((Object)(object)_beaconCharges != (Object)null) { ((TMP_Text)_beaconCharges).text = state.BeaconCharges.ToString(); } if ((Object)(object)_beaconKeyLabel != (Object)null) { ((TMP_Text)_beaconKeyLabel).text = Texts.Format(TextId.HudBeaconKeyFormat, beaconKeyName ?? "?"); ((Graphic)_beaconKeyLabel).color = (flag ? ReadyLabelColor : LockedLabelColor); } } private static Slot BuildSlot(RectTransform parent, string name, Vector2 centerPos, float iconSize, string spriteKey, string fallbackLabel, float fallbackFontSize) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) Slot slot = new Slot(); float num = iconSize + 8f; slot.Root = UiKit.CreateRect((Transform)(object)parent, name + "Slot", Vector2.zero, new Vector2(num, num)); SetAnchorsBottomLeft(slot.Root, centerPos, new Vector2(num, num)); slot.Icon = UiKit.CreateImage((Transform)(object)slot.Root, name + "Icon", Vector2.zero, new Vector2(iconSize, iconSize), LockedIconTint); Sprite sprite = AssetCatalog.GetSprite(spriteKey); if ((Object)(object)sprite != (Object)null) { slot.ColorSprite = sprite; slot.GraySprite = AssetCatalog.GetGraySprite(spriteKey); slot.Icon.sprite = slot.GraySprite ?? sprite; slot.Icon.preserveAspect = true; } else { ((Behaviour)slot.Icon).enabled = false; slot.Fallback = UiKit.CreateText((Transform)(object)slot.Root, name + "Fallback", Vector2.zero, new Vector2(num - 4f, num - 4f), fallbackLabel, fallbackFontSize, LockedIconTint, (TextAlignmentOptions)514); ((TMP_Text)slot.Fallback).enableWordWrapping = true; } return slot; } private void BuildBeaconSlot(RectTransform parent, Vector2 centerPos) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) _beaconSlot = BuildSlot(parent, "Beacon", centerPos, 112f, "perk_beacon", Texts.Get(TextId.HudBeaconLabel), 22f); Sprite sprite = AssetCatalog.GetSprite("perk_beacon"); if ((Object)(object)sprite != (Object)null && (Object)(object)_beaconSlot.Icon != (Object)null) { _beaconSlot.Icon.preserveAspect = false; _beaconGrayOverlay = UiKit.CreateImage((Transform)(object)_beaconSlot.Root, "BeaconGray", Vector2.zero, new Vector2(112f, 112f), BeaconGrayOverlayTint); _beaconGrayOverlay.sprite = _beaconSlot.GraySprite ?? sprite; _beaconGrayOverlay.type = (Type)3; _beaconGrayOverlay.fillMethod = (FillMethod)1; _beaconGrayOverlay.fillOrigin = 1; _beaconGrayOverlay.fillAmount = 0f; } _beaconCooldown = UiKit.CreateText((Transform)(object)_beaconSlot.Root, "BeaconCooldown", Vector2.zero, new Vector2(112f, 112f), string.Empty, 44f, Color.white, (TextAlignmentOptions)514); ((TMP_Text)_beaconCooldown).outlineWidth = 0.25f; ((TMP_Text)_beaconCooldown).outlineColor = Color32.op_Implicit(Color.black); _beaconCharges = UiKit.CreateText((Transform)(object)_beaconSlot.Root, "BeaconCharges", new Vector2(0f, -28f), new Vector2(112f, 56f), string.Empty, 34f, ChargesTextColor, (TextAlignmentOptions)514); ((TMP_Text)_beaconCharges).outlineWidth = 0.25f; ((TMP_Text)_beaconCharges).outlineColor = Color32.op_Implicit(Color.black); } private static TextMeshProUGUI BuildKeyLabel(RectTransform parent, string name, float centerX) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_0041: 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) TextMeshProUGUI obj = UiKit.CreateText((Transform)(object)parent, name, new Vector2(centerX, 17f), new Vector2(220f, 34f), string.Empty, 26f, ReadyLabelColor, (TextAlignmentOptions)514); SetAnchorsBottomLeft(((TMP_Text)obj).rectTransform, new Vector2(centerX, 17f), new Vector2(220f, 34f)); return obj; } private static void SetAnchorsBottomLeft(RectTransform rect, Vector2 centerPos, Vector2 size) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0f, 0f); rect.anchorMax = val; rect.anchorMin = val; rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = centerPos; rect.sizeDelta = size; } } } namespace Werewolf.Net { public static class EventCodes { public const byte MinCode = 160; public const byte MaxCode = 189; public const byte AssignRole = 160; public const byte RevealSelfRole = 161; public const byte RevealTeammates = 162; public const byte PlayerDied = 168; public const byte GameOver = 169; public const byte GameStart = 170; public const byte PhaseChanged = 172; public const byte StartMeeting = 163; public const byte CastVote = 164; public const byte VoteProgress = 165; public const byte MeetingResult = 166; public const byte BeaconAudit = 167; public const byte SyncPerkGauge = 171; public const byte RequestMeeting = 173; public const byte RoleAction = 174; public const byte RoleState = 175; public const byte ConveneDenied = 176; public const byte CurseCandidates = 179; public const byte MeetingCancelled = 178; public const byte CosmeticGrant = 177; public const byte BombDetonation = 180; public const byte BomberState = 181; public const byte CheckmateReveal = 187; public const byte ResultDigest = 188; public const byte ModManifestRequest = 182; public const byte ModManifestReport = 183; public const byte ModIntegritySnapshot = 184; public const byte ModIntegrityDetailRequest = 185; public const byte ModIntegrityDetailResponse = 186; public static bool IsInRange(byte code) { if (code >= 160) { return code <= 189; } return false; } public static bool IsTargetOnly(byte code) { if (code != 160 && code != 161 && code != 162 && code != 171 && code != 179) { return code == 181; } return true; } public static bool IsMasterInbound(byte code) { if (code != 164 && code != 173 && code != 174 && code != 183) { return code == 185; } return true; } public static bool IsSecret(byte code) { if (!IsTargetOnly(code) && code != 164 && code != 174) { return code == 180; } return true; } public static Type[] Schema(byte code) { return code switch { 160 => new Type[1] { typeof(byte) }, 161 => new Type[1] { typeof(byte) }, 162 => new Type[2] { typeof(int[]), typeof(byte[]) }, 168 => new Type[2] { typeof(int), typeof(byte) }, 169 => new Type[3] { typeof(byte), typeof(int[]), typeof(byte[]) }, 170 => new Type[6] { typeof(long), typeof(int), typeof(byte), typeof(byte), typeof(int), typeof(byte) }, 172 => new Type[3] { typeof(byte), typeof(long), typeof(long) }, 163 => new Type[4] { typeof(int), typeof(long), typeof(long), typeof(byte) }, 164 => new Type[1] { typeof(int) }, 165 => new Type[2] { typeof(int[]), typeof(long) }, 166 => new Type[3] { typeof(int), typeof(int[]), typeof(int[]) }, 173 => new Type[1] { typeof(byte) }, 178 => new Type[1] { typeof(byte) }, 167 => new Type[1] { typeof(byte) }, 171 => new Type[6] { typeof(int), typeof(byte), typeof(byte), typeof(byte), typeof(long), typeof(int[]) }, 174 => new Type[3] { typeof(byte), typeof(int), typeof(byte) }, 175 => new Type[3] { typeof(byte), typeof(int[]), typeof(long) }, 179 => new Type[1] { typeof(int[]) }, 176 => new Type[1] { typeof(byte) }, 177 => new Type[2] { typeof(int[]), typeof(byte[]) }, 180 => new Type[2] { typeof(int), typeof(long) }, 181 => new Type[5] { typeof(int), typeof(byte), typeof(byte), typeof(long), typeof(long) }, 187 => new Type[2] { typeof(int[]), typeof(long) }, 188 => new Type[5] { typeof(byte[]), typeof(int[]), typeof(int[]), typeof(int[]), typeof(int[]) }, 182 => new Type[3] { typeof(int), typeof(byte), typeof(string) }, 183 => new Type[9] { typeof(int), typeof(byte), typeof(int), typeof(int), typeof(string), typeof(string[]), typeof(string[]), typeof(string[]), typeof(string[]) }, 184 => new Type[7] { typeof(int), typeof(int), typeof(int), typeof(int[]), typeof(byte[]), typeof(byte[]), typeof(int[]) }, 185 => new Type[2] { typeof(int), typeof(int) }, 186 => new Type[10] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(byte[]), typeof(string[]), typeof(string[]), typeof(string[]), typeof(string[]) }, _ => null, }; } } public sealed class InboundMessage { public byte Code { get; } public object[] Payload { get; } public int SenderActor { get; } public InboundMessage(byte code, object[] payload, int senderActor) { Code = code; Payload = payload; SenderActor = senderActor; } } public interface INetBus { int LocalActorNumber { get; } event Action OnReceived; event Action OnPlayerLeft; bool SendToAll(byte code, object[] payload); bool SendToActors(byte code, object[] payload, int[] targetActors); bool SendToMaster(byte code, object[] payload); } public sealed class LoopbackNetBus : INetBus { public int LocalActorNumber { get; } public event Action OnReceived; public event Action OnPlayerLeft; public LoopbackNetBus(int localActorNumber = 1) { LocalActorNumber = localActorNumber; } public void SimulatePlayerLeft(int actorNumber) { WLog.Line("player_left", false, ("actor", actorNumber), ("src", "loopback")); this.OnPlayerLeft?.Invoke(actorNumber); } public bool SendToAll(byte code, object[] payload) { object[] payload2 = NetPayload.Build(payload); LogSend(code, "all", payload2, null); Deliver(code, payload2); return true; } public bool SendToMaster(byte code, object[] payload) { object[] payload2 = NetPayload.Build(payload); LogSend(code, "master", payload2, null); Deliver(code, payload2); return true; } public bool SendToActors(byte code, object[] payload, int[] targetActors) { object[] payload2 = NetPayload.Build(payload); LogSend(code, "actors", payload2, targetActors); if (Contains(targetActors, LocalActorNumber)) { Deliver(code, payload2); } return true; } private void Deliver(byte code, object[] payload) { if (NetPayload.TryDeserialize(code, payload, out var payload2, out var _)) { this.OnReceived?.Invoke(new InboundMessage(code, payload2, LocalActorNumber)); } } private static void LogSend(byte code, string target, object[] payload, int[] targetActors) { WLog.Event("send", code, target, payload, EventCodes.IsSecret(code), targetActors); } private static bool Contains(int[] actors, int value) { if (actors == null) { return false; } for (int i = 0; i < actors.Length; i++) { if (actors[i] == value) { return true; } } return false; } } public sealed class ModManifestRequestMessage { public int Epoch { get; } public byte ProtocolVersion { get; } public string BaselineFingerprint { get; } public ModManifestRequestMessage(int epoch, byte protocolVersion, string baselineFingerprint) { Epoch = epoch; ProtocolVersion = protocolVersion; BaselineFingerprint = baselineFingerprint; } } public sealed class ModManifestReportChunk { public int Epoch { get; } public byte ProtocolVersion { get; } public int ChunkIndex { get; } public int ChunkCount { get; } public string ManifestFingerprint { get; } public IReadOnlyList Entries { get; } public bool IsFingerprintOnly => ChunkCount == 0; public ModManifestReportChunk(int epoch, byte protocolVersion, int chunkIndex, int chunkCount, string manifestFingerprint, IReadOnlyList entries) { Epoch = epoch; ProtocolVersion = protocolVersion; ChunkIndex = chunkIndex; ChunkCount = chunkCount; ManifestFingerprint = manifestFingerprint; Entries = ModIntegrityCollections.Freeze(entries); } } public sealed class ModIntegrityDetailRequestMessage { public int Epoch { get; } public int Actor { get; } public ModIntegrityDetailRequestMessage(int epoch, int actor) { Epoch = epoch; Actor = actor; } } public enum ModManifestChunkAddOutcome { Accepted, Duplicate, Completed, Conflict, Invalid } public sealed class ModManifestChunkAssembler { private readonly ModManifestReportChunk[] _chunks; private int _received; public int Epoch { get; } public string ManifestFingerprint { get; } public ModManifest Manifest { get; private set; } public ModManifestChunkAssembler(ModManifestReportChunk first) { if (first == null || first.IsFingerprintOnly) { throw new ArgumentException("first"); } Epoch = first.Epoch; ManifestFingerprint = first.ManifestFingerprint; _chunks = new ModManifestReportChunk[first.ChunkCount]; } public ModManifestChunkAddOutcome Add(ModManifestReportChunk chunk) { if (chunk == null || chunk.IsFingerprintOnly || chunk.Epoch != Epoch || chunk.ChunkCount != _chunks.Length || !string.Equals(chunk.ManifestFingerprint, ManifestFingerprint, StringComparison.Ordinal)) { return ModManifestChunkAddOutcome.Invalid; } ModManifestReportChunk modManifestReportChunk = _chunks[chunk.ChunkIndex]; if (modManifestReportChunk != null) { if (!SameEntries(modManifestReportChunk.Entries, chunk.Entries)) { return ModManifestChunkAddOutcome.Conflict; } return ModManifestChunkAddOutcome.Duplicate; } _chunks[chunk.ChunkIndex] = chunk; _received++; if (_received != _chunks.Length) { return ModManifestChunkAddOutcome.Accepted; } List list = new List(); for (int i = 0; i < _chunks.Length; i++) { ModManifestReportChunk modManifestReportChunk2 = _chunks[i]; if (modManifestReportChunk2 == null) { return ModManifestChunkAddOutcome.Invalid; } for (int j = 0; j < modManifestReportChunk2.Entries.Count; j++) { list.Add(modManifestReportChunk2.Entries[j]); if (list.Count > 512) { return ModManifestChunkAddOutcome.Invalid; } } } if (!ModManifestComparer.TryCreateManifest(list, out var manifest, out var _) || !string.Equals(manifest.Fingerprint, ManifestFingerprint, StringComparison.Ordinal)) { return ModManifestChunkAddOutcome.Invalid; } Manifest = manifest; return ModManifestChunkAddOutcome.Completed; } private static bool SameEntries(IReadOnlyList left, IReadOnlyList right) { if (left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { ModManifestEntry modManifestEntry = left[i]; ModManifestEntry modManifestEntry2 = right[i]; if (!string.Equals(modManifestEntry.Guid, modManifestEntry2.Guid, StringComparison.Ordinal) || !string.Equals(modManifestEntry.Name, modManifestEntry2.Name, StringComparison.Ordinal) || !string.Equals(modManifestEntry.Version, modManifestEntry2.Version, StringComparison.Ordinal) || !string.Equals(modManifestEntry.ContentId, modManifestEntry2.ContentId, StringComparison.Ordinal)) { return false; } } return true; } } public static class ModIntegrityWire { public const byte ProtocolVersion = 1; public const int MaxParticipants = 100; public const int MaxPlugins = 512; public const int ManifestEntriesPerChunk = 32; public const int MaxManifestChunks = 16; public const int MaxDifferences = 1024; public const int DetailEntriesPerChunk = 32; public const int MaxDetailChunks = 32; public const int MaxTextCharsPerChunk = 16384; public const int FingerprintLength = 64; public const int MaxGuidLength = 128; public const int MaxNameLength = 128; public const int MaxVersionLength = 64; public const int MaxContentIdLength = 96; public const int DetailRequestTimeoutMs = 3000; public static object[] BuildManifestRequest(int epoch, string baselineFingerprint) { return NetPayload.Build(new object[3] { epoch, (byte)1, baselineFingerprint }); } public static bool TryReadManifestRequest(object[] payload, out ModManifestRequestMessage request, out string error) { request = null; error = null; if (payload == null || payload.Length != 3) { return Fail("arity", out error); } int num = (int)payload[0]; byte b = (byte)payload[1]; string text = (string)payload[2]; if (num <= 0) { return Fail("epoch", out error); } if (b != 1) { return Fail("protocol", out error); } if (!ModManifestComparer.IsCanonicalFingerprint(text)) { return Fail("fingerprint", out error); } request = new ModManifestRequestMessage(num, b, text); return true; } public static IReadOnlyList BuildManifestReport(int epoch, string baselineFingerprint, ModManifest manifest) { if (epoch <= 0) { throw new ArgumentOutOfRangeException("epoch"); } if (!ModManifestComparer.IsCanonicalFingerprint(baselineFingerprint)) { throw new ArgumentException("baselineFingerprint"); } if (manifest == null) { throw new ArgumentNullException("manifest"); } if (manifest.Entries.Count > 512) { throw new ArgumentOutOfRangeException("manifest"); } List list = new List(); if (string.Equals(manifest.Fingerprint, baselineFingerprint, StringComparison.Ordinal)) { list.Add(NetPayload.Build(new object[9] { epoch, (byte)1, 0, 0, manifest.Fingerprint, Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty() })); return list.AsReadOnly(); } int num = Math.Max(1, (manifest.Entries.Count + 32 - 1) / 32); if (num > 16) { throw new ArgumentOutOfRangeException("manifest"); } for (int i = 0; i < num; i++) { int num2 = i * 32; int num3 = Math.Min(32, manifest.Entries.Count - num2); string[] array = new string[num3]; string[] array2 = new string[num3]; string[] array3 = new string[num3]; string[] array4 = new string[num3]; for (int j = 0; j < num3; j++) { ModManifestEntry modManifestEntry = manifest.Entries[num2 + j]; array[j] = modManifestEntry.Guid; array2[j] = modManifestEntry.Name; array3[j] = modManifestEntry.Version; array4[j] = modManifestEntry.ContentId; } ValidateManifestArrays(array, array2, array3, array4, throwOnFailure: true); list.Add(NetPayload.Build(new object[9] { epoch, (byte)1, i, num, manifest.Fingerprint, array, array2, array3, array4 })); } return list.AsReadOnly(); } public static bool TryReadManifestReport(object[] payload, out ModManifestReportChunk report, out string error) { report = null; error = null; if (payload == null || payload.Length != 9) { return Fail("arity", out error); } int num = (int)payload[0]; byte b = (byte)payload[1]; int num2 = (int)payload[2]; int num3 = (int)payload[3]; string text = (string)payload[4]; string[] array = (string[])payload[5]; string[] array2 = (string[])payload[6]; string[] array3 = (string[])payload[7]; string[] array4 = (string[])payload[8]; if (num <= 0) { return Fail("epoch", out error); } if (b != 1) { return Fail("protocol", out error); } if (!ModManifestComparer.IsCanonicalFingerprint(text)) { return Fail("fingerprint", out error); } switch (num3) { case 0: if (num2 != 0 || !AllEmpty(array, array2, array3, array4)) { return Fail("fingerprint_shape", out error); } report = new ModManifestReportChunk(num, b, 0, 0, text, Array.Empty()); return true; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: if (num2 >= 0 && num2 < num3) { if (!ValidateManifestArrays(array, array2, array3, array4, throwOnFailure: false)) { return Fail("entries", out error); } List list = new List(array.Length); for (int i = 0; i < array.Length; i++) { list.Add(new ModManifestEntry(array[i], array2[i], array3[i], array4[i])); } report = new ModManifestReportChunk(num, b, num2, num3, text, list); return true; } goto default; default: return Fail("chunk", out error); } } public static object[] BuildSnapshot(ModIntegritySnapshot snapshot) { if (snapshot == null) { throw new ArgumentNullException("snapshot"); } if (snapshot.Records.Count > 100) { throw new ArgumentOutOfRangeException("snapshot"); } int count = snapshot.Records.Count; int[] array = new int[count]; byte[] array2 = new byte[count]; byte[] array3 = new byte[count]; int[] array4 = new int[count * 4]; for (int i = 0; i < count; i++) { ModParticipantRecord modParticipantRecord = snapshot.Records[i]; array[i] = modParticipantRecord.Actor; array2[i] = (byte)modParticipantRecord.Status; array3[i] = (byte)modParticipantRecord.UnavailableReason; int num = i * 4; array4[num] = modParticipantRecord.Summary.Missing; array4[num + 1] = modParticipantRecord.Summary.Extra; array4[num + 2] = modParticipantRecord.Summary.Version; array4[num + 3] = modParticipantRecord.Summary.Content; } return NetPayload.Build(new object[7] { snapshot.Epoch, snapshot.Revision, snapshot.BaselineActor, array, array2, array3, array4 }); } public static bool TryReadSnapshot(object[] payload, out ModIntegritySnapshot snapshot, out string error) { snapshot = null; error = null; if (payload == null || payload.Length != 7) { return Fail("arity", out error); } int epoch = (int)payload[0]; int revision = (int)payload[1]; int baselineActor = (int)payload[2]; int[] array = (int[])payload[3]; byte[] array2 = (byte[])payload[4]; byte[] array3 = (byte[])payload[5]; int[] array4 = (int[])payload[6]; if (array == null || array2 == null || array3 == null || array4 == null || array.Length == 0 || array.Length > 100 || array2.Length != array.Length || array3.Length != array.Length || array4.Length != array.Length * 4) { return Fail("arrays", out error); } List list = new List(array.Length); for (int i = 0; i < array.Length; i++) { if (!Enum.IsDefined(typeof(ModIntegrityStatus), array2[i]) || !Enum.IsDefined(typeof(ModUnavailableReason), array3[i])) { return Fail("enum", out error); } int num = i * 4; if (array4[num] < 0 || array4[num + 1] < 0 || array4[num + 2] < 0 || array4[num + 3] < 0) { return Fail("diff", out error); } list.Add(new ModParticipantRecord(array[i], (ModIntegrityStatus)array2[i], (ModUnavailableReason)array3[i], new ModDifferenceSummary(array4[num], array4[num + 1], array4[num + 2], array4[num + 3]))); } return ModIntegritySnapshot.TryCreate(epoch, revision, baselineActor, list, out snapshot, out error); } public static object[] BuildDetailRequest(int epoch, int actor) { return NetPayload.Build(new object[2] { epoch, actor }); } public static bool TryReadDetailRequest(object[] payload, out ModIntegrityDetailRequestMessage request, out string error) { request = null; error = null; if (payload == null || payload.Length != 2) { return Fail("arity", out error); } int num = (int)payload[0]; int num2 = (int)payload[1]; if (num <= 0 || num2 <= 0) { return Fail("value", out error); } request = new ModIntegrityDetailRequestMessage(num, num2); return true; } public static IReadOnlyList BuildDetailResponse(int epoch, int revision, int actor, IReadOnlyList differences) { if (epoch <= 0 || revision < 0 || actor <= 0) { throw new ArgumentOutOfRangeException("epoch"); } int num = differences?.Count ?? 0; if (num > 1024) { throw new ArgumentOutOfRangeException("differences"); } int num2 = Math.Max(1, (num + 32 - 1) / 32); if (num2 > 32) { throw new ArgumentOutOfRangeException("differences"); } List list = new List(num2); for (int i = 0; i < num2; i++) { int num3 = i * 32; int num4 = Math.Min(32, num - num3); byte[] array = new byte[num4]; string[] array2 = new string[num4]; string[] array3 = new string[num4]; string[] array4 = new string[num4]; string[] array5 = new string[num4]; for (int j = 0; j < num4; j++) { ModDifference modDifference = differences[num3 + j]; array[j] = (byte)modDifference.Kind; array2[j] = modDifference.Guid; array3[j] = modDifference.Name; array4[j] = modDifference.BaselineValue; array5[j] = modDifference.ParticipantValue; } ValidateDetailArrays(array, array2, array3, array4, array5, throwOnFailure: true); list.Add(NetPayload.Build(new object[10] { epoch, revision, actor, i, num2, array, array2, array3, array4, array5 })); } return list.AsReadOnly(); } public static bool TryReadDetailResponse(object[] payload, out ModIntegrityDetailChunk chunk, out string error) { chunk = null; error = null; if (payload == null || payload.Length != 10) { return Fail("arity", out error); } int num = (int)payload[0]; int num2 = (int)payload[1]; int num3 = (int)payload[2]; int num4 = (int)payload[3]; int num5 = (int)payload[4]; byte[] array = (byte[])payload[5]; string[] array2 = (string[])payload[6]; string[] array3 = (string[])payload[7]; string[] array4 = (string[])payload[8]; string[] array5 = (string[])payload[9]; if (num <= 0 || num2 < 0 || num3 <= 0 || num5 < 1 || num5 > 32 || num4 < 0 || num4 >= num5) { return Fail("value", out error); } if (!ValidateDetailArrays(array, array2, array3, array4, array5, throwOnFailure: false)) { return Fail("entries", out error); } List list = new List(array.Length); for (int i = 0; i < array.Length; i++) { list.Add(new ModDifference((ModDifferenceKind)array[i], array2[i], array3[i], array4[i], array5[i])); } chunk = new ModIntegrityDetailChunk(num, num2, num3, num4, num5, list); return true; } private static bool ValidateManifestArrays(string[] guids, string[] names, string[] versions, string[] contentIds, bool throwOnFailure) { bool flag = guids != null && names != null && versions != null && contentIds != null && guids.Length == names.Length && guids.Length == versions.Length && guids.Length == contentIds.Length && guids.Length <= 32; int num = 0; if (flag) { for (int i = 0; i < guids.Length; i++) { flag = ValidText(guids[i], 1, 128) && ValidText(names[i], 0, 128) && ValidText(versions[i], 0, 64) && ValidText(contentIds[i], 0, 96); if (!flag) { break; } num += guids[i].Length + names[i].Length + versions[i].Length + contentIds[i].Length; if (num > 16384) { flag = false; break; } } } if (!flag && throwOnFailure) { throw new ArgumentException("manifest_entries"); } return flag; } private static bool ValidateDetailArrays(byte[] kinds, string[] guids, string[] names, string[] baselineValues, string[] participantValues, bool throwOnFailure) { bool flag = kinds != null && guids != null && names != null && baselineValues != null && participantValues != null && kinds.Length == guids.Length && kinds.Length == names.Length && kinds.Length == baselineValues.Length && kinds.Length == participantValues.Length && kinds.Length <= 32; int num = 0; if (flag) { for (int i = 0; i < kinds.Length; i++) { flag = Enum.IsDefined(typeof(ModDifferenceKind), kinds[i]) && ValidText(guids[i], 1, 128) && ValidText(names[i], 0, 128) && ValidText(baselineValues[i], 0, 96) && ValidText(participantValues[i], 0, 96); if (!flag) { break; } num += guids[i].Length + names[i].Length + baselineValues[i].Length + participantValues[i].Length; if (num > 16384) { flag = false; break; } } } if (!flag && throwOnFailure) { throw new ArgumentException("detail_entries"); } return flag; } private static bool AllEmpty(params Array[] arrays) { if (arrays == null) { return false; } for (int i = 0; i < arrays.Length; i++) { if (arrays[i] == null || arrays[i].Length != 0) { return false; } } return true; } private static bool ValidText(string value, int min, int max) { if (value != null && value.Length >= min) { return value.Length <= max; } return false; } private static bool Fail(string value, out string error) { error = value; return false; } } public static class NetPayload { public static bool IsAllowedType(Type t) { if (!(t == typeof(int)) && !(t == typeof(long)) && !(t == typeof(string)) && !(t == typeof(byte)) && !(t == typeof(byte[])) && !(t == typeof(int[]))) { return t == typeof(string[]); } return true; } public static object[] Build(object[] payload) { if (payload == null) { return Array.Empty(); } object[] array = new object[payload.Length]; for (int i = 0; i < payload.Length; i++) { object obj = payload[i]; if (obj is bool flag) { array[i] = (flag ? ((byte)1) : ((byte)0)); continue; } if (obj == null) { throw new ArgumentException($"payload[{i}] が null です(送信payloadに null は許可されない)。"); } if (!IsAllowedType(obj.GetType())) { throw new ArgumentException($"payload[{i}] が未許可の型です: {obj.GetType()}。"); } array[i] = obj; } return array; } public static bool TryDeserialize(byte code, object content, out object[] payload, out string dropReason) { payload = null; if (!EventCodes.IsInRange(code)) { return Drop(code, "badcode", out dropReason); } Type[] array = EventCodes.Schema(code); if (array == null) { return Drop(code, "unimplemented", out dropReason); } if (!(content is object[] array2)) { return Drop(code, "notarray", out dropReason); } if (array2.Length != array.Length) { return Drop(code, "arity", out dropReason); } object[] array3 = new object[array2.Length]; for (int i = 0; i < array2.Length; i++) { object obj = array2[i]; if (obj is bool flag) { obj = (flag ? ((byte)1) : ((byte)0)); } if (obj == null || obj.GetType() != array[i]) { return Drop(code, "badtype", out dropReason); } array3[i] = obj; } payload = array3; dropReason = null; return true; } private static bool Drop(byte code, string reason, out string dropReason) { dropReason = reason; WLog.Line("drop", false, ("reason", reason), ("code", (int)code)); return false; } } public sealed class PhotonNetBus : INetBus, IOnEventCallback, IInRoomCallbacks, IConnectionCallbacks { public int LocalActorNumber { get { if (!PhotonNetwork.InRoom || PhotonNetwork.LocalPlayer == null) { return -1; } return PhotonNetwork.LocalPlayer.ActorNumber; } } public event Action OnReceived; public event Action OnPlayerLeft; public event Action OnLocalDisconnected; public event Action OnRoomPropertiesChanged; public void Register() { PhotonNetwork.AddCallbackTarget((object)this); } public void Unregister() { PhotonNetwork.RemoveCallbackTarget((object)this); } public bool SendToAll(byte code, object[] payload) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return Raise(code, payload, "all", new RaiseEventOptions { Receivers = (ReceiverGroup)1 }, null); } public bool SendToActors(byte code, object[] payload, int[] targetActors) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return Raise(code, payload, "actors", new RaiseEventOptions { TargetActors = targetActors }, targetActors); } public bool SendToMaster(byte code, object[] payload) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return Raise(code, payload, "master", new RaiseEventOptions { Receivers = (ReceiverGroup)2 }, null); } private bool Raise(byte code, object[] payload, string target, RaiseEventOptions options, int[] targetActors) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.IsConnected || !PhotonNetwork.InRoom) { WLog.Line("send_fail", false, ("reason", "not_in_room"), ("code", (int)code)); return false; } object[] array; try { array = NetPayload.Build(payload); } catch (Exception ex) { WLog.Line("send_fail", false, ("reason", "bad_payload"), ("code", (int)code), ("err", ex.Message)); return false; } bool result = PhotonNetwork.RaiseEvent(code, (object)array, options, SendOptions.SendReliable); WLog.Event("send", code, target, array, EventCodes.IsSecret(code), targetActors); return result; } public unsafe void OnEvent(EventData photonEvent) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 try { byte code = photonEvent.Code; if (EventCodes.IsInRange(code)) { ClientState networkClientState = PhotonNetwork.NetworkClientState; object[] payload; string dropReason; if ((int)networkClientState != 9) { WLog.Line("drop_disconnecting", false, ("code", (int)code), ("state", ((object)(*(ClientState*)(&networkClientState))/*cast due to .constrained prefix*/).ToString())); } else if (NetPayload.TryDeserialize(code, photonEvent.CustomData, out payload, out dropReason)) { WLog.Event("recv", code, "in", payload, EventCodes.IsSecret(code)); this.OnReceived?.Invoke(new InboundMessage(code, payload, photonEvent.Sender)); } } } catch { } } public void OnPlayerLeftRoom(Player otherPlayer) { try { if (otherPlayer != null) { int actorNumber = otherPlayer.ActorNumber; WLog.Line("player_left", false, ("actor", actorNumber), ("src", "photon")); this.OnPlayerLeft?.Invoke(actorNumber); } } catch { } } public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { try { if (propertiesThatChanged != null) { this.OnRoomPropertiesChanged?.Invoke(propertiesThatChanged); } } catch { } } public void OnPlayerEnteredRoom(Player newPlayer) { } public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { } public void OnMasterClientSwitched(Player newMasterClient) { } public unsafe void OnDisconnected(DisconnectCause cause) { try { WLog.Line("local_disconnected", false, ("cause", ((object)(*(DisconnectCause*)(&cause))/*cast due to .constrained prefix*/).ToString())); this.OnLocalDisconnected?.Invoke(); } catch { } } public void OnConnected() { } public void OnConnectedToMaster() { } public void OnRegionListReceived(RegionHandler regionHandler) { } public void OnCustomAuthenticationResponse(Dictionary data) { } public void OnCustomAuthenticationFailed(string debugMessage) { } } } namespace Werewolf.Game { public sealed class BeaconEffect { private sealed class ActiveSequence { public BeaconPulseSequence Sequence; public Vector3 Position; public int Fired; } private const float InvestigateRadius = 100f; private readonly List _active = new List(); private readonly BeaconSummonGate _summonGate = new BeaconSummonGate(); public void Trigger(PlayerAvatar requesterAvatar, long nowUnixMs, int summonCooldownSec) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_007a: 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_00b4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)requesterAvatar == (Object)null) { WLog.Line("beacon_effect_skipped", true, ("reason", "no_avatar")); return; } Vector3 position = ((Component)requesterAvatar).transform.position; _active.Add(new ActiveSequence { Sequence = new BeaconPulseSequence(nowUnixMs), Position = position, Fired = 0 }); WLog.Line("beacon_effect", true, ("x", (int)position.x), ("y", (int)position.y), ("z", (int)position.z)); TrySummon(nowUnixMs, summonCooldownSec); Tick(nowUnixMs); } private void TrySummon(long nowUnixMs, int cooldownSec) { EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null) { return; } List list = new List(); foreach (EnemyParent item in instance.enemiesSpawned) { if ((Object)(object)item == (Object)null) { continue; } try { if (!GameRefs.EnemyParent_Spawned.Invoke(item)) { list.Add(item); } } catch (Exception ex) { WLog.Line("beacon_summon_error", true, ("err", ex.Message)); } } if (list.Count == 0) { WLog.Line("beacon_summon_skipped", true, ("reason", "no_despawned")); return; } if (!_summonGate.TryOpen(nowUnixMs, cooldownSec)) { WLog.Line("beacon_summon_skipped", true, ("reason", "team_cooldown")); return; } for (int i = 0; i < list.Count; i++) { try { list[i].DespawnedTimerSet(BeaconSummonPlan.ClampSeconds(i), true); } catch (Exception ex2) { WLog.Line("beacon_summon_error", true, ("err", ex2.Message)); } } WLog.Line("beacon_summon", true, ("count", list.Count)); } public void ResetSummonGate() { _summonGate.Reset(); } public void Tick(long nowUnixMs) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) if (_active.Count == 0) { return; } for (int num = _active.Count - 1; num >= 0; num--) { ActiveSequence activeSequence = _active[num]; int num2 = activeSequence.Sequence.DuePulses(nowUnixMs); while (activeSequence.Fired < num2) { activeSequence.Fired++; FirePulse(activeSequence.Position, activeSequence.Fired); } if (activeSequence.Fired >= 10) { _active.RemoveAt(num); } } } public void CancelAll(string reason) { if (_active.Count != 0) { WLog.Line("beacon_pulse_cancel", true, ("reason", reason), ("sequences", _active.Count)); _active.Clear(); } } private void FirePulse(Vector3 position, int pulseIndex) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("beacon_effect_skipped", true, ("reason", "no_enemy_director")); } else { instance.SetInvestigate(position, 100f, false); WLog.Line("beacon_pulse", true, ("n", pulseIndex)); } } } public static class BombExplosionDriver { private sealed class WarmupCoroutineHost : MonoBehaviour { public void Run(ParticleScriptExplosion source) { ((MonoBehaviour)this).StartCoroutine(RunCoroutine(source)); } private static IEnumerator RunCoroutine(ParticleScriptExplosion source) { Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(0f, -5000f, 0f); try { ((Component)source).transform.position = val; source.Spawn(val, 0.01f, 0, 0, 0f, false, false, 0f); WLog.Line("bomb_warmup", false); } catch (Exception ex) { WLog.Line("bomb_warmup_fail", false, ("err", ex.GetType().Name)); } yield break; } } private const int LethalDamage = 9999; private const float HurtColliderBaseRadiusMeters = 1.5f; private static readonly FieldInfo ExplosionPrefabField = GameRefs.ParticleScriptExplosion_explosionPrefab; private static ParticleScriptExplosion _source; private static bool _warmedThisRound; private static WarmupCoroutineHost _warmupHost; public static void Detonate(PlayerAvatar epicenter, bool localIsBomber, int playerDamage, int enemyDamage, float blastRadiusMeters) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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) if ((Object)(object)epicenter == (Object)null) { WLog.Line("bomb_explode_fail", false, ("reason", "epicenter_null")); } else { DetonateAt(((Component)epicenter).transform.position + Vector3.up * 0.5f, epicenter, localIsBomber, playerDamage, enemyDamage, blastRadiusMeters); } } public static void DetonateAt(Vector3 pos, PlayerAvatar ignorePlayer, bool localIsBomber, int playerDamage, int enemyDamage, float blastRadiusMeters) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) ParticleScriptExplosion val = EnsureSource(); if ((Object)(object)val == (Object)null) { return; } ((Component)val).transform.position = pos; float num = Mathf.Clamp(blastRadiusMeters / 1.5f, 0.5f, 20f); int num2 = (localIsBomber ? 9999 : playerDamage); ParticlePrefabExplosion val2; try { val2 = val.Spawn(pos, num, num2, enemyDamage, 1f, false, false, 1f); } catch (Exception ex) { WLog.Line("bomb_explode_fail", false, ("reason", "spawn_exception"), ("err", ex.GetType().Name)); return; } if ((Object)(object)val2 == (Object)null) { WLog.Line("bomb_explode_fail", false, ("reason", "spawn_null")); return; } GameRefs.ParticlePrefabExplosion_HurtColliderSecondSetup?.SetValue(val2, false); HurtCollider hurtCollider = val2.HurtCollider; float num3 = -1f; float num4 = -1f; if ((Object)(object)hurtCollider != (Object)null) { hurtCollider.playerDamageCooldown = 1f; if ((Object)(object)ignorePlayer != (Object)null) { hurtCollider.ignorePlayers.Add(ignorePlayer); } SphereCollider component = ((Component)hurtCollider).GetComponent(); if ((Object)(object)component != (Object)null) { float num5 = (((Object)(object)((Component)hurtCollider).transform.parent != (Object)null) ? ((Component)hurtCollider).transform.parent.lossyScale.x : 1f); num3 = component.radius; num4 = component.radius * num5 * num; } } int num6 = 0; if ((Object)(object)ignorePlayer != (Object)null && (Object)(object)ignorePlayer.playerHealth != (Object)null) { int currentHealth = GameRefs.PlayerHealth_health.Invoke(ignorePlayer.playerHealth); num6 = BombDamageRules.TargetDamage(playerDamage, currentHealth); if (num6 > 0) { ignorePlayer.playerHealth.Hurt(num6, false, -1, false); } } WLog.Line("bomb_explode", false, ("local_bomber", localIsBomber), ("size", num), ("player_dmg", num2), ("target_dmg", num6), ("enemy_dmg", enemyDamage), ("cfg_r_m", blastRadiusMeters), ("hurt_r", num3), ("blast_r_m", num4)); } private static ParticleScriptExplosion EnsureSource() { //IL_008d: 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_0098: Expected O, but got Unknown if ((Object)(object)_source != (Object)null) { return _source; } ExplosionPreset val = FindPreset(); if ((Object)(object)val == (Object)null) { WLog.Line("bomb_explode_fail", false, ("reason", "preset_not_found")); return null; } GameObject val2 = Resources.Load("Effects/Part Prefab Explosion"); if ((Object)(object)val2 == (Object)null) { WLog.Line("bomb_explode_fail", false, ("reason", "prefab_not_found")); return null; } GameObject val3 = new GameObject("WW_BombExplosionSource"); Object.DontDestroyOnLoad((Object)val3); _source = val3.AddComponent(); _source.explosionPreset = val; ExplosionPrefabField?.SetValue(_source, val2); WLog.Line("bomb_explode_source_ready", false, ("preset", ((Object)val).name)); return _source; } public static void WarmupOnce() { if (_warmedThisRound) { return; } ParticleScriptExplosion val = EnsureSource(); if (!((Object)(object)val == (Object)null)) { _warmedThisRound = true; if ((Object)(object)_warmupHost == (Object)null) { _warmupHost = ((Component)val).gameObject.AddComponent(); } _warmupHost.Run(val); } } public static void ResetWarmup() { _warmedThisRound = false; } private static ExplosionPreset FindPreset() { ExplosionPreset[] array = Resources.FindObjectsOfTypeAll(); if (array.Length != 0) { return array[0]; } ExplosionPreset[] array2 = Resources.LoadAll(""); if (array2.Length == 0) { return null; } return array2[0]; } } internal static class CosmeticCoinApplier { internal enum ApplyRoute { Primary, RecoveredSaveOnly, FallbackDirect } internal sealed class ApplyRun { private readonly int[] _plannedCounts; private readonly int[] _remaining; private int _primaryCount; private int _recoveredSaveOnlyCount; private int _fallbackDirectCount; private string _firstError; private bool _finished; private bool _success; internal int TotalRemaining { get { int num = 0; for (int i = 0; i < _remaining.Length; i++) { if (_remaining[i] > 0) { num += _remaining[i]; } } return num; } } internal bool HasRemaining => TotalRemaining > 0; internal bool Finished => _finished; internal ApplyRun(int[] countsByRarity) { _plannedCounts = (int[])countsByRarity.Clone(); _remaining = (int[])countsByRarity.Clone(); } internal bool TryApplyNext(out byte rarity, out string outcome) { rarity = 0; outcome = null; for (byte b = 0; b < _remaining.Length; b++) { if (_remaining[b] > 0) { _remaining[b]--; rarity = b; outcome = ApplyOneCoin(b); return true; } } return false; } internal void FlushRemaining() { byte rarity; string outcome; while (TryApplyNext(out rarity, out outcome)) { } } internal bool Finish() { if (_finished) { return _success; } _finished = true; if (_firstError == null) { WLog.Line("cosmetic_grant_applied", false, ("actor", LocalActorNumber()), ("counts", _plannedCounts), ("primary", _primaryCount), ("recovered_save_only", _recoveredSaveOnlyCount), ("fallback_direct", _fallbackDirectCount)); _success = true; } else { WLog.Line("cosmetic_grant_apply_failed", false, ("actor", LocalActorNumber()), ("counts", _plannedCounts), ("err", _firstError)); _success = false; } return _success; } private string ApplyOneCoin(byte rarity) { try { MetaManager instance = MetaManager.instance; if ((Object)(object)instance == (Object)null) { RecordError(rarity, "no_metamanager"); return "error:no_metamanager"; } ApplyRoute route; string text = ApplyOne(instance, rarity, out route); if (text != null) { RecordError(rarity, text); return "error:" + text; } switch (route) { case ApplyRoute.Primary: _primaryCount++; return "primary"; case ApplyRoute.RecoveredSaveOnly: _recoveredSaveOnlyCount++; return "recovered_save_only"; default: _fallbackDirectCount++; return "fallback_direct"; } } catch (Exception ex) { RecordError(rarity, ex.Message); return "error:" + ex.Message; } } private void RecordError(byte rarity, string err) { if (_firstError == null) { _firstError = "rarity=" + rarity + ":" + err; } } } private static readonly FieldRef> CosmeticTokensRef = GameRefs.MetaManager_cosmeticTokens; internal static ApplyRun BeginRun(int[] countsByRarity) { if (countsByRarity == null || countsByRarity.Length != 4) { WLog.Line("cosmetic_grant_apply_failed", false, ("actor", LocalActorNumber()), ("counts", countsByRarity ?? Array.Empty()), ("err", "invalid_counts")); return null; } return new ApplyRun(countsByRarity); } private static string ApplyOne(MetaManager instance, byte rarity, out ApplyRoute route) { int num = ReadTokenCount(instance); try { instance.CosmeticTokenAdd((Rarity)rarity); route = ApplyRoute.Primary; return null; } catch (Exception ex) { route = ApplyRoute.FallbackDirect; if (ReadTokenCount(instance) > num) { try { instance.Save(); route = ApplyRoute.RecoveredSaveOnly; return null; } catch (Exception ex2) { return "primary_partial(" + ex.Message + ") save(" + ex2.Message + ")"; } } if (CosmeticTokensRef == null) { return "primary(" + ex.Message + ") fallback(field_unresolved)"; } try { List list = CosmeticTokensRef.Invoke(instance); if (list == null) { return "primary(" + ex.Message + ") fallback(tokens_null)"; } list.Add(rarity); instance.Save(); route = ApplyRoute.FallbackDirect; return null; } catch (Exception ex3) { return "primary(" + ex.Message + ") fallback(" + ex3.Message + ")"; } } } private static int ReadTokenCount(MetaManager instance) { try { return (CosmeticTokensRef == null) ? (-1) : (CosmeticTokensRef.Invoke(instance)?.Count ?? (-1)); } catch { return -1; } } private static int LocalActorNumber() { try { return (PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : (-1); } catch { return -1; } } } public sealed class EnemyFreezer { private const float AliveShortRespawnSeconds = 1.5f; private bool _active; public bool Active => _active; public void Begin() { if (!SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("enemy_freeze_skip", false, ("reason", "not_host")); return; } EnemySpawnPatch.SuppressSpawns = true; _active = true; int num = 0; int num2 = 0; List list = (((Object)(object)EnemyDirector.instance != (Object)null) ? EnemyDirector.instance.enemiesSpawned : null); if (list != null) { EnemyParent[] array = list.ToArray(); foreach (EnemyParent val in array) { if ((Object)(object)val == (Object)null) { continue; } try { if ((Object)(object)val.EnableObject != (Object)null && val.EnableObject.activeSelf) { val.Despawn(); val.DespawnedTimerSet(1.5f, true); num++; } else { num2++; } } catch (Exception ex) { WLog.Line("enemy_despawn_error", false, ("err", ex.Message)); } } } WLog.Line("enemy_freeze_begin", false, ("despawned", num), ("preserved", num2)); } public void End() { if (_active) { _active = false; EnemySpawnPatch.SuppressSpawns = false; WLog.Line("enemy_freeze_end", false); } } } public sealed class EnemyIgnoreRoster { private static readonly FieldRef> DebugNoVision = GameRefs.EnemyDirector_debugNoVision; private readonly HashSet _added = new HashSet(); public void SetIgnored(string steamId, bool ignored) { if (string.IsNullOrEmpty(steamId)) { return; } EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("enemy_ignore_skipped", true, ("reason", "no_enemy_director")); return; } List list = DebugNoVision.Invoke(instance); if (ignored) { if (!list.Contains(steamId)) { list.Add(steamId); } _added.Add(steamId); } else { list.Remove(steamId); _added.Remove(steamId); } WLog.Line("enemy_ignore", true, ("ignored", ignored), ("count", list.Count)); } public void ClearAll() { if (_added.Count == 0) { return; } EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance != (Object)null) { List list = DebugNoVision.Invoke(instance); foreach (string item in _added) { list.Remove(item); } } _added.Clear(); WLog.Line("enemy_ignore_cleared", true); } } internal static class EnemyMapIcons { private static readonly FieldRef EnemyRef = GameRefs.EnemyParent_Enemy; private static readonly FieldRef SpawnedRef = GameRefs.EnemyParent_Spawned; private static readonly FieldRef RigidbodyRef = GameRefs.Enemy_Rigidbody; private static readonly FieldRef HasRigidbodyRef = GameRefs.Enemy_HasRigidbody; private static readonly FieldRef MapEntityRef = GameRefs.MapCustom_mapCustomEntity; private static Sprite _spriteLevel1; private static Sprite _spriteLevel2; private static Sprite _spriteLevel3; private static readonly List _added = new List(); private static bool _active; private const float VeilLiftY = 0.2f; private const int VeilSortingOrder = 100; private const float MonsterPixelsPerUnit = 480f; private static bool GateOpen() { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance != (Object)null && instance.IsRoundActiveClient) { return instance.LocalRoleClient == Role.Werewolf; } return false; } internal static void Tick() { bool flag = GateOpen(); if (flag != _active) { _active = flag; if (flag) { AttachAll(); } else { ClearAll(); } } } internal static void LateTick() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (!_active) { return; } foreach (MapCustom item in _added) { if ((Object)(object)item == (Object)null) { continue; } MapCustomEntity val = MapEntityRef.Invoke(item); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.spriteRenderer == (Object)null)) { SpriteRenderer spriteRenderer = val.spriteRenderer; Vector3 localPosition = ((Component)spriteRenderer).transform.localPosition; if (localPosition.y != 0.2f) { localPosition.y = 0.2f; ((Component)spriteRenderer).transform.localPosition = localPosition; } if (((Renderer)spriteRenderer).sortingOrder < 100) { ((Renderer)spriteRenderer).sortingOrder = 100; } } } } internal static void OnSpawn(EnemyParent parent) { if (GateOpen()) { Attach(parent); } } internal static void OnDespawn(EnemyParent parent) { Enemy val = EnemyRef.Invoke(parent); if ((Object)(object)val == (Object)null) { return; } MapCustom component = ResolveIconObject(val).GetComponent(); if (!((Object)(object)component == (Object)null)) { int num = _added.IndexOf(component); if (num >= 0) { _added.RemoveAt(num); DestroyIcon(component); } } } internal static void ClearAll() { int num = 0; foreach (MapCustom item in _added) { if (!((Object)(object)item == (Object)null)) { DestroyIcon(item); num++; } } _added.Clear(); WLog.Line("enemy_map_off", true, ("destroyed", num)); } private static void AttachAll() { int num = 0; EnemyParent[] array = Object.FindObjectsOfType(); foreach (EnemyParent val in array) { if (SpawnedRef.Invoke(val) && Attach(val)) { num++; } } WLog.Line("enemy_map_on", true, ("icons", num)); } private static bool Attach(EnemyParent parent) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) Enemy val = EnemyRef.Invoke(parent); if ((Object)(object)val == (Object)null) { return false; } GameObject val2 = ResolveIconObject(val); if ((Object)(object)val2.GetComponent() != (Object)null) { return false; } MapCustom val3 = val2.AddComponent(); val3.autoAdd = false; val3.sprite = SpriteFor(parent.difficulty); val3.color = (IsMonsterSprite(val3.sprite) ? Color.white : Color.red); val3.Add(); _added.Add(val3); return true; } private static GameObject ResolveIconObject(Enemy enemy) { EnemyRigidbody val = (HasRigidbodyRef.Invoke(enemy) ? RigidbodyRef.Invoke(enemy) : null); if (!((Object)(object)val != (Object)null)) { return ((Component)enemy).gameObject; } return ((Component)val).gameObject; } private static void DestroyIcon(MapCustom custom) { MapCustomEntity val = MapEntityRef.Invoke(custom); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } Object.Destroy((Object)(object)custom); } private static Sprite SpriteFor(Difficulty difficulty) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 EnsureSprites(); if ((int)difficulty != 1) { if ((int)difficulty == 2) { return _spriteLevel3; } return _spriteLevel1; } return _spriteLevel2; } private static bool IsMonsterSprite(Sprite sprite) { if ((Object)(object)sprite != (Object)null && ((Object)(object)sprite == (Object)(object)_spriteLevel1 || (Object)(object)sprite == (Object)(object)_spriteLevel2 || (Object)(object)sprite == (Object)(object)_spriteLevel3) && (Object)(object)sprite.texture != (Object)null && ((Texture)sprite.texture).width == 48) { return ((Texture)sprite.texture).height == 48; } return false; } private static void EnsureSprites() { if (!((Object)(object)_spriteLevel1 != (Object)null) || !((Object)(object)_spriteLevel2 != (Object)null) || !((Object)(object)_spriteLevel3 != (Object)null)) { _spriteLevel1 = CreateMonsterSprite("map_enemy_level1") ?? CreateSquareSprite(); _spriteLevel2 = CreateMonsterSprite("map_enemy_level2") ?? CreateCircleSprite(); _spriteLevel3 = CreateMonsterSprite("map_enemy_level3") ?? CreateTriangleSprite(); } } private static Sprite CreateMonsterSprite(string assetKey) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) Texture2D texture = AssetCatalog.GetTexture(assetKey); if ((Object)(object)texture == (Object)null) { return null; } return Sprite.Create(texture, new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), new Vector2(0.5f, 0.5f), 480f); } private static Sprite CreateSquareSprite() { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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) Texture2D val = new Texture2D(5, 5, (TextureFormat)5, false); Color[] array = (Color[])(object)new Color[25]; for (int i = 0; i < array.Length; i++) { array[i] = Color.white; } val.SetPixels(array); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 5f, 5f), new Vector2(0.5f, 0.5f)); } private static Sprite CreateCircleSprite() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: 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) //IL_0051: 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) Texture2D val = new Texture2D(10, 10, (TextureFormat)5, false); Color[] array = (Color[])(object)new Color[100]; float num = 5f; float num2 = 4f; for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { float num3 = (float)j - num; float num4 = (float)i - num; array[i * 10 + j] = ((num3 * num3 + num4 * num4 <= num2 * num2) ? Color.white : Color.clear); } } val.SetPixels(array); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 10f, 10f), new Vector2(0.5f, 0.5f)); } private static Sprite CreateTriangleSprite() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(10, 10, (TextureFormat)5, false); Color[] array = (Color[])(object)new Color[100]; Vector2 a = default(Vector2); ((Vector2)(ref a))..ctor(5f, 9f); Vector2 b = default(Vector2); ((Vector2)(ref b))..ctor(1f, 1f); Vector2 c = default(Vector2); ((Vector2)(ref c))..ctor(8f, 1f); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { array[i * 10 + j] = (IsPointInTriangle(new Vector2((float)j, (float)i), a, b, c) ? Color.white : Color.clear); } } val.SetPixels(array); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 10f, 10f), new Vector2(0.5f, 0.5f)); } private static bool IsPointInTriangle(Vector2 p, Vector2 a, Vector2 b, Vector2 c) { //IL_0000: 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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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) //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_009d: Unknown result type (might be due to invalid IL or missing references) float num = (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y); float num2 = (p.x - c.x) * (b.y - c.y) - (b.x - c.x) * (p.y - c.y); float num3 = (p.x - a.x) * (c.y - a.y) - (c.x - a.x) * (p.y - a.y); bool flag = num < 0f || num2 < 0f || num3 < 0f; bool flag2 = num > 0f || num2 > 0f || num3 > 0f; if (flag) { return !flag2; } return true; } } [HarmonyPatch(typeof(EnemyParent), "SpawnRPC")] internal static class EnemyMapSpawnPatch { private static void Postfix(EnemyParent __instance) { try { EnemyMapIcons.OnSpawn(__instance); } catch (Exception ex) { WLog.Line("patch_enemymap_error", false, ("via", "spawn"), ("err", ex.Message)); } } } [HarmonyPatch(typeof(EnemyParent), "DespawnRPC")] internal static class EnemyMapDespawnPatch { private static void Postfix(EnemyParent __instance) { try { EnemyMapIcons.OnDespawn(__instance); } catch (Exception ex) { WLog.Line("patch_enemymap_error", false, ("via", "despawn"), ("err", ex.Message)); } } } internal static class FakeBodies { private const float CapsuleCenterHeight = 1f; private static readonly Dictionary _bodies = new Dictionary(); internal static bool Any { get { Prune(); return _bodies.Count > 0; } } internal static bool TryGetPosition(int actor, out Vector3 pos) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (_bodies.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null) { pos = GroundOf(value); return true; } pos = default(Vector3); return false; } internal static List<(int actor, Vector3 pos)> Snapshot() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) Prune(); List<(int, Vector3)> list = new List<(int, Vector3)>(_bodies.Count); foreach (KeyValuePair body in _bodies) { list.Add((body.Key, GroundOf(body.Value))); } return list; } private static Vector3 GroundOf(Transform t) { //IL_0001: 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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) return t.position - Vector3.up * 1f; } internal static bool SpawnOrMove(int actor, Vector3 groundPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: 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) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Vector3 position = groundPos + Vector3.up * 1f; if (_bodies.TryGetValue(actor, out var value) && (Object)(object)value != (Object)null) { value.position = position; return false; } GameObject val = GameObject.CreatePrimitive((PrimitiveType)1); ((Object)val).name = "WW_FakeBody_" + actor; val.transform.position = position; Object.Destroy((Object)(object)val.GetComponent()); TryTint(val, actor); _bodies[actor] = val.transform; return true; } internal static int Clear() { int num = 0; foreach (KeyValuePair body in _bodies) { if ((Object)(object)body.Value != (Object)null) { Object.Destroy((Object)(object)((Component)body.Value).gameObject); num++; } } _bodies.Clear(); return num; } private static void Prune() { List list = null; foreach (KeyValuePair body in _bodies) { if ((Object)(object)body.Value == (Object)null) { (list ?? (list = new List())).Add(body.Key); } } if (list == null) { return; } foreach (int item in list) { _bodies.Remove(item); } } private static void TryTint(GameObject go, int actor) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) try { Renderer component = go.GetComponent(); if (!((Object)(object)component == (Object)null)) { float num = Mathf.Repeat((float)Mathf.Abs(actor) * 0.13f, 1f); component.material.color = Color.HSVToRGB(num, 0.6f, 0.9f); } } catch { } } } public sealed class GameItemDatabase : IWorldgenCatalog { public static readonly GameItemDatabase Instance = new GameItemDatabase(); public IReadOnlyDictionary ItemMaxAmounts { get { Dictionary dictionary = new Dictionary(); try { StatsManager instance = StatsManager.instance; if ((Object)(object)instance != (Object)null && instance.itemDictionary != null && instance.itemDictionary.Count > 0) { foreach (KeyValuePair item in instance.itemDictionary) { AddIfBringable(dictionary, item.Key, item.Value); } } else { Item[] array = Resources.FindObjectsOfTypeAll(); foreach (Item val in array) { AddIfBringable(dictionary, ((Object)(object)val != (Object)null) ? ((Object)val).name : null, val); } } } catch (Exception ex) { dictionary.Clear(); WLog.Line("worldgen_catalog_error", false, ("part", "items"), ("err", ex.Message)); } return dictionary; } } public IReadOnlyList PlayableLevelNames { get { List list = new List(); try { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || instance.levels == null) { return list; } foreach (Level level in instance.levels) { if (!((Object)(object)level == (Object)null)) { string name = ((Object)level).name; if (!string.IsNullOrEmpty(name)) { list.Add(name); } } } } catch (Exception ex) { list.Clear(); WLog.Line("worldgen_catalog_error", false, ("part", "levels"), ("err", ex.Message)); } return list; } } private static void AddIfBringable(Dictionary catalog, string name, Item item) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(name) && !item.disabled && (int)item.itemType != 5 && item.maxAmount > 0 && !catalog.ContainsKey(name)) { catalog[name] = item.maxAmount; } } } internal static class InputGate { internal static bool KeysFree { get { try { return SemiFunc.NoTextInputsActive(); } catch { return true; } } } } internal static class LastRunGate { private static readonly FieldRef PointsRef = GameRefs.RoundDirector_extractionPoints; private static readonly FieldRef PointsCompletedRef = GameRefs.RoundDirector_extractionPointsCompleted; private static readonly FieldRef AllCompletedRef = GameRefs.RoundDirector_allExtractionPointsCompleted; public static bool IsLastRunActive() { try { RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance == (Object)null) { return false; } int num = PointsRef.Invoke(instance); return num > 0 && PointsCompletedRef.Invoke(instance) >= num - 1; } catch (Exception ex) { WLog.Line("lastrun_probe_error", false, ("err", ex.Message)); return false; } } public static bool IsAllExtractionCompleted() { try { RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance == (Object)null) { return false; } return AllCompletedRef.Invoke(instance); } catch (Exception ex) { WLog.Line("allextract_probe_error", false, ("err", ex.Message)); return false; } } } public sealed class MeetingButton { private const float InteractDistance = 2f; private GameObject _visual; private float _promptCooldown; private bool _isLocalPlayerNear; private bool _useGrabInteract; private Transform _pressVisual; private MeshRenderer _pressRenderer; private AnimationCurve _pressCurve; private bool _pressAnimating; private float _pressEval; private static readonly Color PressColorStart = new Color(1f, 0.5f, 0f, 1f); private const float AimDotThreshold = 0.9f; public Action OnConvene; public Action OnIncompleteHold; private readonly ConveneHold _hold = new ConveneHold(); public float HoldRatio => _hold.Ratio; public bool HoldCharging => _hold.IsCharging; public Vector3? VisualWorldPos { get { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_visual != (Object)null)) { return null; } return _visual.transform.position; } } public bool Exists => (Object)(object)_visual != (Object)null; public bool IsLocalPlayerNear => _isLocalPlayerNear; public bool Create() { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_visual != (Object)null) { return true; } TruckSafetySpawnPoint instance = TruckSafetySpawnPoint.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("meeting_button_error", false, ("reason", "no_truck_anchor")); return false; } Transform transform = ((Component)instance).transform; GameConfig gameConfig = Plugin.GameConfig; float num = gameConfig?.ButtonOffsetX ?? 0f; float num2 = gameConfig?.ButtonOffsetY ?? 0f; float num3 = gameConfig?.ButtonOffsetZ ?? 0f; float num4 = gameConfig?.ButtonYaw ?? 0f; float num5 = gameConfig?.ButtonPitch ?? 0f; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(num, num2, num3); Vector3 val2 = transform.position + transform.TransformVector(val); Quaternion val3 = transform.rotation * Quaternion.Euler(num5, num4, 0f); _visual = BuildVisual(); if ((Object)(object)_visual == (Object)null) { WLog.Line("meeting_button_error", false, ("reason", "visual_build_failed")); return false; } _visual.transform.SetPositionAndRotation(val2, val3); WLog.Line("meeting_button_created", false, ("pos", $"{val2.x:F2},{val2.y:F2},{val2.z:F2}")); return true; } public void Destroy() { if ((Object)(object)_visual != (Object)null) { Object.Destroy((Object)(object)_visual); _visual = null; } _isLocalPlayerNear = false; _hold.Reset(); _useGrabInteract = false; _pressVisual = null; _pressRenderer = null; _pressCurve = null; _pressAnimating = false; _pressEval = 0f; } private bool IsAimingAtButton(PlayerAvatar local) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0046: 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_0057: Unknown result type (might be due to invalid IL or missing references) Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return true; } Vector3 val = _visual.transform.position - ((Component)main).transform.position; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.01f) { return true; } val /= magnitude; return Vector3.Dot(((Component)main).transform.forward, val) >= 0.9f; } private static bool IsLocalTumbling(PlayerAvatar local) { if (GameRefs.PlayerAvatar_isTumbling != null) { return GameRefs.PlayerAvatar_isTumbling.Invoke(local); } return false; } private void StartPressAnimation() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_pressVisual == (Object)null)) { _pressVisual.localScale = new Vector3(1f, 0.1f, 1f); _pressAnimating = true; _pressEval = 0f; } } private void TickPressAnimation() { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_0096: Unknown result type (might be due to invalid IL or missing references) if (_pressAnimating && !((Object)(object)_pressVisual == (Object)null) && _pressCurve != null) { _pressEval += Time.deltaTime * 2f; _pressEval = Mathf.Clamp01(_pressEval); float num = _pressCurve.Evaluate(_pressEval); if ((Object)(object)_pressRenderer != (Object)null && (Object)(object)((Renderer)_pressRenderer).material != (Object)null) { ((Renderer)_pressRenderer).material.SetColor("_EmissionColor", Color.Lerp(PressColorStart, Color.white, num)); } float num2 = Mathf.Clamp(num, 0.5f, 1f); _pressVisual.localScale = new Vector3(1f, num2, 1f); if (_pressEval >= 1f) { _pressAnimating = false; _pressEval = 0f; } } } public void Tick(bool localCanConvene, long readyAtUnixMs, long nowUnixMs, int rightsRemaining) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0152: 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) TickPressAnimation(); if ((Object)(object)_visual == (Object)null) { _isLocalPlayerNear = false; _hold.Reset(); return; } PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { _isLocalPlayerNear = false; _hold.Reset(); return; } if (!(_isLocalPlayerNear = Vector3.Distance(((Component)instance).transform.position, _visual.transform.position) <= 2f)) { _hold.Tick(engaged: false, Time.deltaTime); return; } try { _promptCooldown -= Time.deltaTime; if (_promptCooldown <= 0f) { string text; if (localCanConvene) { long num = readyAtUnixMs - nowUnixMs; if (num > 0) { int num2 = (int)((num + 999) / 1000); text = Texts.Format(TextId.MeetingButtonSuppressCountdownFormat, num2); } else { string text2 = (_useGrabInteract ? Texts.Get(TextId.MeetingButtonConveneGrabPrompt) : Texts.Get(TextId.MeetingButtonConveneInteractPrompt)); text = ((rightsRemaining >= 0) ? (text2 + Texts.Format(TextId.MeetingButtonRightsSuffixFormat, rightsRemaining)) : text2); } } else { text = Texts.Get(TextId.MeetingButtonSuppressedPrompt); } SemiFunc.UIFocusText(text, Color.white, new Color(1f, 0.6f, 0.6f), 0.2f); _promptCooldown = 0.1f; } int num3 = (_useGrabInteract ? 10 : 2); bool flag = SemiFunc.InputHold((InputKey)num3); bool flag2 = SemiFunc.InputUp((InputKey)num3); bool flag3 = !_useGrabInteract || IsAimingAtButton(instance); bool flag4 = readyAtUnixMs - nowUnixMs > 0; bool flag5 = localCanConvene && !flag4 && flag3 && !IsLocalTumbling(instance); bool isCharging = _hold.IsCharging; if (_hold.Tick(flag5 && flag, Time.deltaTime)) { WLog.Line("meeting_button_pressed", false, ("localGate", localCanConvene), ("mode", _useGrabInteract ? "grab" : "interact")); StartPressAnimation(); OnConvene?.Invoke(); } else if (isCharging && flag2 && flag5) { WLog.Line("meeting_button_hold_incomplete", false, ("mode", _useGrabInteract ? "grab" : "interact")); OnIncompleteHold?.Invoke(); } } catch (Exception ex) { WLog.Line("meeting_button_tick_error", false, ("err", ex.Message)); } } private GameObject BuildVisual() { GameObject val = TryBuildFromShopButtonTemplate(); if ((Object)(object)val != (Object)null) { return val; } GameObject val2 = TryBuildFromAssetBundle(); if ((Object)(object)val2 != (Object)null) { return val2; } return BuildPrimitive(); } private GameObject TryBuildFromShopButtonTemplate() { try { if (!MeetingButtonAssetResolver.TryResolve()) { return null; } GameObject template = MeetingButtonAssetResolver.Template; if ((Object)(object)template == (Object)null) { return null; } GameObject val = Object.Instantiate(template); ((Object)val).name = "WW_MeetingButton_ShopClone"; MeshRenderer val2 = val.GetComponent() ?? val.GetComponentInChildren(true); _pressVisual = val.transform; _pressRenderer = val2; _pressCurve = MeetingButtonAssetResolver.PressCurve; _useGrabInteract = true; _pressAnimating = false; _pressEval = 0f; WLog.Line("meeting_button_visual_shop", false, ("hasRenderer", (Object)(object)val2 != (Object)null), ("hasCurve", _pressCurve != null)); return val; } catch (Exception ex) { WLog.Line("meeting_button_shop_fallback", false, ("err", ex.Message)); _useGrabInteract = false; _pressVisual = null; _pressRenderer = null; _pressCurve = null; return null; } } private GameObject TryBuildFromAssetBundle() { try { GameObject prefab = AssetCatalog.GetPrefab("MeetingButton"); if ((Object)(object)prefab == (Object)null) { return null; } GameObject val = Object.Instantiate(prefab); ((Object)val).name = "WW_MeetingButton"; Collider[] componentsInChildren = val.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.Destroy((Object)(object)componentsInChildren[i]); } return val; } catch (Exception ex) { WLog.Line("meeting_button_bundle_fallback", false, ("err", ex.Message)); return null; } } private GameObject BuildPrimitive() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GameObject.CreatePrimitive((PrimitiveType)2); ((Object)obj).name = "WW_MeetingButton"; Collider component = obj.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } obj.transform.localScale = new Vector3(0.3f, 0.15f, 0.3f); Renderer component2 = obj.GetComponent(); if ((Object)(object)component2 != (Object)null && (Object)(object)component2.material != (Object)null) { component2.material.color = Color.red; component2.material.EnableKeyword("_EMISSION"); component2.material.SetColor("_EmissionColor", Color.red); } return obj; } } internal static class MeetingButtonAssetResolver { private static bool _resolved; private static GameObject _template; private static AnimationCurve _pressCurve; internal static GameObject Template => _template; internal static AnimationCurve PressCurve => _pressCurve; internal static bool TryResolve() { if (_resolved) { return (Object)(object)_template != (Object)null; } _resolved = true; RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("mb_resolver_no_runmanager", false); return false; } List levelShop = instance.levelShop; if (levelShop == null || levelShop.Count == 0) { WLog.Line("mb_resolver_no_shop_levels", false, ("null", levelShop == null)); return false; } foreach (Level item in levelShop) { if (!((Object)(object)item == (Object)null)) { string lvlName = item.NarrativeName ?? ((Object)item).name ?? "?"; TryScan(lvlName, "StartRooms", item.StartRooms); if ((Object)(object)_template != (Object)null) { return true; } TryScan(lvlName, "ModulesSpecial", item.ModulesSpecial); if ((Object)(object)_template != (Object)null) { return true; } TryScan(lvlName, "ModulesNormal1", item.ModulesNormal1); TryScan(lvlName, "ModulesNormal2", item.ModulesNormal2); TryScan(lvlName, "ModulesNormal3", item.ModulesNormal3); TryScan(lvlName, "ModulesPassage1", item.ModulesPassage1); TryScan(lvlName, "ModulesPassage2", item.ModulesPassage2); TryScan(lvlName, "ModulesPassage3", item.ModulesPassage3); TryScan(lvlName, "ModulesDeadEnd1", item.ModulesDeadEnd1); TryScan(lvlName, "ModulesDeadEnd2", item.ModulesDeadEnd2); TryScan(lvlName, "ModulesDeadEnd3", item.ModulesDeadEnd3); TryScan(lvlName, "ModulesExtraction1", item.ModulesExtraction1); TryScan(lvlName, "ModulesExtraction2", item.ModulesExtraction2); TryScan(lvlName, "ModulesExtraction3", item.ModulesExtraction3); if ((Object)(object)_template != (Object)null) { return true; } } } WLog.Line("mb_resolver_not_found", false); return false; } private static void TryScan(string lvlName, string listName, List list) { if (list == null || list.Count == 0) { return; } for (int i = 0; i < list.Count; i++) { PrefabRef val = list[i]; if (val == null || !((PrefabRef)(object)val).IsValid()) { continue; } GameObject prefab; try { prefab = ((PrefabRef)(object)val).Prefab; } catch (Exception ex) { WLog.Line("mb_resolver_prefab_error", false, ("lvl", lvlName), ("list", listName), ("name", ((PrefabRef)(object)val).PrefabName), ("err", ex.Message)); continue; } if ((Object)(object)prefab == (Object)null) { continue; } ExtractionPoint[] componentsInChildren = prefab.GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { continue; } ExtractionPoint[] array = componentsInChildren; foreach (ExtractionPoint val2 in array) { if (!((Object)(object)val2.shopButton == (Object)null)) { _template = ((Component)val2.shopButton).gameObject; _pressCurve = val2.buttonPressAnimationCurve; WLog.Line("mb_resolver_ok", false, ("lvl", lvlName), ("list", listName), ("name", ((PrefabRef)(object)val).PrefabName), ("btnName", ((Object)_template).name), ("curve", _pressCurve != null)); return; } } } } } internal enum CollectorState { NotStarted, Collecting, Ready, Failed } internal sealed class ModManifestCollector { private sealed class PluginSnapshot { public string Guid; public string Name; public string Version; public string Location; public Guid ModuleVersionId; public string AssemblyFullName; } private sealed class CollectionResult { public ModManifest Manifest; public string Error; } private Task _task; public CollectorState State { get; private set; } public ModManifest Result { get; private set; } public string Error { get; private set; } public void EnsureStarted() { if (State == CollectorState.Ready || State == CollectorState.Failed) { return; } if (State == CollectorState.Collecting) { CompleteIfReady(); return; } try { List snapshots = new List(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if (((value != null) ? value.Metadata : null) != null) { Assembly assembly = (((Object)(object)value.Instance != (Object)null) ? ((object)value.Instance).GetType().Assembly : null); snapshots.Add(new PluginSnapshot { Guid = value.Metadata.GUID, Name = value.Metadata.Name, Version = ((value.Metadata.Version != null) ? value.Metadata.Version.ToString() : string.Empty), Location = value.Location, ModuleVersionId = ((assembly != null) ? assembly.ManifestModule.ModuleVersionId : Guid.Empty), AssemblyFullName = (assembly?.FullName ?? string.Empty) }); } } State = CollectorState.Collecting; _task = Task.Run(() => Collect(snapshots)); WLog.Line("mod_integrity_collect", false, ("state", "started"), ("plugins", snapshots.Count)); } catch (Exception ex) { Fail(ex.GetType().Name); } } public void ResetSessionView() { } private void CompleteIfReady() { if (_task == null || !_task.IsCompleted) { return; } try { CollectionResult result = _task.GetAwaiter().GetResult(); if (result.Manifest == null) { Fail(result.Error ?? "manifest"); return; } Result = result.Manifest; Error = null; State = CollectorState.Ready; WLog.Line("mod_integrity_collect", false, ("state", "ready"), ("plugins", Result.Entries.Count), ("fingerprint", Result.Fingerprint.Substring(0, 8))); } catch (Exception ex) { Fail(ex.GetType().Name); } } private void Fail(string error) { Result = null; Error = (string.IsNullOrEmpty(error) ? "failed" : error); State = CollectorState.Failed; WLog.Line("mod_integrity_collect", false, ("state", "failed"), ("reason", Error)); } private static CollectionResult Collect(IReadOnlyList snapshots) { List list = new List(snapshots.Count); for (int i = 0; i < snapshots.Count; i++) { PluginSnapshot pluginSnapshot = snapshots[i]; list.Add(new ModManifestEntry(Bound(pluginSnapshot.Guid, 128), Bound(pluginSnapshot.Name, 128), Bound(pluginSnapshot.Version, 64), BuildContentId(pluginSnapshot))); } if (!ModManifestComparer.TryCreateManifest(list, out var manifest, out var error)) { return new CollectionResult { Error = error }; } return new CollectionResult { Manifest = manifest }; } private static string BuildContentId(PluginSnapshot plugin) { if (!string.IsNullOrEmpty(plugin.Location)) { try { using FileStream inputStream = File.OpenRead(plugin.Location); using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(inputStream); StringBuilder stringBuilder = new StringBuilder(array.Length * 2 + 7); stringBuilder.Append("sha256:"); for (int i = 0; i < array.Length; i++) { stringBuilder.Append(array[i].ToString("x2")); } return stringBuilder.ToString(); } catch { } } if (plugin.ModuleVersionId != Guid.Empty) { return "mvid:" + plugin.ModuleVersionId.ToString("D").ToLowerInvariant(); } return Bound("metadata:" + (plugin.AssemblyFullName ?? string.Empty), 96); } private static string Bound(string value, int maxLength) { if (value == null) { value = string.Empty; } if (value.Length > maxLength) { return value.Substring(0, maxLength); } return value; } } public sealed class MovementFreezer { private static readonly FieldRef> VoiceChatsRef = GameRefs.RunManager_voiceChats; private static readonly FieldRef> InputActionsRef = GameRefs.InputManager_inputActions; private static readonly FieldRef ToggleMuteRef = GameRefs.DataDirector_toggleMute; private const float KeepAliveSeconds = 0.1f; private bool _active; public bool Active => _active; public void Begin() { _active = true; PlayerSpawnPatch.MeetingActive = true; ReleaseGrabNow(); WLog.Line("freeze_begin", false); } public void End() { if (_active) { _active = false; PlayerSpawnPatch.MeetingActive = false; WLog.Line("freeze_end", false); } } public void Tick(bool freezeMovement) { if (!_active) { return; } try { if (freezeMovement) { SemiFunc.InputDisableMovement(); PhysGrabber instance = PhysGrabber.instance; if ((Object)(object)instance != (Object)null) { instance.OverrideGrabDisable(0.1f); } PollToggleMute(); } DisableSpatialAll(); } catch (Exception ex) { WLog.Line("freeze_tick_error", false, ("err", ex.Message)); } } public void ReleaseGrabNow() { PhysGrabber instance = PhysGrabber.instance; if ((Object)(object)instance != (Object)null) { instance.OverrideGrabRelease(-1, 0.1f); } } private void DisableSpatialAll() { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return; } List list = VoiceChatsRef.Invoke(instance); if (list == null) { return; } foreach (PlayerVoiceChat item in list) { if ((Object)(object)item != (Object)null) { item.SpatialDisable(0.1f); } } } private static void PollToggleMute() { if (InputActionsRef == null || ToggleMuteRef == null || !SemiFunc.IsMultiplayer() || !InputGate.KeysFree) { return; } InputManager instance = InputManager.instance; DataDirector instance2 = DataDirector.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null)) { Dictionary dictionary = InputActionsRef.Invoke(instance); if (dictionary != null && dictionary.TryGetValue((InputKey)32, out var value) && value != null && value.WasPressedThisFrame()) { ref bool reference = ref ToggleMuteRef.Invoke(instance2); reference = !reference; } } } } public sealed class PerkEffects { private bool _staminaApplied; private readonly NaturalHealClock _healClock = new NaturalHealClock(); public void Tick(RolesClientState state, Role? localRole, Action sendRoleAction, Action onWolfModeChanged = null) { //IL_0014: 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) bool keysFree = InputGate.KeysFree; if (keysFree && Plugin.WolfModeKey != null && Input.GetKeyDown(Plugin.WolfModeKey.Value) && state.TryToggleWolfMode(localRole)) { sendRoleAction?.Invoke(2, 0, state.WolfMode ? ((byte)1) : ((byte)0)); onWolfModeChanged?.Invoke(state.WolfMode); } if (keysFree && Plugin.BeaconKey != null && Input.GetKeyDown(Plugin.BeaconKey.Value) && localRole == Role.Werewolf) { sendRoleAction?.Invoke(1, 0, 0); } ApplyStamina(state.StaminaActive); } public void TickHeal(bool active, long nowUnixMs, int intervalSec) { if (_healClock.ShouldHeal(active, nowUnixMs, intervalSec)) { PlayerAvatar instance = PlayerAvatar.instance; PlayerHealth val = (((Object)(object)instance != (Object)null) ? instance.playerHealth : null); if (!((Object)(object)val == (Object)null)) { val.Heal(1, false); } } } private void ApplyStamina(bool active) { PlayerController instance = PlayerController.instance; if (!((Object)(object)instance == (Object)null)) { if (active && !_staminaApplied) { instance.DebugEnergy = true; _staminaApplied = true; } else if (!active && _staminaApplied) { instance.DebugEnergy = false; _staminaApplied = false; } } } public void ResetEffects() { ApplyStamina(active: false); _healClock.Reset(); } } public sealed class PlayerRegistry { private FieldRef _deadSetRef; public bool Available { get; private set; } public void Initialize() { _deadSetRef = GameRefs.PlayerAvatar_deadSet; Available = _deadSetRef != null; WLog.Line("registry_init", false, ("status", Available ? "ok" : "field_resolve_failed")); } public List BuildRealPlayers() { List list = new List(); GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { WLog.Line("registry_build", false, ("status", "no_gamedirector")); return list; } foreach (PlayerAvatar player in instance.PlayerList) { if (!((Object)(object)player == (Object)null)) { WPlayer item = new WPlayer { ActorNumber = ResolveActor(player), Name = SemiFunc.PlayerGetName(player), SteamId = SemiFunc.PlayerGetSteamID(player), IsBot = false }; list.Add(item); } } return list; } public int ResolveActor(PlayerAvatar avatar) { if (!SemiFunc.IsMultiplayer()) { return 1; } if ((Object)(object)avatar != (Object)null && (Object)(object)avatar.photonView != (Object)null && avatar.photonView.Owner != null) { return avatar.photonView.Owner.ActorNumber; } return -1; } public bool IsDeadSet(PlayerAvatar avatar) { if (!Available || _deadSetRef == null || (Object)(object)avatar == (Object)null) { return false; } return _deadSetRef.Invoke(avatar); } } public sealed class RoomState { public const string KeyPhase = "WW_Phase"; public const string KeyRoundEndTime = "WW_RoundEndTime"; public const string KeyIsAlive = "WW_IsAlive"; public const string KeyMeetingCaller = "WW_MeetingCaller"; public const string KeyMeetingEndTime = "WW_MeetingEndTime"; public const string KeyMinimapHide = "WW_CfgMinimapHide"; public const string KeyCatPossible = "WW_CfgCatPossible"; public const string KeyValuableMapMode = "WW_CfgValuableMapMode"; public const string KeyRights = "WW_Rights"; public const string KeyCfgShared = "WW_CfgShared"; public const string KeyNecroVoiceMode = "WW_CfgNecroVoiceMode"; public const string KeyExtraJump = "WW_CfgExtraJump"; public const string KeyConveneSuppressStart = "WW_CfgConveneSuppressStart"; public const string KeyConveneSuppressAfter = "WW_CfgConveneSuppressAfter"; public const string KeyHealInterval = "WW_CfgHealInterval"; public const string KeyBomb = "WW_CfgBomb"; public const string KeyShaman = "WW_CfgShaman"; public void PublishPhase(GamePhase phase, long roundEndUnixMs) { SemiFunc.SetCurrentRoomProperty("WW_Phase", (object)(int)phase); SemiFunc.SetCurrentRoomProperty("WW_RoundEndTime", (object)roundEndUnixMs); WLog.Line("roomstate_publish", false, ("key", "phase"), ("phase", phase), ("roundEnd", roundEndUnixMs)); } public void PublishAlive(int actorNumber, bool alive) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (SemiFunc.IsMasterClientOrSingleplayer()) { Room currentRoom = PhotonNetwork.CurrentRoom; Player val = ((currentRoom != null) ? currentRoom.GetPlayer(actorNumber, false) : null); if (val != null) { Hashtable val2 = new Hashtable { [(object)"WW_IsAlive"] = (alive ? ((byte)1) : ((byte)0)) }; val.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); } WLog.Line("roomstate_publish", false, ("key", "isalive"), ("actor", actorNumber), ("alive", alive ? 1 : 0)); } } public void PublishMeeting(int callerActor, long meetingEndUnixMs) { SemiFunc.SetCurrentRoomProperty("WW_MeetingCaller", (object)callerActor); SemiFunc.SetCurrentRoomProperty("WW_MeetingEndTime", (object)meetingEndUnixMs); WLog.Line("roomstate_publish", false, ("key", "meeting"), ("caller", callerActor), ("meetingEnd", meetingEndUnixMs)); } public bool TryReadMeeting(out int callerActor, out long meetingEndUnixMs) { callerActor = -1; meetingEndUnixMs = 0L; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { return false; } if (((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_MeetingCaller", out object value) && value is int num && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_MeetingEndTime", out object value2) && value2 is long num2) { callerActor = num; meetingEndUnixMs = num2; return true; } return false; } public bool TryReadPhase(out GamePhase phase) { phase = GamePhase.Lobby; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_Phase", out object value) && value is int num) { phase = (GamePhase)num; return true; } return false; } public bool TryReadRoundEndTime(out long roundEndUnixMs) { roundEndUnixMs = 0L; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_RoundEndTime", out object value) && value is long num) { roundEndUnixMs = num; return true; } return false; } public bool TryReadAlive(int actorNumber, out bool alive) { alive = true; Room currentRoom = PhotonNetwork.CurrentRoom; Player val = ((currentRoom != null) ? currentRoom.GetPlayer(actorNumber, false) : null); if (val != null && ((Dictionary)(object)val.CustomProperties).TryGetValue((object)"WW_IsAlive", out object value) && value is byte b) { alive = b != 0; return true; } return false; } public void PublishSettings(GameConfig config, int playerCount) { SemiFunc.SetCurrentRoomProperty("WW_CfgMinimapHide", (object)RoomStateKeys.EncodeBool(config.MinimapHideEnabled)); SemiFunc.SetCurrentRoomProperty("WW_CfgCatPossible", (object)RoomStateKeys.EncodeBool(config.BlackCatPossible(playerCount))); SemiFunc.SetCurrentRoomProperty("WW_CfgValuableMapMode", (object)RoomStateKeys.EncodeValuableMapMode(config.ValuableMapMode)); SemiFunc.SetCurrentRoomProperty("WW_CfgNecroVoiceMode", (object)RoomStateKeys.EncodeNecroVoiceMode(config.NecroVoiceMode)); SemiFunc.SetCurrentRoomProperty("WW_CfgExtraJump", (object)RoomStateKeys.EncodeExtraJump(config.ExtraJumpCount)); SemiFunc.SetCurrentRoomProperty("WW_CfgConveneSuppressStart", (object)config.ConveneSuppressStartSec); SemiFunc.SetCurrentRoomProperty("WW_CfgConveneSuppressAfter", (object)config.ConveneSuppressAfterSec); SemiFunc.SetCurrentRoomProperty("WW_CfgHealInterval", (object)config.HealIntervalSec); SemiFunc.SetCurrentRoomProperty("WW_CfgBomb", (object)RoomStateKeys.EncodeBomb(config, playerCount)); SemiFunc.SetCurrentRoomProperty("WW_CfgShaman", (object)RoomStateKeys.EncodeShaman(config)); WLog.Line("roomstate_publish", false, ("key", "settings"), ("minimapHide", config.MinimapHideEnabled ? 1 : 0), ("catPossible", config.BlackCatPossible(playerCount) ? 1 : 0), ("valuableMode", (int)config.ValuableMapMode), ("necroVoiceMode", (int)config.NecroVoiceMode), ("extraJump", config.ExtraJumpCount), ("conveneSupStart", config.ConveneSuppressStartSec), ("conveneSupAfter", config.ConveneSuppressAfterSec), ("healInterval", config.HealIntervalSec), ("bomberPossible", config.BomberPossible(playerCount) ? 1 : 0)); } public bool TryReadBombPack(out int[] packed) { packed = null; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgBomb", out object value) && value is int[] array && array.Length == 8) { packed = array; return true; } return false; } public bool TryReadShamanPack(out int[] packed) { packed = null; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgShaman", out object value) && value is int[] array && array.Length == 5) { packed = array; return true; } return false; } public bool TryReadMinimapHide(out bool minimapHideEnabled) { minimapHideEnabled = false; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgMinimapHide", out object value) && value is byte value2) { minimapHideEnabled = RoomStateKeys.DecodeBool(value2); return true; } return false; } public bool TryReadCatPossible(out bool catPossible) { catPossible = false; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgCatPossible", out object value) && value is byte value2) { catPossible = RoomStateKeys.DecodeBool(value2); return true; } return false; } public bool TryReadValuableMapMode(out ValuableMapMode mode) { mode = ValuableMapMode.MeetingSync; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgValuableMapMode", out object value) && value is byte value2) { mode = RoomStateKeys.DecodeValuableMapMode(value2); return true; } return false; } public bool TryReadNecroVoiceMode(out NecroVoiceMode mode) { mode = NecroVoiceMode.Off; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgNecroVoiceMode", out object value) && value is byte value2) { mode = RoomStateKeys.DecodeNecroVoiceMode(value2); return true; } return false; } public bool TryReadExtraJump(out int extraJumpCount) { extraJumpCount = 0; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgExtraJump", out object value) && value is byte value2) { extraJumpCount = RoomStateKeys.DecodeExtraJump(value2); return true; } return false; } public bool TryReadConveneSuppressStart(out int seconds) { seconds = 0; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgConveneSuppressStart", out object value) && value is int num) { seconds = num; return true; } return false; } public bool TryReadConveneSuppressAfter(out int seconds) { seconds = 0; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgConveneSuppressAfter", out object value) && value is int num) { seconds = num; return true; } return false; } public bool TryReadHealInterval(out int seconds) { seconds = 0; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgHealInterval", out object value) && value is int num) { seconds = num; return true; } return false; } public void PublishRights(int actorNumber, int remaining) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown if (SemiFunc.IsMasterClientOrSingleplayer()) { Room currentRoom = PhotonNetwork.CurrentRoom; Player val = ((currentRoom != null) ? currentRoom.GetPlayer(actorNumber, false) : null); if (val != null) { Hashtable val2 = new Hashtable { [(object)"WW_Rights"] = RoomStateKeys.EncodeRights(remaining) }; val.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); } WLog.Line("roomstate_publish", false, ("key", "rights"), ("actor", actorNumber), ("remaining", remaining)); } } public bool TryReadRights(int actorNumber, out int remaining) { remaining = 0; Room currentRoom = PhotonNetwork.CurrentRoom; Player val = ((currentRoom != null) ? currentRoom.GetPlayer(actorNumber, false) : null); if (val != null && ((Dictionary)(object)val.CustomProperties).TryGetValue((object)"WW_Rights", out object value) && value is byte value2) { remaining = RoomStateKeys.DecodeRights(value2); return true; } return false; } public void PublishSharedSettings(string blob) { SemiFunc.SetCurrentRoomProperty("WW_CfgShared", (object)blob); WLog.Line("roomstate_publish", false, ("key", "cfgshared"), ("blobLen", blob?.Length ?? 0)); } public bool TryReadSharedSettings(out string blob) { blob = null; Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && ((Dictionary)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"WW_CfgShared", out object value) && value is string text) { blob = text; return true; } return false; } } public sealed class TruckWarper { private static readonly FieldRef DeathHeadRef = GameRefs.PlayerAvatar_playerDeathHead; private static readonly FieldRef HeadGrabObjectRef = GameRefs.PlayerDeathHead_physGrabObject; private static readonly FieldRef IsTumblingRef = GameRefs.PlayerAvatar_isTumbling; private const float WrapOffset = 0.6f; internal const float RewarpDistance = 6f; internal const long RewarpSustainMs = 1500L; internal const long RewarpCooldownMs = 3000L; private readonly Random _rng = new Random(); private readonly List<(PlayerAvatar avatar, Vector3 pos, Quaternion rot, bool wasAlive)> _assignments = new List<(PlayerAvatar, Vector3, Quaternion, bool)>(); private readonly RewarpGate _rewarpGate = new RewarpGate(1500L, 3000L); public Vector3? LocalPlayerWarpTarget { get; private set; } private static PhysGrabObject TumbleGrabObject(PlayerAvatar avatar) { PlayerTumble val = GameRefs.PlayerAvatar_tumble.Invoke(avatar); if (!((Object)(object)val != (Object)null)) { return null; } return GameRefs.PlayerTumble_physGrabObject.Invoke(val); } internal static bool TryGetDeathHeadPosition(PlayerAvatar avatar, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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) //IL_006a: Unknown result type (might be due to invalid IL or missing references) pos = default(Vector3); try { if ((Object)(object)avatar == (Object)null) { return false; } PlayerDeathHead val = DeathHeadRef.Invoke(avatar); if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy) { return false; } PhysGrabObject val2 = HeadGrabObjectRef.Invoke(val); pos = (((Object)(object)val2 != (Object)null) ? ((Component)val2).transform.position : ((Component)val).transform.position); return true; } catch { return false; } } public static bool IsTumbling(PlayerAvatar avatar) { if ((Object)(object)avatar != (Object)null) { return IsTumblingRef.Invoke(avatar); } return false; } public static SpawnPoint[] ResolveTruckSpawnPoints() { return Object.FindObjectsOfType(); } internal static string FormatVec(Vector3 v) { return v.x.ToString("F2", CultureInfo.InvariantCulture) + "," + v.y.ToString("F2", CultureInfo.InvariantCulture) + "," + v.z.ToString("F2", CultureInfo.InvariantCulture); } public bool WarpAll(Func isAlive) { //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_015a: 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_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) LocalPlayerWarpTarget = null; _assignments.Clear(); _rewarpGate.Reset(); if (!SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("truck_warp_skip", false, ("reason", "not_host")); return false; } SpawnPoint[] array = ResolveTruckSpawnPoints(); if (array == null || array.Length == 0) { WLog.Line("truck_warp_error", false, ("reason", "no_spawnpoint")); return false; } Shuffle(array); List list = (((Object)(object)GameDirector.instance != (Object)null) ? GameDirector.instance.PlayerList : null); if (list == null || list.Count == 0) { WLog.Line("truck_warp_error", false, ("reason", "no_players")); return false; } List> list2 = new List>(list.Count); Vector3 val = Vector3.zero; int num = 0; foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null)) { SpawnPoint obj = array[num % array.Length]; int num2 = num / array.Length; Vector3 val2 = ((Component)obj).transform.position; if (num2 > 0) { float num3 = 0.6f * (float)num2; val2 += new Vector3(num3, 0f, num3); } list2.Add(new KeyValuePair(item, val2)); val += val2; num++; } } if (list2.Count == 0) { WLog.Line("truck_warp_error", false, ("reason", "no_valid_players")); return false; } Vector3 val3 = val / (float)list2.Count; int num4 = 0; int num5 = 0; foreach (KeyValuePair item2 in list2) { PlayerAvatar key = item2.Key; Vector3 value = item2.Value; Vector3 val4 = val3 - value; val4.y = 0f; Quaternion val5 = ((((Vector3)(ref val4)).sqrMagnitude > 0.0001f) ? Quaternion.LookRotation(val4) : Quaternion.identity); bool flag = isAlive?.Invoke(key) ?? false; _assignments.Add((key, value, val5, flag)); try { if (flag) { bool num6 = (Object)(object)key == (Object)(object)PlayerAvatar.instance; Vector3 v = (num6 ? ((Component)key).transform.position : Vector3.zero); WarpAliveAvatar(key, value, val5); num4++; if (num6) { LocalPlayerWarpTarget = value; WLog.Line("truck_warp_local_diag", false, ("beforePos", FormatVec(v)), ("targetPos", FormatVec(value)), ("isTumbling", IsTumbling(key)), ("multiplayer", SemiFunc.IsMultiplayer())); } continue; } PlayerDeathHead val6 = DeathHeadRef.Invoke(key); if ((Object)(object)val6 != (Object)null) { PhysGrabObject val7 = HeadGrabObjectRef.Invoke(val6); if ((Object)(object)val7 != (Object)null) { val7.Teleport(value, val5); num5++; } } } catch (Exception ex) { WLog.Line("truck_warp_player_error", false, ("err", ex.Message)); } } WLog.Line("truck_warp", false, ("points", array.Length), ("alive", num4), ("heads", num5)); return true; } private void WarpAliveAvatar(PlayerAvatar avatar, Vector3 pos, Quaternion rot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) avatar.Spawn(pos, rot); if (!IsTumbling(avatar)) { return; } PhysGrabObject val = TumbleGrabObject(avatar); if ((Object)(object)val != (Object)null) { val.Teleport(pos, rot); if ((Object)(object)val.rb != (Object)null) { val.rb.velocity = Vector3.zero; val.rb.angularVelocity = Vector3.zero; } WLog.Line("truck_warp_tumble", false, ("actor", ((Object)(object)avatar.photonView != (Object)null) ? avatar.photonView.OwnerActorNr : (-1))); } } public void TickWatchdog(Func isAlive, long nowMs) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) if (!SemiFunc.IsMasterClientOrSingleplayer() || _assignments.Count == 0) { return; } try { foreach (var assignment in _assignments) { var (val, _, _, _) = assignment; if ((Object)(object)val == (Object)null || (isAlive?.Invoke(val) ?? false) != assignment.wasAlive) { continue; } Vector3 position; if (assignment.wasAlive) { position = ((Component)val).transform.position; } else { PlayerDeathHead val2 = DeathHeadRef.Invoke(val); if ((Object)(object)val2 == (Object)null) { continue; } PhysGrabObject val3 = HeadGrabObjectRef.Invoke(val2); if ((Object)(object)val3 == (Object)null) { continue; } position = ((Component)val3).transform.position; } if ((Object)(object)val.photonView == (Object)null) { continue; } int ownerActorNr = val.photonView.OwnerActorNr; float num = Vector3.Distance(position, assignment.pos); bool isFar = num > 6f; if (!_rewarpGate.Tick(ownerActorNr, isFar, nowMs)) { continue; } if (assignment.wasAlive) { WarpAliveAvatar(val, assignment.pos, assignment.rot); } else { PlayerDeathHead val4 = DeathHeadRef.Invoke(val); PhysGrabObject obj = (((Object)(object)val4 != (Object)null) ? HeadGrabObjectRef.Invoke(val4) : null); if (obj != null) { obj.Teleport(assignment.pos, assignment.rot); } } WLog.Line("truck_rewarp", false, ("actor", ownerActorNr), ("distance", num.ToString("F1")), ("alive", assignment.wasAlive)); } } catch (Exception ex) { WLog.Line("truck_rewarp_error", false, ("err", ex.Message)); } } public void ResetWatchdog() { if (_assignments.Count != 0) { _assignments.Clear(); _rewarpGate.Reset(); WLog.Line("truck_rewarp_reset", false); } } private void Shuffle(SpawnPoint[] array) { for (int num = array.Length - 1; num > 0; num--) { int num2 = _rng.Next(num + 1); SpawnPoint val = array[num]; array[num] = array[num2]; array[num2] = val; } } } public static class TruckZone { public static bool IsNearTruck(Vector3 pos, float radiusMeters) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0062: Unknown result type (might be due to invalid IL or missing references) SpawnPoint[] array = TruckWarper.ResolveTruckSpawnPoints(); if (array == null || array.Length == 0) { WLog.Line("truck_zone_no_spawn", false, ("radiusM", radiusMeters)); return false; } float num = radiusMeters * radiusMeters; SpawnPoint[] array2 = array; foreach (SpawnPoint val in array2) { if (!((Object)(object)val == (Object)null)) { Vector3 val2 = ((Component)val).transform.position - pos; if (((Vector3)(ref val2)).sqrMagnitude <= num) { return true; } } } return false; } } internal static class VisionProbe { private const float ProbeHeight = 1.2f; private static int _obstructMask = -1; public static bool BodyToBodyClear(Vector3 fromBodyPos, Vector3 toBodyPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: 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) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) Vector3 val = fromBodyPos + Vector3.up * 1.2f; Vector3 val2 = toBodyPos + Vector3.up * 1.2f - val; float magnitude = ((Vector3)(ref val2)).magnitude; if (magnitude <= 0.001f) { return true; } if (_obstructMask == -1) { _obstructMask = LayerMask.GetMask(new string[2] { "Default", "StaticGrabObject" }); } return !Physics.Raycast(val, val2, magnitude, _obstructMask, (QueryTriggerInteraction)1); } } public sealed class VoiceMixerDriver { private struct PausedLowPassLogic { public AudioLowPassLogic Logic; public bool OriginalEnabled; } private struct FilterSet { public AudioLowPassFilter LowPass; public AudioEchoFilter Echo; public AudioReverbFilter Reverb; public bool IsAlive() { if ((Object)(object)LowPass != (Object)null && (Object)(object)Echo != (Object)null) { return (Object)(object)Reverb != (Object)null; } return false; } } private static FieldRef s_voiceChatRef; private static FieldRef s_audioSourceRef; private static FieldRef s_recorderRef; private static FieldRef> s_voiceChatsRef; private static bool s_fieldRefResolved; private const float SpatialKeepAliveSeconds = 0.1f; private const float DeadCueMuteKeepAliveSeconds = 0.1f; private readonly PlayerRegistry _registry; private VoicePlanKind _lastKind; private bool _deadCueMuteWasActive; private readonly HashSet _touched = new HashSet(); private readonly HashSet _frameSeenTargets = new HashSet(); private readonly Dictionary _filters = new Dictionary(); private readonly HashSet _filterEnabledActors = new HashSet(); private readonly Dictionary _pausedActorLowPassLogics = new Dictionary(); private bool _debugSelfEchoOn; private FilterSet _debugSelfEchoFilters; private AudioLowPassLogic _pausedLowPassLogic; private bool _pausedLowPassLogicOriginalEnabled; public bool IsDebugSelfEchoOn => _debugSelfEchoOn; public VoiceMixerDriver(PlayerRegistry registry) { _registry = registry ?? throw new ArgumentNullException("registry"); } public void Tick(VoicePlanKind kind, Func isEavesdropTarget, Func isDeadActor, bool deadCueMuteActive) { try { if (ResolveFieldRef()) { if (_debugSelfEchoOn) { DebugSelfEchoKeepAlive(); } if (kind != _lastKind) { RestoreTouched(isDeadActor); WLog.Line("voice_plan", false, ("from", _lastKind), ("to", kind)); _lastKind = kind; } switch (kind) { case VoicePlanKind.ResultAll: ApplyResultAll(); break; case VoicePlanKind.Eavesdrop: ApplyEavesdrop(isEavesdropTarget, isDeadActor); break; } ApplyDeadCueMute(deadCueMuteActive, kind, isEavesdropTarget, isDeadActor); } } catch (Exception ex) { WLog.Line("voice_tick_error", false, ("kind", kind), ("err", ex.Message)); ForceRestore("tick_error"); } } public void ForceRestore(string reason) { int count = _touched.Count; if (_debugSelfEchoOn) { SetDebugSelfEcho(enabled: false); } try { if (ResolveFieldRef()) { RestoreTouched(null); return; } DisableAllFilters(reason); _touched.Clear(); } catch (Exception ex) { _touched.Clear(); _filterEnabledActors.Clear(); foreach (KeyValuePair pausedActorLowPassLogic in _pausedActorLowPassLogics) { if ((Object)(object)pausedActorLowPassLogic.Value.Logic != (Object)null) { ((Behaviour)pausedActorLowPassLogic.Value.Logic).enabled = pausedActorLowPassLogic.Value.OriginalEnabled; } } _pausedActorLowPassLogics.Clear(); WLog.Line("voice_restore_error", false, ("reason", reason), ("err", ex.Message)); } finally { _lastKind = VoicePlanKind.None; _deadCueMuteWasActive = false; WLog.Line("voice_restore", false, ("reason", reason), ("touched", count)); } } public void SanitizeAtRoundStart() { try { if (!ResolveFieldRef()) { return; } RunManager instance = RunManager.instance; List list = (((Object)(object)instance != (Object)null) ? s_voiceChatsRef.Invoke(instance) : null); if (list == null) { WLog.Line("voice_sanitize", false, ("status", "no_voicechats")); ClearInternalTracking(); return; } int num = 0; int num2 = 0; foreach (PlayerVoiceChat item in list) { if ((Object)(object)item == (Object)null || (Object)(object)s_audioSourceRef.Invoke(item) == (Object)null) { continue; } if ((Object)(object)item == (Object)(object)PlayerVoiceChat.instance) { item.ToggleMixer(false, false); num++; continue; } item.ToggleMixer(false, false); GameObject val = ResolveVoiceGameObject(item); if ((Object)(object)val != (Object)null) { AudioLowPassLogic component = val.GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = true; } AudioEchoFilter component2 = val.GetComponent(); if ((Object)(object)component2 != (Object)null) { ((Behaviour)component2).enabled = false; } AudioReverbFilter component3 = val.GetComponent(); if ((Object)(object)component3 != (Object)null) { ((Behaviour)component3).enabled = false; } } num2++; } ClearInternalTracking(); WLog.Line("voice_sanitize", false, ("remote", num2), ("local", num), ("listed", list.Count)); } catch (Exception ex) { WLog.Line("voice_sanitize_error", false, ("err", ex.Message)); } } private void ClearInternalTracking() { _touched.Clear(); _filterEnabledActors.Clear(); _pausedActorLowPassLogics.Clear(); _filters.Clear(); _lastKind = VoicePlanKind.None; _deadCueMuteWasActive = false; } private static GameObject ResolveVoiceGameObject(PlayerVoiceChat vc) { AudioSource val = s_audioSourceRef.Invoke(vc); if (!((Object)(object)val != (Object)null)) { return ((Component)vc).gameObject; } return ((Component)val).gameObject; } public void SetDebugSelfEcho(bool enabled) { try { if (!ResolveFieldRef()) { WLog.Line("voice_debug_echo_rejected", false, ("enabled", enabled ? 1 : 0), ("reason", "fieldref_failed")); return; } PlayerVoiceChat instance = PlayerVoiceChat.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("voice_debug_echo_rejected", false, ("enabled", enabled ? 1 : 0), ("reason", "no_local_voice")); return; } Recorder val = s_recorderRef.Invoke(instance); AudioSource val2 = s_audioSourceRef.Invoke(instance); GameObject go = (((Object)(object)val2 != (Object)null) ? ((Component)val2).gameObject : ((Component)instance).gameObject); if (enabled) { PauseVanillaLowPassLogic(go); EnsureDebugSelfEchoFilters(go); SetDebugSelfEchoFiltersEnabled(enabled: true); if ((Object)(object)val != (Object)null) { val.DebugEchoMode = true; val.TransmitEnabled = true; } if ((Object)(object)val2 != (Object)null) { val2.volume = EavesdropVolume(); } _debugSelfEchoOn = true; WLog.Line("voice_debug_echo", false, ("enabled", 1), ("recorder", ((Object)(object)val != (Object)null) ? 1 : 0), ("volume_set", ((Object)(object)val2 != (Object)null) ? 1 : 0), ("paused_lowpasslogic", ((Object)(object)_pausedLowPassLogic != (Object)null) ? 1 : 0)); } else { SetDebugSelfEchoFiltersEnabled(enabled: false); if ((Object)(object)val != (Object)null) { val.DebugEchoMode = false; } if ((Object)(object)val2 != (Object)null) { val2.volume = 0f; } ResumeVanillaLowPassLogic(); _debugSelfEchoOn = false; WLog.Line("voice_debug_echo", false, ("enabled", 0), ("recorder", ((Object)(object)val != (Object)null) ? 1 : 0), ("volume_set", ((Object)(object)val2 != (Object)null) ? 1 : 0)); } } catch (Exception ex) { _debugSelfEchoOn = false; WLog.Line("voice_debug_echo_error", false, ("enabled", enabled ? 1 : 0), ("err", ex.Message)); } } private void DebugSelfEchoKeepAlive() { PlayerVoiceChat instance = PlayerVoiceChat.instance; if ((Object)(object)instance == (Object)null) { return; } Recorder val = s_recorderRef.Invoke(instance); AudioSource val2 = s_audioSourceRef.Invoke(instance); if ((Object)(object)val != (Object)null && !val.TransmitEnabled) { val.TransmitEnabled = true; } if ((Object)(object)val2 != (Object)null) { float num = EavesdropVolume(); if (val2.volume != num) { val2.volume = num; } } } private void EnsureDebugSelfEchoFilters(GameObject go) { if (_debugSelfEchoFilters.IsAlive()) { ApplyFilterParams(_debugSelfEchoFilters); } else if (!((Object)(object)go == (Object)null)) { AudioLowPassFilter val = go.GetComponent(); if ((Object)(object)val == (Object)null) { val = go.AddComponent(); } AudioEchoFilter val2 = go.GetComponent(); if ((Object)(object)val2 == (Object)null) { val2 = go.AddComponent(); } AudioReverbFilter val3 = go.GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = go.AddComponent(); } ((Behaviour)val2).enabled = false; ((Behaviour)val3).enabled = false; _debugSelfEchoFilters = new FilterSet { LowPass = val, Echo = val2, Reverb = val3 }; ApplyFilterParams(_debugSelfEchoFilters); WLog.Line("voice_debug_echo_filter_ensure", false, ("lowpass_existed", ((Object)(object)val != (Object)null && (!((Object)(object)val2 != (Object)null) || ((Behaviour)val2).enabled)) ? 1 : 0)); } } private void PauseVanillaLowPassLogic(GameObject go) { if (!((Object)(object)go == (Object)null) && !((Object)(object)_pausedLowPassLogic != (Object)null)) { AudioLowPassLogic component = go.GetComponent(); if (!((Object)(object)component == (Object)null)) { _pausedLowPassLogic = component; _pausedLowPassLogicOriginalEnabled = ((Behaviour)component).enabled; ((Behaviour)component).enabled = false; } } } private void ResumeVanillaLowPassLogic() { if ((Object)(object)_pausedLowPassLogic != (Object)null) { ((Behaviour)_pausedLowPassLogic).enabled = _pausedLowPassLogicOriginalEnabled; } _pausedLowPassLogic = null; _pausedLowPassLogicOriginalEnabled = false; } private void SetDebugSelfEchoFiltersEnabled(bool enabled) { if (_debugSelfEchoFilters.IsAlive()) { ((Behaviour)_debugSelfEchoFilters.Echo).enabled = enabled; ((Behaviour)_debugSelfEchoFilters.Reverb).enabled = enabled; } } private void ApplyResultAll() { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return; } foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } int num = _registry.ResolveActor(player); if (num > 0) { PlayerVoiceChat val = s_voiceChatRef.Invoke(player); if (!((Object)(object)val == (Object)null)) { val.ToggleMixer(true, false); _touched.Add(num); } } } } private void ApplyEavesdrop(Func isEavesdropTarget, Func isDeadActor) { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return; } _frameSeenTargets.Clear(); foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } int num = _registry.ResolveActor(player); if (num <= 0) { continue; } PlayerVoiceChat val = s_voiceChatRef.Invoke(player); if ((Object)(object)val == (Object)null) { continue; } bool flag = isDeadActor?.Invoke(num) ?? false; if (flag && isEavesdropTarget != null && isEavesdropTarget(num)) { _frameSeenTargets.Add(num); _touched.Add(num); AudioSource val2 = s_audioSourceRef.Invoke(val); if ((Object)(object)val2 != (Object)null && (Object)(object)val2.outputAudioMixerGroup != (Object)(object)val.mixerMicrophoneSound) { val.ToggleMixer(false, false); } EnableFilters(num, val); if ((Object)(object)val2 != (Object)null) { val2.volume = EavesdropVolume(); } val.SpatialDisable(0.1f); } else if (_touched.Contains(num)) { val.ToggleMixer(flag, false); DisableFilters(num, "non_target"); _touched.Remove(num); } } if (_touched.Count > _frameSeenTargets.Count) { _touched.RemoveWhere((int a) => !_frameSeenTargets.Contains(a)); } } private void ApplyDeadCueMute(bool active, VoicePlanKind kind, Func isEavesdropTarget, Func isDeadActor) { if (!active) { if (_deadCueMuteWasActive) { _deadCueMuteWasActive = false; WLog.Line("voice_deadcue_mute", false, ("active", 0)); } return; } GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return; } int num = 0; foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } int num2 = _registry.ResolveActor(player); if (num2 <= 0) { continue; } bool targetDead = isDeadActor?.Invoke(num2) ?? false; bool targetEavesdropAudible = kind == VoicePlanKind.Eavesdrop && isEavesdropTarget != null && isEavesdropTarget(num2); if (VoiceRules.IsDeadCueMuteTarget(targetDead, targetEavesdropAudible)) { PlayerVoiceChat val = s_voiceChatRef.Invoke(player); if (!((Object)(object)val == (Object)null)) { val.OverrideMute(0.1f); num++; } } } if (!_deadCueMuteWasActive) { _deadCueMuteWasActive = true; WLog.Line("voice_deadcue_mute", false, ("active", 1), ("muted", num)); } } private static float EavesdropVolume() { GameConfig gameConfig = Plugin.GameConfig; if (gameConfig == null) { return 0.05f; } return Mathf.Clamp01(gameConfig.NecroVoiceVolume); } private void RestoreTouched(Func isDeadActor) { if (_touched.Count == 0 && _filterEnabledActors.Count == 0) { return; } GameDirector instance = GameDirector.instance; if ((Object)(object)instance != (Object)null && instance.PlayerList != null) { foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } int num = _registry.ResolveActor(player); if (num > 0 && _touched.Contains(num)) { PlayerVoiceChat val = s_voiceChatRef.Invoke(player); if (!((Object)(object)val == (Object)null)) { bool flag = isDeadActor?.Invoke(num) ?? _registry.IsDeadSet(player); val.ToggleMixer(flag, false); DisableFilters(num, "restore"); } } } } _touched.Clear(); DisableAllFilters("restore_stale"); } private void EnsureFilters(int actor, PlayerVoiceChat vc) { if (_filters.TryGetValue(actor, out var value) && value.IsAlive()) { ApplyFilterParams(value); return; } AudioSource val = s_audioSourceRef.Invoke(vc); GameObject val2 = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : ((Component)vc).gameObject); if (!((Object)(object)val2 == (Object)null)) { AudioLowPassFilter val3 = val2.GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = val2.AddComponent(); } AudioEchoFilter val4 = val2.GetComponent(); if ((Object)(object)val4 == (Object)null) { val4 = val2.AddComponent(); } AudioReverbFilter val5 = val2.GetComponent(); if ((Object)(object)val5 == (Object)null) { val5 = val2.AddComponent(); } ((Behaviour)val4).enabled = false; ((Behaviour)val5).enabled = false; value = new FilterSet { LowPass = val3, Echo = val4, Reverb = val5 }; _filters[actor] = value; ApplyFilterParams(value); WLog.Line("voice_filter_ensure", false, ("actor", actor)); } } private void PauseActorLowPassLogic(int actor, GameObject go) { if (!((Object)(object)go == (Object)null) && !_pausedActorLowPassLogics.ContainsKey(actor)) { AudioLowPassLogic component = go.GetComponent(); if (!((Object)(object)component == (Object)null)) { _pausedActorLowPassLogics[actor] = new PausedLowPassLogic { Logic = component, OriginalEnabled = ((Behaviour)component).enabled }; ((Behaviour)component).enabled = false; } } } private void ResumeActorLowPassLogic(int actor) { if (_pausedActorLowPassLogics.TryGetValue(actor, out var value)) { if ((Object)(object)value.Logic != (Object)null) { ((Behaviour)value.Logic).enabled = value.OriginalEnabled; } _pausedActorLowPassLogics.Remove(actor); } } private static void ApplyFilterParams(FilterSet set) { if (set.IsAlive()) { GameConfig gameConfig = Plugin.GameConfig; if (gameConfig != null) { set.LowPass.cutoffFrequency = gameConfig.NecroVoiceLowPassCutoffHz; set.Echo.delay = gameConfig.NecroVoiceEchoDelayMs; set.Echo.decayRatio = gameConfig.NecroVoiceEchoDecay; set.Echo.wetMix = gameConfig.NecroVoiceEchoDecay; set.Echo.dryMix = 1f; set.Reverb.reverbPreset = (AudioReverbPreset)27; set.Reverb.dryLevel = 0f; set.Reverb.room = gameConfig.NecroVoiceReverbRoom; set.Reverb.roomHF = gameConfig.NecroVoiceReverbRoomHF; set.Reverb.decayTime = gameConfig.NecroVoiceReverbDecayTime; set.Reverb.decayHFRatio = gameConfig.NecroVoiceReverbDecayHFRatio; set.Reverb.reflectionsLevel = gameConfig.NecroVoiceReverbReflections; set.Reverb.reflectionsDelay = gameConfig.NecroVoiceReverbReflectionsDelay; set.Reverb.reverbLevel = gameConfig.NecroVoiceReverbLevel; set.Reverb.reverbDelay = gameConfig.NecroVoiceReverbDelay; set.Reverb.diffusion = gameConfig.NecroVoiceReverbDiffusion; set.Reverb.density = gameConfig.NecroVoiceReverbDensity; set.Reverb.hfReference = gameConfig.NecroVoiceReverbHFReference; } } } private void EnableFilters(int actor, PlayerVoiceChat vc) { EnsureFilters(actor, vc); if (_filters.TryGetValue(actor, out var value) && value.IsAlive()) { AudioSource val = s_audioSourceRef.Invoke(vc); GameObject go = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : ((Component)vc).gameObject); PauseActorLowPassLogic(actor, go); ((Behaviour)value.Echo).enabled = true; ((Behaviour)value.Reverb).enabled = true; if (_filterEnabledActors.Add(actor)) { WLog.Line("voice_filter_apply", false, ("actor", actor)); } } } private void DisableFilters(int actor, string reason) { if (_filters.TryGetValue(actor, out var value) && value.IsAlive()) { ((Behaviour)value.Echo).enabled = false; ((Behaviour)value.Reverb).enabled = false; } ResumeActorLowPassLogic(actor); if (_filterEnabledActors.Remove(actor)) { WLog.Line("voice_filter_disable", false, ("actor", actor), ("reason", reason)); } } private void DisableAllFilters(string reason) { if (_filterEnabledActors.Count != 0) { int[] array = new int[_filterEnabledActors.Count]; _filterEnabledActors.CopyTo(array); int[] array2 = array; foreach (int actor in array2) { DisableFilters(actor, reason); } } } private static bool ResolveFieldRef() { if (s_fieldRefResolved) { return true; } s_voiceChatRef = GameRefs.PlayerAvatar_voiceChat; s_audioSourceRef = GameRefs.PlayerVoiceChat_audioSource; s_recorderRef = GameRefs.PlayerVoiceChat_recorder; s_voiceChatsRef = GameRefs.RunManager_voiceChats; s_fieldRefResolved = true; return true; } } public sealed class WerewolfDirector : MonoBehaviour { private readonly BombClientState _bomberClient = new BombClientState(); private ProximityGauge _bomberProximity; private ProximityGauge _selfDefenseProximity; private float _bomberProximityFullSec; private bool _warningShown; private readonly BomberStatusPanel _bomberHud = new BomberStatusPanel(); private readonly BombIconPresenter _bombIconPresenter = new BombIconPresenter(); private readonly BombWarningPresenter _bombWarningPresenter = new BombWarningPresenter(); private float _bomberLastTickTime; private bool _bomberHasPlantedThisRound; private long _chatDebugDueUnixMs = -1L; private string _chatDebugPendingText; private bool _inputGateProbe; private bool _inputGateLastFree; private bool _chatDebugAvatarFallback; private CheckmateSequence _checkmate; private bool _checkmateScanPending; private long _checkmateNextScanUnixMs; private readonly CheckmateRevealPanel _checkmateReveal = new CheckmateRevealPanel(); private Coroutine _checkmateRevealCoroutine; private bool _checkmateVoiceOpen; private const long CheckmateScanIntervalMs = 500L; private long _checkmateLineNextSyncUnixMs; private const long CheckmateLineSyncIntervalMs = 5000L; private readonly RoomState _roomState = new RoomState(); private readonly LifecycleGate _lifecycleGate = new LifecycleGate(); private readonly AutoStartWaitGate _autoStartWait = new AutoStartWaitGate(); private readonly ResultSequence _resultSequence = new ResultSequence(); private GameSession _session; private MeetingSession _meeting; private RolesSession _roles; private BombSession _bomber; private INetBus _bus; private readonly List _pendingBots = new List(); private readonly Dictionary _pendingForcedRoles = new Dictionary(); private readonly EnemyIgnoreRoster _enemyIgnoreRoster = new EnemyIgnoreRoster(); private readonly BeaconEffect _beaconEffect = new BeaconEffect(); private readonly PerkEffects _perkEffects = new PerkEffects(); private readonly MeetingGaugePanel _gaugePanel = new MeetingGaugePanel(); private readonly MeetingGaugePanel _playGaugePanel = new MeetingGaugePanel("WW_PlayGaugePanel", new Vector2(1560f, 200f), new Vector2(-25f, 50f), new Vector2(1f, 0f), new Vector2(1f, 0f), 0.45f, 2.5f); private readonly HudPanel _hudPanel = new HudPanel(); private readonly HudModel _hudModel = new HudModel(); private readonly ToastPanel _toastPanel = new ToastPanel(); private ToastQueue _toastQueue; private readonly TutorialPresenter _tutorialPresenter = new TutorialPresenter(); private readonly CursorMirror _cursorMirror = new CursorMirror(); private readonly RoleRevealCinematic _revealCinematic = new RoleRevealCinematic(); private readonly BlackCatAwakeningToast _catAwakenToast = new BlackCatAwakeningToast(); private Coroutine _catAwakenToastCoroutine; private readonly WolfStatusPanel _wolfStatusPanel = new WolfStatusPanel(); private readonly WolfStatusModel _wolfStatusModel = new WolfStatusModel(); private readonly CorpseReportHudPanel _corpseReportHud = new CorpseReportHudPanel(); private readonly ValuableRecordHudPanel _valuableRecordHud = new ValuableRecordHudPanel(); private readonly ValuableRecordHold _valuableRecordHold = new ValuableRecordHold(); private readonly DeathRevealPanel _deathReveal = new DeathRevealPanel(); private Coroutine _deathRevealCoroutine; private bool _deathRevealPending; private int _pendingBeaconAudit = -1; private readonly MeetingChatLog _chatLog = new MeetingChatLog(); private bool _chatVoteBaselinePending; private readonly MeetingChatPanel _chatPanel = new MeetingChatPanel(); private readonly List _chatRecapDeaths = new List(); private int _chatRecapBeaconUses = -1; private bool _pendingMeetingTutorial; private readonly ConveneCountdown _conveneCountdown = new ConveneCountdown(); private Coroutine _conveneTweenCoroutine; private readonly SfxPlayer _sfxPlayer = new SfxPlayer(); private readonly ResultScreen _resultScreen = new ResultScreen(); private readonly ResultCountdown _resultCountdown = new ResultCountdown(); private int _roundGameOverAutoReturnSec; private int _lastResultCountdownSecond = -1; private bool _revealStarted; private bool _awakeningRevealStarted; private Coroutine _revealCoroutine; private CurseTargetSource _curseSource; private Role? _localRole; private int[] _knownWerewolves; private byte[] _knownTeammateRoles; private GamePhase _clientPhase; private long _clientRoundEndUnixMs; private int _clientWerewolfCount; private readonly Dictionary _deathMirror = new Dictionary(); private Dictionary _displayNameCache; private string _lastPublishedBlob; private long _lobbyTickAtMs; private readonly LobbySettingsPanel _lobbySettingsPanel = new LobbySettingsPanel(); private string _lastPanelBlob; private bool _lastPanelModeEnabled; private bool _lobbyPanelUserHidden; private string _debugInjectedBlob; private string _lobbyBlobMirror; private bool _clientMinimapHideEnabled; private bool _clientCatPossible; private bool _clientDebugSession; private ValuableMapMode _clientValuableMapMode = ValuableMapMode.MeetingSync; private NecroVoiceMode _clientNecroVoiceMode = NecroVoiceMode.NonWerewolfDead; private int? _clientExtraJumpCount; private int? _clientConveneSuppressStartSec; private int? _clientConveneSuppressAfterSec; private int? _clientHealIntervalSec; private int[] _clientBombPack; private int[] _clientShamanPack; private VoiceMixerDriver _voiceDriver; private Func _voiceIsEavesdropTarget; private Func _voiceIsDeadActor; private readonly MeetingClientState _meetingClient = new MeetingClientState(); private MeetingButton _meetingButton; private long _nextConveneHoldHintUnixMs; private TruckWarper _truckWarper; private MovementFreezer _movementFreezer; private EnemyFreezer _enemyFreezer; private WerewolfUIManager _uiManager; private VotePanel _votePanel; private readonly MeetingMapOverlay _meetingMapOverlay = new MeetingMapOverlay(); private readonly ManualOverlay _manualOverlay = new ManualOverlay(); private IClientPanel[] _roundPanels; private long _clientWarpUnixMs; private bool _warpExecuted; private bool _meetingUiActive; private bool _votePendulumPlayed; private int _executionSfxWaitTicks = -1; private const int ExecutionSfxCurseWaitTicks = 3; private long _gameStartUnixMsClient; private long _lastMeetingEndUnixMsClient; private int _clientRevealDelaySec; private readonly CatAwakenToastGate _catAwakenGate = new CatAwakenToastGate(); private Vector3? _warpVerifyTarget; private long _warpVerifyDeadlineMs; private bool _resetArmed; private bool _cosmeticGrantApplied; private int[] _pendingCoinCounts; private readonly LobbyStartAttempt _lobbyStartAttempt = new LobbyStartAttempt(); private readonly LobbyStartWarning _lobbyStartWarning = new LobbyStartWarning(); private MenuPageLobby _lobbyStartHeldPage; private readonly ConveneHoldGauge _conveneHoldGauge = new ConveneHoldGauge(); private const float CorpseReportDistance = 3f; private readonly ModManifestCollector _modManifestCollector = new ModManifestCollector(); private readonly ModIntegrityHostState _modIntegrityHostState = new ModIntegrityHostState(); private readonly ModIntegrityClientState _modIntegrityClientState = new ModIntegrityClientState(); private readonly Dictionary _modManifestAssemblies = new Dictionary(); private readonly ModIntegrityHeader _modIntegrityHeader = new ModIntegrityHeader(); private readonly ModIntegrityPanel _modIntegrityPanel = new ModIntegrityPanel(); private bool _modIntegritySessionActive; private int _modIntegrityHostActor; private int _modIntegrityEpochSeed; private long _modIntegrityRosterTickAtMs; private ModManifestRequestMessage _modIntegrityPendingRequest; private int _modIntegrityUiEpoch; private int _modIntegrityUiRevision = -1; private bool _modIntegrityHostSignal; private long _busWaitLogAtMs; private readonly ResultDigest _matchDigest = new ResultDigest(); private IReadOnlyList _clientDigestEntries; private ShamanSense _shamanSense; private float _shamanGazeFullSecSnapshot; private float _shamanCooldownSecSnapshot; private readonly ShamanSensePanel _shamanPanel = new ShamanSensePanel(); private const float StormLoopVolumeWeak = 0.3f; private const float StormLoopVolumeMedium = 0.6f; private const float StormLoopVolumeStrong = 1f; private const int ShamanSobVariantCount = 11; private int _lastShamanSobIndex = -1; private const float ShamanStillSpeedMps = 0.5f; private readonly GazeConeLock _shamanGazeLock = new GazeConeLock(); private bool _shamanStartCooldownArmed; private const float ShamanDripVolume = 0.7f; private Vector3? _shamanLastLocalPos; private float _shamanLastTickTime; private readonly StartHoldGate _startHold = new StartHoldGate(); private readonly StartHoldOverlay _startHoldOverlay = new StartHoldOverlay(); private const float StartHoldKeepAliveSeconds = 0.1f; private bool _wasRoleRevealVisible; private int _prevBeaconCharges; public int DebugLocalActor => LocalActor; public static WerewolfDirector Instance { get; private set; } public PlayerRegistry Registry { get; private set; } public bool IsHostSessionActive => _session != null; public GamePhase HostPhase => _session?.Phase ?? GamePhase.Lobby; public RolesClientState RolesClient { get; } = new RolesClientState(); public Role? LocalRoleClient => _localRole; public GamePhase ClientPhase { get { if (_session == null) { return _clientPhase; } return _session.Phase; } } public bool IsRoundActiveClient => CombatRules.IsMatchLive(ClientPhase); public bool ClientMinimapHideEnabled => _clientMinimapHideEnabled; public ValuableMapMode ClientValuableMapMode => _clientValuableMapMode; public NecroVoiceMode ClientNecroVoiceMode => _clientNecroVoiceMode; public int ClientExtraJumpCount { get { int? clientExtraJumpCount = _clientExtraJumpCount; if (!clientExtraJumpCount.HasValue) { if (Plugin.GameConfig == null) { return 0; } return Plugin.GameConfig.ExtraJumpCount; } return clientExtraJumpCount.GetValueOrDefault(); } } public int ClientConveneSuppressStartSec { get { int? clientConveneSuppressStartSec = _clientConveneSuppressStartSec; if (!clientConveneSuppressStartSec.HasValue) { if (Plugin.GameConfig == null) { return 0; } return Plugin.GameConfig.ConveneSuppressStartSec; } return clientConveneSuppressStartSec.GetValueOrDefault(); } } public int ClientConveneSuppressAfterSec { get { int? clientConveneSuppressAfterSec = _clientConveneSuppressAfterSec; if (!clientConveneSuppressAfterSec.HasValue) { if (Plugin.GameConfig == null) { return 0; } return Plugin.GameConfig.ConveneSuppressAfterSec; } return clientConveneSuppressAfterSec.GetValueOrDefault(); } } public int ClientHealIntervalSec { get { int? clientHealIntervalSec = _clientHealIntervalSec; if (!clientHealIntervalSec.HasValue) { if (Plugin.GameConfig == null) { return 3; } return Plugin.GameConfig.HealIntervalSec; } return clientHealIntervalSec.GetValueOrDefault(); } } public bool ClientBomberPossible => ClientBombPackValue(0) != 0; public float ClientBomberProximityMeters => (float)ClientBombPackValue(1) / 100f; public float ClientBomberGaugeFullSec => (float)ClientBombPackValue(2) / 1000f; public int ClientBomberCooldownSec => ClientBombPackValue(3) / 1000; public int ClientBomberInitialCooldownSec => ClientBombPackValue(7) / 1000; public float ClientBomberBlastRadiusMeters => (float)ClientBombPackValue(4) / 100f; public int ClientBomberBlastPlayerDamage => ClientBombPackValue(5); public int ClientBomberBlastEnemyDamage => ClientBombPackValue(6); public float ClientShamanGazeFullSec => (float)ClientShamanPackValue(0) / 1000f; public float ClientShamanGhostCooldownSec => (float)ClientShamanPackValue(1) / 1000f; public float ClientShamanStormWeakMeters => (float)ClientShamanPackValue(2) / 100f; public float ClientShamanStormMediumMeters => (float)ClientShamanPackValue(3) / 100f; public float ClientShamanStormStrongMeters => (float)ClientShamanPackValue(4) / 100f; public int PendingBotCount => _pendingBots.Count; internal bool LocalIsNearUnannouncedCorpse { get; private set; } internal bool CorpseReportConsumedPress { get; private set; } private static bool MeetingChatLogEnabled { get { if (Plugin.Bindings != null) { return Plugin.Bindings.MeetingChatLog.Value; } return true; } } private int LocalActor { get { if (_bus == null) { return 1; } return _bus.LocalActorNumber; } } internal int ModIntegrityRevision => _modIntegrityClientState.Current?.Revision ?? (-1); internal bool LocalValuableDiscoverSuppressed => ValuableRecordGate.ShouldSuppressDiscover(_localRole, IsLocalAlive(), IsRoundActiveClient, RolesClient.ValuableRecordOn); private bool LocalIsVillagerTeam { get { if (_localRole.HasValue) { return RoleDistribution.TeamOf(_localRole.Value) == Team.Villagers; } return false; } } internal void ApplyBomberState(int targetActor, byte ammo, byte lastDenyByte, long plantReadyUnixMs, long detonateReadyUnixMs) { bool hasBomb = _bomberClient.HasBomb; int targetActor2 = _bomberClient.TargetActor; BomberStateSnapshot snapshot = new BomberStateSnapshot(targetActor, ammo, plantReadyUnixMs, detonateReadyUnixMs, (BombDenyReason)lastDenyByte); _bomberClient.ApplyState(snapshot); if (_localRole == Role.Bomber && snapshot.LastDeny == BombDenyReason.None && snapshot.TargetActor != -1 && (!hasBomb || targetActor2 != snapshot.TargetActor)) { _bomberHasPlantedThisRound = true; EnsurePanelBuilt(_bombIconPresenter); _bombIconPresenter.BeginPlantFlight(snapshot.TargetActor); MaybeShowTutorial(TutorialId.BombPlantedAsBomber); } if (snapshot.LastDeny != BombDenyReason.None) { string text = FormatBomberDenyToast(snapshot.LastDeny); if (!string.IsNullOrEmpty(text)) { PushRawToast(text); } _bomberClient.ConsumeLastDeny(); } WLog.Line("recv_bomberstate", true, ("target", targetActor), ("ammo", ammo), ("deny", snapshot.LastDeny)); } internal void ApplyBombDetonation(int targetActor, long detonateAtUnixMs) { _bomberClient.ApplyPendingDetonation(targetActor, detonateAtUnixMs); _warningShown = false; WLog.Line("recv_bombdetonation", true, ("target", targetActor), ("atUnixMs", detonateAtUnixMs)); } private void TickBomberClient(long now) { //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_03d9: Unknown result type (might be due to invalid IL or missing references) //IL_03de: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Unknown result type (might be due to invalid IL or missing references) try { if (!IsRoundActiveClient) { HideBomberUi(); _bomberLastTickTime = 0f; return; } float unscaledTime = Time.unscaledTime; float num = ((_bomberLastTickTime > 0f) ? (unscaledTime - _bomberLastTickTime) : 0f); _bomberLastTickTime = unscaledTime; if (num < 0f) { num = 0f; } if (num > 0.5f) { num = 0.5f; } EnsureBomberGauges(); bool flag = _localRole == Role.Bomber; bool clientBomberPossible = ClientBomberPossible; bool num2 = ClientPhase != GamePhase.Play; bool flag2 = !_bomberHasPlantedThisRound && (flag ? (now < _bomberClient.PlantReadyUnixMs) : (_gameStartUnixMsClient > 0 && now < _gameStartUnixMsClient + (long)ClientBomberInitialCooldownSec * 1000L)); bool flag3 = _lastMeetingEndUnixMsClient > 0 && now < _lastMeetingEndUnixMsClient + (long)ClientBomberCooldownSec * 1000L; bool suspend = num2 || flag2 || flag3; if (_meetingClient.MeetingActive && _meetingClient.WarpDone(now)) { _bomberProximity?.ResetAll(); _selfDefenseProximity?.ResetAll(); } if (clientBomberPossible) { BombExplosionDriver.WarmupOnce(); } int localActor = LocalActor; PlayerAvatar val = ResolveAvatar(localActor); Vector3? localPos = (((Object)(object)val != (Object)null) ? new Vector3?(((Component)val).transform.position) : ((Vector3?)null)); List players = (((Object)(object)GameDirector.instance != (Object)null) ? GameDirector.instance.PlayerList : null); float num3 = ClientBomberProximityMeters; if (num3 <= 0f) { num3 = 8f; } List<(int, Vector3)> list = null; if ((Object)(object)val != (Object)null && ((flag && _bomberProximity != null) || (clientBomberPossible && _selfDefenseProximity != null))) { list = CollectOtherBodies(players, localActor); } Dictionary dictionary = null; if (flag && _bomberProximity != null && list != null) { dictionary = new Dictionary(); foreach (var (num4, val2) in list) { if (IsDeadActorClient(num4)) { _bomberProximity.Remove(num4); continue; } bool within = Vector3.Distance(localPos.Value, val2) <= num3 && VisionProbe.BodyToBodyClear(localPos.Value, val2); _bomberProximity.Tick(num4, within, num, suspend); float num5 = _bomberProximity.Ratio(num4); if (num5 > 0f) { dictionary[num4] = num5; } } } if (!flag && clientBomberPossible && _selfDefenseProximity != null && list != null) { foreach (var (num6, val3) in list) { if (IsDeadActorClient(num6)) { _selfDefenseProximity.Remove(num6); continue; } bool within2 = Vector3.Distance(localPos.Value, val3) <= num3 && VisionProbe.BodyToBodyClear(localPos.Value, val3); _selfDefenseProximity.Tick(num6, within2, num, suspend); } if (_selfDefenseProximity.TryGetNotifyEdge(out var _)) { PushRawToast(Texts.Get(TextId.BomberProximityWarning), "sfx_bomb_proximity_warning"); if (LocalIsVillagerTeam) { MaybeShowTutorial(TutorialId.BomberProximityWarnedAsVillager); } } } if (flag && Plugin.Bindings != null && InputGate.KeysFree) { KeyCode value = Plugin.Bindings.BomberPlantKey.Value; KeyCode value2 = Plugin.Bindings.BomberDetonateKey.Value; if (Input.GetKeyDown(value)) { int excludedActor = (_bomberClient.HasBomb ? _bomberClient.TargetActor : (-1)); int num7 = FindNearestFullTarget(localPos, list, excludedActor); if (num7 == -1) { PushRawToast(Texts.Get(TextId.BomberDenyNoFullTarget)); } else { SendBomberRoleAction(3, num7); } } if (Input.GetKeyDown(value2)) { SendBomberRoleAction(4, 0); } } DrivePendingDetonation(now, localActor, players); DriveBomberUi(now, localActor, flag, dictionary, players); } catch (Exception ex) { WLog.Line("bomber_client_tick_error", false, ("err", ex.Message)); } } private void DrivePendingDetonation(long now, int localActor, IReadOnlyList players) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if (!_bomberClient.HasPendingDetonation) { return; } int pendingTargetActor = _bomberClient.PendingTargetActor; long pendingDetonateAtUnixMs = _bomberClient.PendingDetonateAtUnixMs; PlayerAvatar val = ResolveAvatar(pendingTargetActor); if (!_warningShown) { _warningShown = true; EnsurePanelBuilt(_bombWarningPresenter); bool localIsTarget = pendingTargetActor == localActor; float clientBomberBlastRadiusMeters = ClientBomberBlastRadiusMeters; Vector3? spatialFallbackPos = null; if ((Object)(object)val == (Object)null && FakeBodies.TryGetPosition(pendingTargetActor, out var pos)) { spatialFallbackPos = pos; } _bombWarningPresenter.Show(localIsTarget, val, clientBomberBlastRadiusMeters, spatialFallbackPos); } _bombWarningPresenter.Tick(); if (now >= pendingDetonateAtUnixMs) { PlayerAvatar val2 = val ?? ResolveAvatar(pendingTargetActor); bool localIsBomber = _localRole == Role.Bomber; int clientBomberBlastPlayerDamage = ClientBomberBlastPlayerDamage; int clientBomberBlastEnemyDamage = ClientBomberBlastEnemyDamage; float clientBomberBlastRadiusMeters2 = ClientBomberBlastRadiusMeters; Vector3 pos2; if ((Object)(object)val2 != (Object)null) { BombExplosionDriver.Detonate(val2, localIsBomber, clientBomberBlastPlayerDamage, clientBomberBlastEnemyDamage, clientBomberBlastRadiusMeters2); } else if (FakeBodies.TryGetPosition(pendingTargetActor, out pos2)) { BombExplosionDriver.DetonateAt(pos2 + Vector3.up * 0.5f, null, localIsBomber, clientBomberBlastPlayerDamage, clientBomberBlastEnemyDamage, clientBomberBlastRadiusMeters2); } else { WLog.Line("bomb_explode_skip", false, ("reason", "epicenter_unresolved"), ("target", pendingTargetActor)); } if (pendingTargetActor == localActor && LocalIsVillagerTeam) { MaybeShowTutorial(TutorialId.SelfBombExplodedAsVillager); } _bomberClient.ClearPendingDetonation(); _bombWarningPresenter.Hide(); _warningShown = false; } } private void DriveBomberUi(long now, int localActor, bool localIsBomber, Dictionary radialTargets, IReadOnlyList players) { //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) EnsurePanelBuilt(_bomberHud); bool flag = _meetingClient.MeetingActive && _meetingClient.WarpDone(now); if (localIsBomber && !flag) { long num = Math.Max(0L, _bomberClient.PlantReadyUnixMs - now); long num2 = (_bomberClient.HasBomb ? Math.Max(0L, _bomberClient.DetonateReadyUnixMs - now) : 0); int num3 = (int)((num + 999) / 1000); int detonateCooldownSec = (int)((num2 + 999) / 1000); bool phaseAllows = ClientPhase == GamePhase.Play; int excludedTarget = (_bomberClient.HasBomb ? _bomberClient.TargetActor : (-1)); bool hasPlantResource = _bomberClient.HasBomb || _bomberClient.Ammo > 0; float plantFraction = BomberHudModel.PlantFraction(phaseAllows, num3, hasPlantResource, radialTargets, excludedTarget); float detonateBrightFraction; if (!_bomberClient.HasBomb) { detonateBrightFraction = 0f; } else if (num2 <= 0) { detonateBrightFraction = 1f; } else { long num4 = (long)Math.Max(1, ClientBomberCooldownSec) * 1000L; detonateBrightFraction = 1f - Math.Min(1f, (float)num2 / (float)num4); } string plantKeyName = ((Plugin.Bindings != null) ? ((object)Plugin.Bindings.BomberPlantKey.Value/*cast due to .constrained prefix*/).ToString() : "?"); string detonateKeyName = ((Plugin.Bindings != null) ? ((object)Plugin.Bindings.BomberDetonateKey.Value/*cast due to .constrained prefix*/).ToString() : "?"); _bomberHud.Tick(visible: true, plantFraction, num3, _bomberClient.Ammo, detonateBrightFraction, detonateCooldownSec, plantKeyName, detonateKeyName); } else { _bomberHud.Tick(visible: false, 0f, 0, 0, 0f, 0, null, null); } EnsurePanelBuilt(_bombIconPresenter); HashSet hashSet = new HashSet(); if (localIsBomber && _bomberClient.HasBomb) { hashSet.Add(_bomberClient.TargetActor); } if (_bomberClient.HasPendingDetonation) { hashSet.Add(_bomberClient.PendingTargetActor); } bool flag2 = localIsBomber && radialTargets != null && radialTargets.Count > 0; _bombIconPresenter.Tick(flag2 || hashSet.Count > 0, flag2 ? radialTargets : null, hashSet, ResolveBodyWorldPos); } private Vector3? ResolveBodyWorldPos(int actor) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar val = ResolveAvatar(actor); if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform != (Object)null) { return ((Component)val).transform.position; } if (FakeBodies.TryGetPosition(actor, out var pos)) { return pos; } return null; } private int FindNearestFullTarget(Vector3? localPos, IReadOnlyList<(int actor, Vector3 pos)> bodies, int excludedActor) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (_bomberProximity == null || !localPos.HasValue || bodies == null) { return -1; } int result = -1; float num = float.MaxValue; foreach (var (num2, val) in bodies) { if (num2 != excludedActor && _bomberProximity.IsFull(num2)) { float num3 = Vector3.Distance(localPos.Value, val); if (num3 < num) { num = num3; result = num2; } } } return result; } private List<(int actor, Vector3 pos)> CollectOtherBodies(IReadOnlyList players, int localActor) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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) List<(int, Vector3)> list = new List<(int, Vector3)>(); if (players != null) { foreach (PlayerAvatar player in players) { if (!((Object)(object)player == (Object)null) && !((Object)(object)((Component)player).transform == (Object)null)) { int num = ((Registry != null) ? Registry.ResolveActor(player) : (-1)); if (num >= 0 && num != localActor) { list.Add((num, ((Component)player).transform.position)); } } } } if (FakeBodies.Any) { foreach (var (num2, item) in FakeBodies.Snapshot()) { if (num2 != localActor) { list.Add((num2, item)); } } } return list; } private void SendBomberRoleAction(byte subtype, int arg) { if (_bus == null) { WLog.Line("bomber_send_fail", true, ("subtype", subtype), ("reason", "no_bus")); } else { _bus.SendToMaster(174, new object[3] { subtype, arg, (byte)0 }); } } private void EnsureBomberGauges() { float num = ClientBomberGaugeFullSec; if (num <= 0f) { num = 20f; } if (_bomberProximity == null || Mathf.Abs(num - _bomberProximityFullSec) > 0.01f) { _bomberProximity = new ProximityGauge(num); _selfDefenseProximity = new ProximityGauge(num); _bomberProximityFullSec = num; } } private void HideBomberUi() { _bomberHud?.Hide(); _bombIconPresenter?.Hide(); if (_warningShown) { _bombWarningPresenter?.Hide(); _warningShown = false; } } private void ResetBomberClient() { _bomberClient.Reset(); _bomberProximity?.ResetAll(); _selfDefenseProximity?.ResetAll(); _bomberLastTickTime = 0f; _bomberHasPlantedThisRound = false; _warningShown = false; HideBomberUi(); BombExplosionDriver.ResetWarmup(); } private static string FormatBomberDenyToast(BombDenyReason reason) { return reason switch { BombDenyReason.NoAmmo => Texts.Get(TextId.BomberDenyNoAmmo), BombDenyReason.NoFullTarget => Texts.Get(TextId.BomberDenyNoFullTarget), BombDenyReason.PlantCooldown => Texts.Get(TextId.BomberDenyPlantCooldown), BombDenyReason.DetonateCooldown => Texts.Get(TextId.BomberDenyDetonateCooldown), BombDenyReason.NoBomb => Texts.Get(TextId.BomberDenyNoBomb), BombDenyReason.MeetingLocked => Texts.Get(TextId.BomberDenyMeetingLocked), BombDenyReason.TruckZone => Texts.Get(TextId.BomberDenyTruckZone), BombDenyReason.TargetDead => Texts.Get(TextId.BomberDudTargetDead), _ => null, }; } private void PushRawToast(string message, string clipKey = "sfx_toast") { if (!string.IsNullOrEmpty(message)) { if (_toastQueue == null) { int durationSec = ((Plugin.GameConfig != null && Plugin.GameConfig.ToastDurationSec > 0) ? Plugin.GameConfig.ToastDurationSec : 9); _toastQueue = new ToastQueue(durationSec); } EnsureRolesUiBuilt(); _toastQueue.Push(message, NowUnixMs()); EnsureSfxBuilt(); _sfxPlayer.Play(clipKey); WLog.Line("toast_push_raw", false, ("kind", "bomber")); } } public bool DebugInjectChat(int actor, string name, string text) { if (!MeetingChatLogEnabled) { WLog.Line("chat_debug_inject", false, ("result", "disabled_by_cfg")); return false; } if (ClientPhase != GamePhase.Meeting) { WLog.Line("chat_debug_inject", false, ("result", "not_meeting"), ("phase", ClientPhase)); return false; } if (!ShouldShowDeadTextClient(actor)) { WLog.Line("chat_debug_inject", false, ("result", "blocked_dead_text"), ("actor", actor), ("localAlive", IsLocalAlive())); return false; } ChatSpeaker chatSpeaker = ((_meetingClient.GetRowStatus(actor) != RowStatus.Alive) ? ChatSpeaker.Dead : ChatSpeaker.Alive); bool flag = _chatLog.Append(actor, name ?? ResolveDisplayName(actor), text, chatSpeaker); WLog.Line("chat_debug_inject", false, ("result", flag ? "logged" : "rejected_empty"), ("actor", actor), ("speaker", chatSpeaker)); return flag; } public bool DebugInjectVoteLine(int actor) { if (!MeetingChatLogEnabled || ClientPhase != GamePhase.Meeting) { return false; } bool flag = _chatLog.AppendVote(actor, ResolveDisplayName(actor), Texts.Get(TextId.ChatLogVoted)); WLog.Line("chat_debug_vote_line", false, ("actor", actor), ("added", flag)); return flag; } public void DebugArmVoteBaseline() { _chatVoteBaselinePending = true; WLog.Line("chat_debug_vote_baseline_armed", false); } public int DebugSpamChat(int count) { int[] array = BuildDebugSpeakerActors(); int num = 0; for (int i = 0; i < count; i++) { int actor = array[i / 2 % array.Length]; if (DebugInjectChat(actor, null, $"テスト発言 {i + 1}")) { num++; } } WLog.Line("chat_debug_spam", false, ("requested", count), ("added", num), ("total", _chatLog.Count)); return num; } private int[] BuildDebugSpeakerActors() { List list = new List { LocalActor }; if (_session != null) { foreach (WPlayer player in _session.Players) { if (player.ActorNumber != LocalActor) { list.Add(player.ActorNumber); } } } return list.ToArray(); } public void DebugScheduleChat(long delayMs, string text) { _chatDebugDueUnixMs = NowUnixMs() + Math.Max(0L, delayMs); _chatDebugPendingText = text; WLog.Line("chat_debug_scheduled", false, ("delayMs", delayMs)); } public bool DebugToggleInputGateProbe() { _inputGateProbe = !_inputGateProbe; _inputGateLastFree = InputGate.KeysFree; WLog.Line("chat_debug_input_gate_probe", false, ("state", _inputGateProbe ? "on" : "off"), ("keysFree", _inputGateLastFree)); return _inputGateProbe; } public bool DebugToggleChatAvatarFallback() { _chatDebugAvatarFallback = !_chatDebugAvatarFallback; if (_chatPanel.Exists) { _chatPanel.ResetView(); } WLog.Line("chat_debug_avatar_fallback", false, ("state", _chatDebugAvatarFallback ? "on" : "off")); return _chatDebugAvatarFallback; } private PlayerAvatar ResolveAvatarForChatDebug(int actor) { PlayerAvatar val = ResolveAvatar(actor); if (!((Object)(object)val != (Object)null)) { return PlayerAvatar.instance; } return val; } public void DebugClearChat() { _chatLog.Clear(); if (_chatPanel.Exists) { _chatPanel.ResetView(); } WLog.Line("chat_debug_clear", false); } public void DebugDumpChatState() { WLog.Line("chat_debug_state", false, ("enabled", MeetingChatLogEnabled), ("phase", ClientPhase), ("count", _chatLog.Count), ("appended", _chatLog.AppendedTotal), ("dropped", _chatLog.DroppedTotal), ("panelBuilt", _chatPanel.Exists), ("panelOpen", _chatPanel.Exists && _chatPanel.IsOpen), ("localAlive", IsLocalAlive()), ("keysFree", InputGate.KeysFree)); } private void TickChatDebug(long now) { if (_chatDebugDueUnixMs >= 0 && now >= _chatDebugDueUnixMs) { long chatDebugDueUnixMs = _chatDebugDueUnixMs; string chatDebugPendingText = _chatDebugPendingText; _chatDebugDueUnixMs = -1L; _chatDebugPendingText = null; bool flag = DebugInjectChat(LocalActor, null, chatDebugPendingText); WLog.Line("chat_debug_scheduled_fired", false, ("phase", ClientPhase), ("logged", flag), ("lateMs", now - chatDebugDueUnixMs)); } if (_inputGateProbe) { bool keysFree = InputGate.KeysFree; if (keysFree != _inputGateLastFree) { _inputGateLastFree = keysFree; WLog.Line("chat_debug_input_gate", false, ("keysFree", keysFree)); } } } public void DebugRolesGauge(int pct) { if (GuardRolesDebug("gauge")) { _roles.DebugAddGaugePct(pct); WLog.Line("cmd_gauge", true, ("pct", pct), ("permille", _roles.Gauge.DisplayPermille), ("unlocked", _roles.Gauge.UnlockedFlags)); } } public void DebugRolesBeaconCharge(int count) { if (GuardRolesDebug("beacon")) { _roles.DebugChargeBeacon(count); WLog.Line("cmd_beacon", true, ("op", "charge"), ("count", count)); } } public void DebugRolesBeaconUse() { if (GuardRolesDebug("beacon")) { WLog.Line("cmd_beacon", true, ("op", "use"), ("actor", LocalActor)); _roles.DebugUseBeacon(LocalActor, NowUnixMs()); } } public void DebugRolesPerkUnlock(PerkId perk) { if (GuardRolesDebug("perk")) { _roles.DebugUnlockPerk(perk); WLog.Line("cmd_perk", true, ("perk", perk), ("unlocked", _roles.Gauge.UnlockedFlags)); } } public void DebugRolesInformant() { if (GuardRolesDebug("informant")) { _roles.DebugForceInformant(); WLog.Line("cmd_informant", true, ("established", _roles.InformantEstablished)); } } public void DebugRolesCurse(int? actorNumber) { if (!GuardRolesDebug("curse")) { return; } int num = 0; bool flag = actorNumber.HasValue; if (flag) { num = actorNumber.Value; } else { foreach (WPlayer player in _session.Players) { if (player.Role == Role.BlackCat && player.Alive) { num = player.ActorNumber; flag = true; break; } } } if (!flag) { WLog.Line("cmd_rejected", false, ("name", "curse"), ("reason", "no_alive_blackcat")); } else { ExecuteVotedPlayer(num); WLog.Line("cmd_curse", true, ("target", num), ("curseActive", _roles.ActiveCurse != null && !_roles.ActiveCurse.Resolved)); } } public void DebugBomberFillGauge() { if (!SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("cmd_rejected", false, ("name", "bomb"), ("reason", "not_host")); return; } if (_bomberProximity == null) { WLog.Line("cmd_rejected", false, ("name", "bomb"), ("reason", "no_proximity")); return; } int localActor = LocalActor; int num = 0; IReadOnlyList readOnlyList = ((_session != null) ? _session.Players : null); if (readOnlyList != null) { foreach (WPlayer item in readOnlyList) { if (item.Alive && item.ActorNumber != localActor) { _bomberProximity.DebugSetFull(item.ActorNumber); num++; } } } WLog.Line("cmd_bomb_gauge", false, ("filled", num)); } public void DebugBomberPlant(int targetActor) { if (GuardBomberDebug("plant")) { HandleBombPlant(_bomber.BomberActor, targetActor, NowUnixMs()); } } public void DebugBomberDetonate() { if (GuardBomberDebug("detonate")) { HandleBombDetonate(_bomber.BomberActor, NowUnixMs()); } } public void DebugBomberGrantAmmo(int n) { if (GuardBomberDebug("ammo")) { _bomber.DebugGrantAmmo(n); SendBomberStateIfDirty(); WLog.Line("cmd_bomb_ammo", true, ("granted", n), ("ammo", _bomber.Ammo)); } } private bool GuardBomberDebug(string sub) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("cmd_rejected", false, ("name", "bomb"), ("sub", sub), ("reason", "not_host")); return false; } if (_bomber == null) { WLog.Line("cmd_rejected", false, ("name", "bomb"), ("sub", sub), ("reason", "no_bomb_session")); return false; } if (_bomber.BomberActor < 0) { WLog.Line("cmd_rejected", false, ("name", "bomb"), ("sub", sub), ("reason", "no_bomber")); return false; } return true; } public void DebugToggleSelfEcho() { if (_voiceDriver == null) { WLog.Line("cmd_rejected", false, ("name", "echo"), ("reason", "no_voice_driver")); } else { bool flag = !_voiceDriver.IsDebugSelfEchoOn; _voiceDriver.SetDebugSelfEcho(flag); WLog.Line("cmd_echo", false, ("enabled", flag ? 1 : 0)); } } private bool GuardRolesDebug(string name) { if (_roles != null && _session != null && SemiFunc.IsMasterClientOrSingleplayer()) { return true; } WLog.Line("cmd_rejected", false, ("name", name), ("reason", "no_roles_session")); return false; } public VoteRejectReason HostCastVoteAsActor(int voterActor, int targetActor) { if (_meeting == null || !SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("vote_proxy_rejected", false, ("voter", voterActor), ("reason", (!SemiFunc.IsMasterClientOrSingleplayer()) ? "not_host" : "no_meeting_session")); return VoteRejectReason.NotVotingStage; } VoteRejectReason voteRejectReason = _meeting.CastVote(voterActor, targetActor, NowUnixMs()); WLog.Line("vote_proxy", true, ("voter", voterActor), ("target", targetActor), ("reason", voteRejectReason)); return voteRejectReason; } public bool SimulatePlayerLeave(int actorNumber) { if (_bus is LoopbackNetBus loopbackNetBus) { loopbackNetBus.SimulatePlayerLeft(actorNumber); return true; } WLog.Line("leave_rejected", false, ("actor", actorNumber), ("reason", "loopback_only")); return false; } public void DumpMeetingStatus() { long nowUnixMs = NowUnixMs(); MeetingSession meeting = _meeting; if (meeting != null) { WLog.Line("meetingstatus_host", false, ("stage", meeting.Stage), ("caller", meeting.CallerActor), ("voted", meeting.VotedCount), ("endUnixMs", meeting.EndUnixMs), ("lastEndUnixMs", meeting.LastMeetingEndUnixMs), ("executed", meeting.Outcome?.ExecutedActor.ToString() ?? "none")); } else { WLog.Line("meetingstatus_host", false, ("session", "none")); } WLog.Line("meetingstatus_client", false, ("active", _meetingClient.MeetingActive), ("caller", _meetingClient.CallerActor), ("warpDone", _meetingClient.WarpDone(nowUnixMs)), ("remainingMs", _meetingClient.RemainingMs(nowUnixMs)), ("votedActors", new List(_meetingClient.VotedActors)), ("executed", _meetingClient.Result?.ExecutedActor.ToString() ?? "none")); foreach (KeyValuePair row in _meetingClient.Rows) { WLog.Line("meetingstatus_row", false, ("actor", row.Key), ("status", row.Value)); } } public void HostRequestCheckmateScan() { if (_checkmate != null && SemiFunc.IsMasterClientOrSingleplayer()) { _checkmateScanPending = true; } } private void TickCheckmateHost(long now) { if (_checkmate != null && _session != null && _session.Winner == null && SemiFunc.IsMasterClientOrSingleplayer()) { bool flag = ValueTrackPatch.InHaulSuckWindow(now); if (_roles != null && !flag && now >= _checkmateLineNextSyncUnixMs && SemiFunc.RunIsLevel()) { _checkmateLineNextSyncUnixMs = now + 5000; _roles.UpdateCheckmateLine(HostComputeCheckmateLineDollars()); } if (_checkmateScanPending && !_checkmate.Detected && !flag && now >= _checkmateNextScanUnixMs && SemiFunc.RunIsLevel()) { _checkmateScanPending = false; _checkmateNextScanUnixMs = now + 500; TryDetectCheckmate(); } bool curseActive = _roles != null && _roles.ActiveCurse != null && !_roles.ActiveCurse.Resolved; switch (_checkmate.Tick(now, _session.Phase, curseActive)) { case CheckmateAction.StartCeremony: _session.LockValueCheckmate(); SendCheckmateReveal(now); break; case CheckmateAction.ConfirmWin: _session.NotifyValueCheckmate(now); break; } } } private void TryDetectCheckmate() { try { RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance == (Object)null) { return; } int haulGoalDollars = GameRefs.RoundDirector_haulGoal.Invoke(instance); int extractionPoints = GameRefs.RoundDirector_extractionPoints.Invoke(instance); int pointsCompleted = GameRefs.RoundDirector_extractionPointsCompleted.Invoke(instance); int num = CheckmateJudge.RemainingQuotaDollars(haulGoalDollars, extractionPoints, pointsCompleted); if (num > 0) { float num2 = ValueTrackPatch.ComputeObtainableDollars(); if (CheckmateJudge.IsCheckmate(num2, num)) { _checkmate.NotifyDetected(); WLog.Line("checkmate_detected", false, ("obtainable", (int)num2), ("remainingQuota", num), ("phase", _session.Phase)); } } } catch (Exception ex) { WLog.Line("checkmate_scan_error", false, ("err", ex.Message)); } } private void SendCheckmateReveal(long now) { if (_roles == null) { WLog.Line("checkmate_send_skip", false, ("reason", "no_roles_session")); return; } int extractedDollars = -1; int num = -1; RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance != (Object)null) { num = GameRefs.RoundDirector_haulGoal.Invoke(instance); int num2 = GameRefs.RoundDirector_extractionPoints.Invoke(instance); int num3 = GameRefs.RoundDirector_extractionPointsCompleted.Invoke(instance); extractedDollars = ((num2 > 0) ? (num / num2 * num3) : 0); } SendViaBus(new OutboundMessage(187, new object[2] { _roles.BuildMeetingGaugeData(extractedDollars, num, HostComputeCheckmateLineDollars()), now }, MessageTarget.All, null)); WLog.Line("checkmate_ceremony_start", false); } internal int HostComputeCheckmateLineDollars() { try { if (_roles == null || !SemiFunc.RunIsLevel()) { return -1; } RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance == (Object)null) { return -1; } int haulGoalDollars = GameRefs.RoundDirector_haulGoal.Invoke(instance); int extractionPoints = GameRefs.RoundDirector_extractionPoints.Invoke(instance); int pointsCompleted = GameRefs.RoundDirector_extractionPointsCompleted.Invoke(instance); int num = CheckmateJudge.RemainingQuotaDollars(haulGoalDollars, extractionPoints, pointsCompleted); if (num <= 0) { return -1; } float num2 = ValueTrackPatch.ComputeObtainableDollars(); float num3 = _roles.Gauge.LostDollars + (num2 - (float)num); if (num3 < 0f) { num3 = 0f; } return (int)(num3 + 0.5f); } catch (Exception ex) { WLog.Line("checkmate_line_error", false, ("err", ex.Message)); return -1; } } public bool DebugForceCheckmate() { if (_checkmate == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return false; } _checkmate.NotifyDetected(); WLog.Line("checkmate_forced", false); return true; } private void HandleCheckmateReveal(object[] p) { MeetingGaugeSnapshot meetingGaugeSnapshot = MeetingGaugeSnapshot.FromData((int[])p[0]); if (meetingGaugeSnapshot == null) { WLog.Line("checkmate_reveal_drop", false, ("reason", "invalid_payload")); return; } EnsurePanelBuilt(_checkmateReveal); if (_checkmateReveal.Exists) { _checkmateReveal.Show(meetingGaugeSnapshot, _gaugePanel.LastRevealedPermille, _gaugePanel.LastRevealedLoss); _checkmateVoiceOpen = true; EnsureSfxBuilt(); if (_checkmateRevealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_checkmateRevealCoroutine); } _checkmateRevealCoroutine = ((MonoBehaviour)this).StartCoroutine(_checkmateReveal.Play(delegate { _sfxPlayer.Play("sfx_gauge_break"); }, delegate { _sfxPlayer.Play("sfx_death_stamp"); })); } } private void TickCheckmateClient() { if (_checkmateReveal.Visible && _resultScreen.Visible) { HideCheckmateReveal(); } } private void HideCheckmateReveal() { _checkmateVoiceOpen = false; if (_checkmateRevealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_checkmateRevealCoroutine); _checkmateRevealCoroutine = null; } if (_checkmateReveal.Exists && _checkmateReveal.Visible) { _checkmateReveal.HideNow(); } } public Role? MarkedTeammateRole(int actorNumber) { if (_localRole != Role.BlackCat && _localRole != Role.Werewolf && _localRole != Role.Bomber) { return null; } int[] knownWerewolves = _knownWerewolves; if (knownWerewolves == null || knownWerewolves.Length == 0) { return null; } byte[] knownTeammateRoles = _knownTeammateRoles; for (int i = 0; i < knownWerewolves.Length; i++) { if (knownWerewolves[i] == actorNumber) { if (knownTeammateRoles != null && i < knownTeammateRoles.Length && knownTeammateRoles[i] == 3) { return Role.Bomber; } return Role.Werewolf; } } return null; } public Role? MarkedTeammateRoleForAvatar(PlayerAvatar avatar) { if ((Object)(object)avatar == (Object)null || Registry == null || !Registry.Available) { return null; } if (_localRole != Role.BlackCat && _localRole != Role.Werewolf && _localRole != Role.Bomber) { return null; } if (_knownWerewolves == null || _knownWerewolves.Length == 0) { return null; } int actorNumber = Registry.ResolveActor(avatar); return MarkedTeammateRole(actorNumber); } public bool IsFellowWerewolfActor(int actorNumber) { if (_localRole != Role.Werewolf) { return false; } int[] knownWerewolves = _knownWerewolves; if (knownWerewolves == null || knownWerewolves.Length == 0) { return false; } byte[] knownTeammateRoles = _knownTeammateRoles; for (int i = 0; i < knownWerewolves.Length; i++) { if (knownWerewolves[i] == actorNumber) { if (knownTeammateRoles != null && knownTeammateRoles[i] == 3) { return false; } return true; } } return false; } public bool IsDeadActorClient(int actorNumber) { return _deathMirror.ContainsKey(actorNumber); } public bool ShouldShowDeadCuesClient(int speakerActor) { bool localAlive = !IsDeadActorClient(LocalActor); bool speakerEavesdropAudible = VoiceRules.DecideKind(ClientPhase, LocalRoleClient, localAlive, _clientNecroVoiceMode) == VoicePlanKind.Eavesdrop && _voiceIsEavesdropTarget != null && _voiceIsEavesdropTarget(speakerActor); return VoiceRules.ShouldShowDeadCues(ClientPhase, localAlive, IsDeadActorClient(speakerActor), speakerEavesdropAudible); } public bool ShouldShowDeadTextClient(int speakerActor) { bool localAlive = !IsDeadActorClient(LocalActor); return VoiceRules.ShouldShowDeadText(ClientPhase, localAlive, IsDeadActorClient(speakerActor)); } private int ClientBombPackValue(int index) { if (_clientBombPack != null && index >= 0 && index < _clientBombPack.Length) { return _clientBombPack[index]; } GameConfig gameConfig = Plugin.GameConfig; if (gameConfig == null) { return 0; } return RoomStateKeys.EncodeBomb(gameConfig, int.MaxValue)[index]; } private int ClientShamanPackValue(int index) { if (_clientShamanPack != null && index >= 0 && index < _clientShamanPack.Length) { return _clientShamanPack[index]; } GameConfig gameConfig = Plugin.GameConfig; if (gameConfig == null) { return 0; } return RoomStateKeys.EncodeShaman(gameConfig)[index]; } public void HostFreezeGaugeBase(float totalDollars) { if (_roles != null && SemiFunc.IsMasterClientOrSingleplayer()) { _roles.FreezeBase(totalDollars); } } public void HostAddValueLoss(float lostDollars, bool isOrb) { if (_roles != null && SemiFunc.IsMasterClientOrSingleplayer()) { _roles.AddValueLoss(lostDollars, isOrb); HostRequestCheckmateScan(); } } private void Awake() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) Instance = this; (string, object)[] obj = new(string, object)[2] { ("host", ((Object)((Component)this).gameObject).name), default((string, object)) }; Scene activeScene = SceneManager.GetActiveScene(); obj[1] = ("scene", ((Scene)(ref activeScene)).name); WLog.Line("director_awake", secret: false, obj); Registry = new PlayerRegistry(); Registry.Initialize(); _voiceDriver = new VoiceMixerDriver(Registry); _voiceIsEavesdropTarget = (int actor) => VoiceRules.IsEavesdropTarget(IsDeadActorClient(actor), IsFellowWerewolfActor(actor), _clientNecroVoiceMode); _voiceIsDeadActor = IsDeadActorClient; _meetingButton = new MeetingButton(); _meetingButton.OnConvene = SendConveneRequest; _meetingButton.OnIncompleteHold = ShowConveneHoldHint; _truckWarper = new TruckWarper(); _movementFreezer = new MovementFreezer(); _enemyFreezer = new EnemyFreezer(); _uiManager = new WerewolfUIManager(); _votePanel = new VotePanel(); _votePanel.OnVoteSubmit += SendVote; _votePanel.SetTeamMarkerProvider(MarkedTeammateRole); _gaugePanel.OnRevealBreak = delegate { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_gauge_break"); }; _gaugePanel.OnDeliveryReveal = delegate { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_delivery_register"); }; VoteRow.SetDeadCueProvider(delegate(int actor) { WerewolfDirector instance = Instance; return (Object)(object)instance == (Object)null || instance.ShouldShowDeadCuesClient(actor); }); _roundPanels = new IClientPanel[21] { _votePanel, _gaugePanel, _playGaugePanel, _hudPanel, _toastPanel, _revealCinematic, _catAwakenToast, _wolfStatusPanel, _conveneCountdown, _resultScreen, _deathReveal, _cursorMirror, _bomberHud, _bombIconPresenter, _bombWarningPresenter, _corpseReportHud, _valuableRecordHud, _checkmateReveal, _startHoldOverlay, _conveneHoldGauge, _chatPanel }; } private void OnDestroy() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) (string, object)[] obj = new(string, object)[3] { ("hadSession", _session != null), ("host", ((Object)(object)((Component)this).gameObject != (Object)null) ? ((Object)((Component)this).gameObject).name : "?"), default((string, object)) }; Scene activeScene = SceneManager.GetActiveScene(); obj[2] = ("scene", ((Scene)(ref activeScene)).name); WLog.Line("director_destroyed", secret: false, obj); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } TeardownBus(); } private void Update() { try { EnsureClientBus(); long num = NowUnixMs(); TickModIntegrity(num); WorldgenItemBindings.TryInitialize(); WorldgenMapBinding.TryInitialize(); TickAutoStartWait(num); _session?.Tick(num); _meeting?.Tick(num); TickCorpseReportCancel(num); _roles?.Tick(num); TickCheckmateHost(num); _beaconEffect.Tick(num); if (SemiFunc.IsMasterClientOrSingleplayer() && _resultSequence.TickShouldReturn(num)) { InvokeReturnToLobby(); } TickChatDebug(num); TickResultReturn(); TickLobbySettings(num); TickLobbySettingsPanelVisibility(); TickManualOverlay(); TickStartHold(num); TickMeetingClient(num); TickWarpVerify(num); TickRolesClient(num); TickBomberClient(num); TickShamanClient(num); TickVoice(); TickCatAwakenToast(num); TickCursorMirror(); TickTutorialPresenter(); Plugin.Bindings?.TickTutorialReset(); TickTutorialTriggers(); _deathReveal.Tick(); TickCheckmateClient(); EnemyMapIcons.Tick(); MapHidePatch.Tick(); ValuableMapSyncPatch.Tick(); bool flag = SemiFunc.MenuLevel() || SemiFunc.RunIsLobby(); if (_resetArmed && flag) { ResetToLobby("level_left"); } else if (!flag && (_session != null || _clientPhase != GamePhase.Lobby)) { _resetArmed = true; } } catch (Exception ex) { WLog.Line("tick_error", false, ("err", ex.Message)); } } private void LateUpdate() { try { EnemyMapIcons.LateTick(); } catch (Exception ex) { WLog.Line("latetick_error", false, ("err", ex.Message)); } } private void OnApplicationQuit() { try { ClearCosmeticGrantState("app_quit"); } catch (Exception ex) { WLog.Line("app_quit_flush_error", false, ("err", ex.Message)); } } private void TickVoice() { if (_voiceDriver != null) { bool localAlive = !IsDeadActorClient(LocalActor); GamePhase phase = (_checkmateVoiceOpen ? GamePhase.GameOver : ClientPhase); VoicePlanKind kind = VoiceRules.DecideKind(phase, LocalRoleClient, localAlive, _clientNecroVoiceMode); bool deadCueMuteActive = VoiceRules.IsDeadCueMuteActive(phase, localAlive); _voiceDriver.Tick(kind, _voiceIsEavesdropTarget, _voiceIsDeadActor, deadCueMuteActive); } } private void TickCursorMirror() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Invalid comparison between Unknown and I4 ConfigBindings bindings = Plugin.Bindings; if (bindings != null) { _cursorMirror.SizeScale = bindings.CursorMirrorScale.Value; } bool flag = (_uiManager.IsLayerVisible("Meeting") || _lobbySettingsPanel.Visible || _modIntegrityHeader.Visible || _modIntegrityPanel.Visible || _lobbyStartWarning.Visible || _manualOverlay.IsOpen) && (int)Cursor.lockState == 0; if (flag) { EnsurePanelBuilt(_cursorMirror); } _cursorMirror.Tick(flag); } private void TickManualOverlay() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) EnsurePanelBuilt(_manualOverlay); if (_manualOverlay.Exists) { ConfigBindings bindings = Plugin.Bindings; if (bindings != null) { _manualOverlay.PositionOffset = new Vector2((float)bindings.HudOffsetX.Value, (float)bindings.HudOffsetY.Value); } KeyCode key = (KeyCode)((Plugin.ManualKey != null) ? ((int)Plugin.ManualKey.Value) : 0); bool flag = IsLobbyMenu(); bool available = flag || !IsMenuLevel(); _manualOverlay.Tick(key, available, _revealCinematic.Visible, flag); } } private static bool IsLobbyMenu() { try { return SemiFunc.RunIsLobbyMenu(); } catch { return false; } } private static bool IsMenuLevel() { try { return SemiFunc.MenuLevel(); } catch { return true; } } private void EnsurePanelBuilt(IClientPanel panel) { if (panel.Exists || !_uiManager.EnsureCreated(((Component)this).gameObject)) { return; } Transform layerRoot = _uiManager.GetLayerRoot(panel.LayerName); if ((Object)(object)layerRoot == (Object)null) { return; } try { panel.Build(layerRoot); } catch (Exception ex) { WLog.Line("panel_build_error", false, ("layer", panel.LayerName), ("err", ex.Message)); } } private string BusMode() { if (!(_bus is PhotonNetBus)) { return "loopback"; } return "photon"; } private static long NowUnixMs() { return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } public StartResult StartHosted() { if (Registry == null || !Registry.Available) { WLog.Line("start_rejected", false, ("reason", "mod_disabled")); return StartResult.Rejected(StartRejectReason.NotInLobby); } if (!SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("start_rejected", false, ("reason", "not_host")); return StartResult.Rejected(StartRejectReason.NotInLobby); } Plugin.RefreshGameConfig(); string text = SettingsCatalog.EncodeBlob(Plugin.GameConfig); _roomState.PublishSharedSettings(text); _lastPublishedBlob = text; _roundGameOverAutoReturnSec = Math.Max(0, Plugin.GameConfig.GameOverAutoReturnSec); long num = NowUnixMs(); SelectBus(); List list = Registry.BuildRealPlayers(); list.AddRange(_pendingBots); _session = new GameSession(); _session.OnSend += SendViaBus; _session.OnSessionEvent += HandleSessionEvent; foreach (KeyValuePair pendingForcedRole in _pendingForcedRoles) { _session.ReserveForcedRole(pendingForcedRole.Key, pendingForcedRole.Value); } ClearClientState(); StartResult startResult = _session.Start(Plugin.GameConfig, list, num, new Random()); if (!startResult.Success) { _session.OnSend -= SendViaBus; _session.OnSessionEvent -= HandleSessionEvent; _session = null; return startResult; } _pendingForcedRoles.Clear(); _lifecycleGate.MarkStarted(); _meeting = new MeetingSession(Plugin.GameConfig, _session, _session.Players, num); _meeting.OnSend += SendViaBus; _meeting.OnExecutePlayer += ExecuteVotedPlayer; _meeting.OnPhaseChangeRequest += HandleMeetingPhaseRequest; _meeting.OnMeetingStateChanged += _roomState.PublishMeeting; _meeting.OnRightsChanged += _roomState.PublishRights; _roomState.PublishSettings(Plugin.GameConfig, list.Count); _clientValuableMapMode = Plugin.GameConfig.ValuableMapMode; _clientNecroVoiceMode = Plugin.GameConfig.NecroVoiceMode; _clientExtraJumpCount = Plugin.GameConfig.ExtraJumpCount; _clientConveneSuppressStartSec = Plugin.GameConfig.ConveneSuppressStartSec; _clientConveneSuppressAfterSec = Plugin.GameConfig.ConveneSuppressAfterSec; _clientHealIntervalSec = Plugin.GameConfig.HealIntervalSec; _clientBombPack = RoomStateKeys.EncodeBomb(Plugin.GameConfig, list.Count); _clientShamanPack = RoomStateKeys.EncodeShaman(Plugin.GameConfig); foreach (WPlayer item in list) { _roomState.PublishRights(item.ActorNumber, _meeting.RightsRemaining(item.ActorNumber)); } _checkmate = new CheckmateSequence(); _checkmateScanPending = false; _checkmateNextScanUnixMs = 0L; _roles = new RolesSession(Plugin.GameConfig, _session, num, new Random()); _roles.OnSend += SendViaBus; _roles.OnInformantEstablished += HandleInformantEstablished; _roles.OnInformantEstablished += HandleInformantDigest; _roles.OnPerkUnlocked += HandlePerkUnlockedDigest; _roles.OnCurseSealed += SealBlackCatOnCurseResolved; _roles.OnCurseKill += ExecuteCurseKill; _roles.OnEnemyIgnoreChanged += ApplyEnemyIgnore; _roles.OnBeaconTriggered += TriggerBeacon; _meeting.OnMeetingStateChanged += HandleMeetingStateChangedForRolesAndBomber; _meeting.OnVotingStarted += HandleVotingStartedForRoles; _bomber = new BombSession(Plugin.GameConfig, _session, num); _roles.OnGaugePctChanged += HandleGaugePctChangedForBomber; SendBomberStateIfDirty(); if ((Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated) { ValueTrackPatch.ScanAndFreezeBase(); } _resetArmed = true; WLog.Line("start", false, ("players", list.Count), ("mode", BusMode())); return startResult; } public void OnLevelGenerated() { try { if (SemiFunc.IsMasterClientOrSingleplayer() && Plugin.GameConfig != null && Plugin.GameConfig.WerewolfModeEnabled && _session == null) { bool flag = SemiFunc.RunIsLevel(); GamePhase hostPhase = HostPhase; if (!_lifecycleGate.ShouldAutoStart(modeEnabled: true, flag, hostPhase)) { WLog.Line("autostart_skipped", false, ("isRunLevel", flag), ("phase", hostPhase)); } else { _autoStartWait.Arm(NowUnixMs()); WLog.Line("autostart_armed", false, ("phase", hostPhase), ("timeoutSec", 60)); } } } catch (Exception ex) { WLog.Line("autostart_error", false, ("err", ex.Message)); } } private void TickAutoStartWait(long now) { if (!_autoStartWait.Armed) { return; } if (_session != null || !SemiFunc.RunIsLevel() || !SemiFunc.IsMasterClientOrSingleplayer()) { _autoStartWait.Disarm(); return; } AutoStartFireReason autoStartFireReason = _autoStartWait.ShouldFire(AllPlayersLevelLoadCompleted(), now); if (autoStartFireReason != AutoStartFireReason.None) { WLog.Line("autostart_trigger", false, ("phase", HostPhase), ("reason", (autoStartFireReason == AutoStartFireReason.AllLoaded) ? "all_loaded" : "timeout")); StartResult startResult = StartHosted(); if (!startResult.Success) { WLog.Line("autostart_rejected", false, ("reason", startResult.Reason)); } } } private bool AllPlayersLevelLoadCompleted() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 try { if ((Object)(object)GameDirector.instance == (Object)null || (int)GameDirector.instance.currentState != 2) { return false; } List list = SemiFunc.PlayerGetList(); if (list == null || list.Count == 0) { return false; } if (SemiFunc.IsMultiplayer()) { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && list.Count < currentRoom.PlayerCount) { return false; } } foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null) && !GameRefs.PlayerAvatar_levelAnimationCompleted.Invoke(item)) { return false; } } return true; } catch (Exception ex) { WLog.Line("autostart_probe_error", false, ("err", ex.Message)); return true; } } public void AddPendingBot(WPlayer bot) { if (bot != null) { _pendingBots.Add(bot); } } public int DebugSpawnFakeBodies() { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null || (Object)(object)((Component)instance).transform == (Object)null) { WLog.Line("cmd_body", false, ("reason", "no_local_avatar")); return -1; } List list = new List(); if (_session != null) { foreach (WPlayer player in _session.Players) { if (player.IsBot) { list.Add(player.ActorNumber); } } } else { foreach (WPlayer pendingBot in _pendingBots) { list.Add(pendingBot.ActorNumber); } } Vector3 position = ((Component)instance).transform.position; Vector3 forward = ((Component)instance).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } ((Vector3)(ref forward)).Normalize(); int num = 0; for (int i = 0; i < list.Count; i++) { Vector3 groundPos = position + forward * (2f + 1.5f * (float)i); if (FakeBodies.SpawnOrMove(list[i], groundPos)) { num++; } } WLog.Line("cmd_body", false, ("bots", list.Count), ("created", num)); return num; } public bool DebugSpawnMoneyBag(int valueDollars) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) if (!SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("cmd_spawnbag", false, ("reason", "not_host")); return false; } PlayerAvatar instance = PlayerAvatar.instance; GameObject val = (((Object)(object)AssetManager.instance != (Object)null) ? AssetManager.instance.surplusValuableSmall : null); if ((Object)(object)instance == (Object)null || (Object)(object)((Component)instance).transform == (Object)null || (Object)(object)val == (Object)null) { WLog.Line("cmd_spawnbag", false, ("reason", "no_avatar_or_prefab")); return false; } Vector3 forward = ((Component)instance).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val2 = ((Component)instance).transform.position + forward * 1.5f + Vector3.up * 1f; GameObject val3 = (SemiFunc.IsMultiplayer() ? PhotonNetwork.InstantiateRoomObject("Valuables/" + ((Object)val).name, val2, Quaternion.identity, (byte)0, (object[])null) : Object.Instantiate(val, val2, Quaternion.identity)); if (valueDollars > 0) { ValuableObject component = val3.GetComponent(); if ((Object)(object)component != (Object)null) { GameRefs.ValuableObject_dollarValueOverride.Invoke(component) = valueDollars; } } WLog.Line("cmd_spawnbag", false, ("value", valueDollars)); return true; } public int DebugClearFakeBodies() { int num = FakeBodies.Clear(); WLog.Line("cmd_body_clear", false, ("removed", num)); return num; } public void ReserveForcedRole(int actorNumber, Role role) { _pendingForcedRoles[actorNumber] = role; } public void HostRecordDeath(PlayerAvatar avatar) { if (_session != null && SemiFunc.IsMasterClientOrSingleplayer()) { int actorNumber = Registry.ResolveActor(avatar); _session.RecordDeath(actorNumber, NowUnixMs()); _meeting?.NotifyPlayerDied(actorNumber); } } public void HostRecordDeathByActor(int actorNumber, bool asVote) { if (_session != null && SemiFunc.IsMasterClientOrSingleplayer()) { if (asVote) { _session.MarkNextDeathAsVote(actorNumber); } _session.RecordDeath(actorNumber, NowUnixMs()); _meeting?.NotifyPlayerDied(actorNumber); } } public void HostNotifyExtraction(bool completed, bool failed) { if (_session != null && SemiFunc.IsMasterClientOrSingleplayer()) { _session.NotifyExtractionOutcome(completed, failed, NowUnixMs()); } } public void HostNotifyMailDeparture() { if (_session != null && SemiFunc.IsMasterClientOrSingleplayer()) { WLog.Line("mail_departure", false); _session.NotifyMailDeparture(NowUnixMs()); } } private void TickCorpseReportCancel(long now) { if (_meeting != null && SemiFunc.IsMasterClientOrSingleplayer() && LastRunGate.IsLastRunActive()) { _meeting.TryCancelCorpseReportCountdown(now); } } private bool HostHasUnannouncedCorpse() { if (_session == null) { return false; } foreach (WPlayer player in _session.Players) { if (_meetingClient.IsDeadUnannounced(player.ActorNumber)) { return true; } } return false; } public void HostForceExpireTimer() { if (_session != null && SemiFunc.IsMasterClientOrSingleplayer()) { _session.ForceExpireTimer(NowUnixMs()); } } public void HostNotifyDisclosure(DisclosureKind kind) { if (_session != null && SemiFunc.IsMasterClientOrSingleplayer()) { _session.NotifyDisclosureCondition(kind); } } public PhaseChangeResult HostRequestPhaseChange(GamePhase target) { if (_session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return PhaseChangeResult.Rejected(PhaseChangeRejectReason.InvalidTransition); } return _session.RequestPhaseChange(target, NowUnixMs()); } public void DumpStatus() { GameSession session = _session; if (session == null) { WLog.Line("status", false, ("session", "none"), ("clientPhase", _clientPhase), ("clientRoundEnd", _clientRoundEndUnixMs), ("pendingBots", _pendingBots.Count)); return; } long nowUnixMs = NowUnixMs(); WLog.Line("status", false, ("session", "active"), ("phase", session.Phase), ("remainingMs", session.RemainingMs(nowUnixMs)), ("players", session.Players.Count), ("winner", (session.Winner == null) ? "none" : session.Winner.WinningTeam.ToString()), ("pendingBots", _pendingBots.Count), ("mode", BusMode())); MeetingSession meeting = _meeting; if (meeting != null) { WLog.Line("status_meeting", false, ("stage", meeting.Stage), ("voted", meeting.VotedCount), ("endUnixMs", meeting.EndUnixMs), ("caller", meeting.CallerActor), ("lastEndUnixMs", meeting.LastMeetingEndUnixMs)); } foreach (WPlayer player in session.Players) { WLog.Line("status_player", true, ("actor", player.ActorNumber), ("name", player.Name), ("bot", player.IsBot), ("role", player.Role), ("alive", player.Alive), ("cause", player.DeathCause?.ToString() ?? "none"), ("meetingRights", meeting?.RightsRemaining(player.ActorNumber) ?? 0)); } } private void ExecuteVotedPlayer(int actorNumber) { try { long holdExtensionMs = 0L; if (_roles != null && _meeting != null && _roles.TryStartCurse(actorNumber, NowUnixMs(), out holdExtensionMs, _meeting.VotersFor(actorNumber))) { _meeting.ExtendClosingHold(holdExtensionMs); int num = ((Plugin.GameConfig != null) ? (Plugin.GameConfig.CurseWaitSec * 1000) : 10000); ((MonoBehaviour)this).StartCoroutine(DelayedExecute(actorNumber, num, "blackcat_curse_wait")); WLog.Line("execute_player", false, ("actor", actorNumber), ("via", "deferred"), ("delayMs", num), ("kind", "blackcat")); } else { ((MonoBehaviour)this).StartCoroutine(DelayedExecute(actorNumber, 5000, "post_result_pause")); WLog.Line("execute_player", false, ("actor", actorNumber), ("via", "deferred"), ("delayMs", 5000), ("kind", "regular")); } } catch (Exception ex) { WLog.Line("execute_player_error", false, ("actor", actorNumber), ("err", ex.Message)); } } private IEnumerator DelayedExecute(int actorNumber, int delayMs, string reason) { if (delayMs > 0) { yield return (object)new WaitForSecondsRealtime((float)delayMs / 1000f); } if (_session == null) { WLog.Line("execute_player_skip", false, ("actor", actorNumber), ("reason", "session_gone"), ("via", "delayed")); yield break; } try { PlayerAvatar val = ((actorNumber > 0) ? ResolveAvatar(actorNumber) : null); if ((Object)(object)val != (Object)null) { _session?.MarkNextDeathAsVote(actorNumber); val.PlayerDeath(-1); WLog.Line("execute_player_kill", false, ("actor", actorNumber), ("via", "avatar"), ("reason", reason)); } else { HostRecordDeathByActor(actorNumber, asVote: true); WLog.Line("execute_player_kill", false, ("actor", actorNumber), ("via", "record"), ("reason", reason)); } } catch (Exception ex) { WLog.Line("execute_player_error", false, ("actor", actorNumber), ("stage", "delayed_kill"), ("err", ex.Message)); } } private void HandleMeetingPhaseRequest(GamePhase target) { PhaseChangeResult phaseChangeResult = HostRequestPhaseChange(target); if (!phaseChangeResult.Success) { WLog.Line("meeting_phase_rejected", false, ("target", target), ("reason", phaseChangeResult.Reason)); } } private void HandleMeetingStateChangedForRolesAndBomber(int callerActor, long endUnixMs) { if (callerActor >= 0) { _beaconEffect.CancelAll("meeting_convene"); return; } long nowUnixMs = NowUnixMs(); _roles?.OnMeetingEnded(nowUnixMs); if (_bomber != null) { _bomber.OnMeetingEnded(nowUnixMs); SendBomberStateIfDirty(); } } private void HandleVotingStartedForRoles() { if (_roles != null) { int extractedDollars = -1; int num = -1; RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance != (Object)null) { num = GameRefs.RoundDirector_haulGoal.Invoke(instance); int num2 = GameRefs.RoundDirector_extractionPoints.Invoke(instance); int num3 = GameRefs.RoundDirector_extractionPointsCompleted.Invoke(instance); extractedDollars = ((num2 > 0) ? (num / num2 * num3) : 0); } _roles.OnMeetingStarted(NowUnixMs(), extractedDollars, num, HostComputeCheckmateLineDollars()); } } private void HandleInformantEstablished() { _session?.NotifyDisclosureCondition(DisclosureKind.BlackCatSeesWerewolves); } private void SealBlackCatOnCurseResolved(int catActor) { try { HostRecordDeathByActor(catActor, asVote: true); WLog.Line("curse_cat_sealed", false, ("actor", catActor)); } catch (Exception ex) { WLog.Line("curse_cat_seal_error", false, ("actor", catActor), ("err", ex.Message)); } } private void ExecuteCurseKill(int actorNumber) { ((MonoBehaviour)this).StartCoroutine(DelayedCurseKill(actorNumber, 2000)); WLog.Line("curse_kill_deferred", false, ("actor", actorNumber), ("delayMs", 2000)); } private IEnumerator DelayedCurseKill(int actorNumber, int delayMs) { if (delayMs > 0) { yield return (object)new WaitForSecondsRealtime((float)delayMs / 1000f); } if (_session == null) { WLog.Line("curse_kill_skip", false, ("actor", actorNumber), ("reason", "session_gone")); yield break; } try { PlayerAvatar val = ((actorNumber > 0) ? ResolveAvatar(actorNumber) : null); if ((Object)(object)val != (Object)null) { val.PlayerDeath(-1); WLog.Line("curse_kill", false, ("actor", actorNumber), ("via", "avatar")); } else { HostRecordDeathByActor(actorNumber, asVote: false); WLog.Line("curse_kill", false, ("actor", actorNumber), ("via", "record")); } } catch (Exception ex) { WLog.Line("curse_kill_error", false, ("actor", actorNumber), ("err", ex.Message)); } } private void ApplyEnemyIgnore(int actorNumber, bool ignored) { try { string text = ResolveSteamId(actorNumber); if (string.IsNullOrEmpty(text)) { WLog.Line("enemy_ignore_skipped", true, ("reason", "no_steam_id"), ("actor", actorNumber)); } else { _enemyIgnoreRoster.SetIgnored(text, ignored); } } catch (Exception ex) { WLog.Line("enemy_ignore_error", true, ("actor", actorNumber), ("err", ex.Message)); } } private void TriggerBeacon(int requesterActor) { try { int summonCooldownSec = ((Plugin.GameConfig != null) ? Plugin.GameConfig.BeaconCooldownSec : 60); _beaconEffect.Trigger(ResolveAvatar(requesterActor), NowUnixMs(), summonCooldownSec); } catch (Exception ex) { WLog.Line("beacon_effect_error", true, ("actor", requesterActor), ("err", ex.Message)); } } private string ResolveSteamId(int actorNumber) { if (_session == null) { return null; } foreach (WPlayer player in _session.Players) { if (player.ActorNumber == actorNumber) { return player.SteamId; } } return null; } private PlayerAvatar ResolveAvatar(int actorNumber) { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return null; } foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player != (Object)null && Registry.ResolveActor(player) == actorNumber) { return player; } } return null; } private void SendViaBus(OutboundMessage msg) { if (_bus == null) { return; } try { ObserveForDigest(msg); switch (msg.Target) { case MessageTarget.All: _bus.SendToAll(msg.Code, msg.Payload); break; case MessageTarget.Actors: _bus.SendToActors(msg.Code, msg.Payload, msg.TargetActors); break; case MessageTarget.Master: _bus.SendToMaster(msg.Code, msg.Payload); break; } } catch (Exception ex) { WLog.Line("send_error", false, ("code", (int)msg.Code), ("err", ex.Message)); } } private void HandleBombPlant(int senderActor, int targetActor, long now) { if (_bomber != null) { BombDenyReason bombDenyReason = _bomber.TryPlant(senderActor, targetActor, now); if (bombDenyReason == BombDenyReason.None) { WLog.Line("bomb_plant", true, ("actor", senderActor), ("target", targetActor)); } else { WLog.Line("bomb_deny", false, ("actor", senderActor), ("stage", "plant"), ("reason", bombDenyReason)); } SendBomberStateIfDirty(); } } private void HandleBombDetonate(int senderActor, long now) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if (_bomber == null) { return; } bool meetingLocked = HostPhase != GamePhase.Play; bool targetNearTruck = false; int targetActor = _bomber.TargetActor; if (targetActor != -1) { PlayerAvatar val = ResolveAvatar(targetActor); Vector3? val2 = (((Object)(object)val != (Object)null) ? new Vector3?(((Component)val).transform.position) : ((Vector3?)null)); if (!val2.HasValue && FakeBodies.TryGetPosition(targetActor, out var pos)) { val2 = pos; } if (val2.HasValue) { float radiusMeters = ((Plugin.GameConfig != null) ? Plugin.GameConfig.BomberTruckSafeRadiusMeters : 10f); targetNearTruck = TruckZone.IsNearTruck(val2.Value, radiusMeters); } } int detonatedTargetActor; BombDenyReason bombDenyReason = _bomber.TryDetonate(senderActor, now, meetingLocked, targetNearTruck, out detonatedTargetActor); if (bombDenyReason == BombDenyReason.None && detonatedTargetActor != -1) { float num = ((Plugin.GameConfig != null) ? Plugin.GameConfig.BomberWarningSec : 1f); long num2 = now + (long)(num * 1000f); SendViaBus(new OutboundMessage(180, new object[2] { detonatedTargetActor, num2 }, MessageTarget.All, null)); WLog.Line("bomb_detonate", true, ("actor", senderActor), ("target", detonatedTargetActor), ("atUnixMs", num2)); } else if (bombDenyReason == BombDenyReason.TargetDead) { WLog.Line("bomb_dud", false, ("actor", senderActor)); } else { WLog.Line("bomb_deny", false, ("actor", senderActor), ("stage", "detonate"), ("reason", bombDenyReason)); } SendBomberStateIfDirty(); } private void HandleGaugePctChangedForBomber(float cumulativeGaugePct) { if (_bomber != null) { _bomber.OnGaugeChanged(cumulativeGaugePct); SendBomberStateIfDirty(); } } private void HandleBomberPlayerDied(int actorNumber) { if (_bomber != null) { int bomberActor = _bomber.BomberActor; _bomber.OnPlayerDied(actorNumber); if (bomberActor == actorNumber && bomberActor >= 0) { WLog.Line("bomb_invalidate", false, ("reason", "bomber_died"), ("actor", actorNumber)); } SendBomberStateIfDirty(); } } private void SendBomberStateIfDirty() { if (_bomber != null && _bomber.Dirty) { int bomberActor = _bomber.BomberActor; BomberStateSnapshot bomberStateSnapshot = _bomber.BuildSnapshot(); if (bomberActor >= 0) { SendViaBus(new OutboundMessage(181, new object[5] { bomberStateSnapshot.TargetActor, bomberStateSnapshot.Ammo, (byte)bomberStateSnapshot.LastDeny, bomberStateSnapshot.PlantReadyUnixMs, bomberStateSnapshot.DetonateReadyUnixMs }, MessageTarget.Actors, new int[1] { bomberActor })); } } } private void HandleSessionEvent(SessionEvent e) { try { switch (e.Kind) { case SessionEventKind.PhaseChanged: _roomState.PublishPhase(e.Phase, e.RoundEndUnixMs); if (e.Phase == GamePhase.GameOver || e.Phase == GamePhase.Lobby) { StructuredLog.FlushDeferredSecrets("host_phase"); } break; case SessionEventKind.PlayerDied: _roomState.PublishAlive(e.ActorNumber, alive: false); _roles?.OnPlayerDied(e.ActorNumber); HandleBomberPlayerDied(e.ActorNumber); break; case SessionEventKind.WinnerConfirmed: { WLog.Line("session_win", false, ("team", e.Winner.WinningTeam)); _enemyIgnoreRoster.ClearAll(); long num = NowUnixMs(); if (!CosmeticHandoff.ShouldGrant(_session.StartUnixMs, num)) { WLog.Line("cosmetic_grant_skipped", false, ("reason", "short_match"), ("durationMs", num - _session.StartUnixMs), ("minMs", 180000L)); } else { CosmeticGrant cosmeticGrant = CosmeticLottery.BuildGrant(_session.Players.Select((WPlayer p) => p.ActorNumber).ToList(), new Random()); int[] array = new int[4]; byte[] rarities = cosmeticGrant.Rarities; foreach (byte b in rarities) { array[b]++; } WLog.Line("cosmetic_grant_sent", false, ("actors", cosmeticGrant.Actors.Length), ("common", array[0]), ("uncommon", array[1]), ("rare", array[2]), ("ultraRare", array[3])); _bus.SendToAll(177, CosmeticGrantWire.ToWire(cosmeticGrant)); } if (SemiFunc.IsMasterClientOrSingleplayer()) { int autoReturnSeconds = ((Plugin.GameConfig != null) ? Plugin.GameConfig.GameOverAutoReturnSec : 60); _resultSequence.Begin(NowUnixMs(), autoReturnSeconds); } break; } } } catch (Exception ex) { WLog.Line("session_event_error", false, ("err", ex.Message)); } } private void ApplyInbound(InboundMessage msg) { try { if (TryHandleModIntegrityInbound(msg, NowUnixMs())) { return; } if (EventCodes.IsMasterInbound(msg.Code)) { HandleMasterInbound(msg); return; } object[] payload = msg.Payload; switch (msg.Code) { case 160: _localRole = (Role)(byte)payload[0]; WLog.Line("recv_role", true, ("role", _localRole)); TryStartRoleReveal(); break; case 161: { bool revealStarted = _revealStarted; _localRole = (Role)(byte)payload[0]; WLog.Line("recv_selfrole", true, ("role", _localRole)); TryStartRoleReveal(); if (revealStarted) { TryStartAwakeningReveal(); } break; } case 162: { _knownWerewolves = (int[])payload[0]; byte[] array2 = ((payload.Length > 1) ? (payload[1] as byte[]) : null); _knownTeammateRoles = ((array2 != null && array2.Length == _knownWerewolves.Length) ? array2 : null); WLog.Line("recv_teammates", true, ("count", _knownWerewolves.Length)); TryStartRoleReveal(); if (_localRole == Role.BlackCat) { MaybeShowTutorial(TutorialId.InformantUnlockedAsBlackCat); } break; } case 168: { int num3 = (int)payload[0]; DeathCause deathCause = (DeathCause)(byte)payload[1]; _deathMirror[num3] = deathCause; _meetingClient.ApplyPlayerDied(num3, deathCause); if (num3 == LocalActor) { RolesClient.ForceWolfModeOff(); RolesClient.ForceValuableRecordOff(); _valuableRecordHold.Reset(); _perkEffects.ResetEffects(); MaybeShowTutorial(TutorialId.FirstDeath); } WLog.Line("recv_died", false, ("actor", num3), ("cause", deathCause)); break; } case 188: ApplyResultDigest(payload); break; case 169: _clientPhase = GamePhase.GameOver; _meetingClient.ApplyPhase(GamePhase.GameOver); _meeting?.AbortForGameOver(); ResetRolesClient("gameover"); WLog.Line("recv_gameover", false, ("team", (Team)(byte)payload[0])); BeginResultCountdown(); ShowResultScreen((byte)payload[0], (int[])payload[1], (byte[])payload[2]); break; case 170: ClearCosmeticGrantState("game_start"); ClearClientDigest(); CrownRoster.Clear(); WipeGuardPatch.ResetLogThrottle(); _voiceDriver?.SanitizeAtRoundStart(); _clientRoundEndUnixMs = (long)payload[0]; _clientWerewolfCount = (byte)payload[2]; _gameStartUnixMsClient = NowUnixMs(); FreezeResultAutoReturnConfig(); _clientRevealDelaySec = (int)payload[4]; _clientCatPossible = (byte)payload[3] != 0; _clientDebugSession = (byte)payload[5] != 0; WLog.Line("recv_gamestart", false, ("roundEnd", _clientRoundEndUnixMs), ("werewolfCount", _clientWerewolfCount), ("catPossible", _clientCatPossible), ("revealDelaySec", _clientRevealDelaySec), ("debugSession", _clientDebugSession ? 1 : 0)); TryStartRoleReveal(); break; case 172: { GamePhase gamePhase = (GamePhase)(byte)payload[0]; if (_clientPhase == GamePhase.Meeting && gamePhase == GamePhase.Play) { _lastMeetingEndUnixMsClient = NowUnixMs(); } _clientPhase = gamePhase; _clientRoundEndUnixMs = (long)payload[2]; _meetingClient.ApplyPhase(_clientPhase); if (_clientPhase == GamePhase.GameOver || _clientPhase == GamePhase.Lobby) { ResetRolesClient("phase"); StructuredLog.FlushDeferredSecrets("recv_phase"); } WLog.Line("recv_phase", false, ("phase", _clientPhase), ("roundEnd", _clientRoundEndUnixMs)); break; } case 163: { ConveneKind conveneKind = (((byte)payload[3] == 1) ? ConveneKind.CorpseReport : ConveneKind.Button); HandleStartMeeting((int)payload[0], (long)payload[1], (long)payload[2], conveneKind); string callerName = ResolveDisplayName((int)payload[0]); PushToast((conveneKind == ConveneKind.CorpseReport) ? SessionNotice.ForCorpseReportStarted(callerName) : SessionNotice.ForConveneStarted(callerName)); break; } case 178: _meetingClient.ApplyCancelled(); PushToast(SessionNotice.ForMeetingCancelled()); WLog.Line("recv_meeting_cancelled", false, ("reason", (byte)payload[0])); break; case 165: { int[] array3 = (int[])payload[0]; bool num2 = array3.Length > _meetingClient.VotedActors.Count; RecordMeetingVotesClient(array3); _meetingClient.ApplyVoteProgress(array3, (long)payload[1]); _votePanel?.NotifyVoteProgress(); if (num2) { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_vote_cast"); } WLog.Line("recv_voteprogress", false, ("voted", array3.Length), ("endUnixMs", (long)payload[1])); break; } case 166: { int num = (int)payload[0]; _meetingClient.ApplyResult(new MeetingOutcome { ExecutedActor = num, TargetActors = (int[])payload[1], VoteCounts = (int[])payload[2] }); WLog.Line("recv_meetingresult", false, ("executed", num)); PushToast((num == -1) ? SessionNotice.ForNoExecution() : SessionNotice.ForExecuted(ResolveDisplayName(num))); if (num != -1) { _executionSfxWaitTicks = 0; } break; } case 176: { ConveneRejectReason conveneRejectReason = ConveneDeniedWire.FromWire((byte)payload[0]); PushToast(SessionNotice.ForConveneDenied(conveneRejectReason)); WLog.Line("recv_convenedenied", false, ("wire", (byte)payload[0]), ("reason", conveneRejectReason)); break; } case 167: _pendingBeaconAudit = (byte)payload[0]; WLog.Line("recv_beacon_audit", false, ("uses", (byte)payload[0])); break; case 171: RolesClient.ApplyGaugeSync((int)payload[0], (byte)payload[1], (byte)payload[2], (byte)payload[3], (long)payload[4], (int[])payload[5], NowUnixMs()); WLog.Line("recv_gauge", true, ("ratio", (int)payload[0]), ("flags", (byte)payload[1]), ("charges", (byte)payload[2]), ("status", (byte)payload[3])); break; case 175: HandleRoleState((byte)payload[0], (int[])payload[1], (long)payload[2]); break; case 179: { RolesClient.ApplyCurseCandidates((int[])payload[0]); (string, object)[] array = new(string, object)[1]; int[] obj = (int[])payload[0]; array[0] = ("count", (obj != null) ? obj.Length : (-1)); WLog.Line("recv_curse_candidates", secret: true, array); break; } case 177: HandleCosmeticGrant(payload); break; case 181: ApplyBomberState((int)payload[0], (byte)payload[1], (byte)payload[2], (long)payload[3], (long)payload[4]); break; case 180: ApplyBombDetonation((int)payload[0], (long)payload[1]); break; case 187: HandleCheckmateReveal(payload); break; case 164: case 173: case 174: case 182: case 183: case 184: case 185: case 186: break; } } catch (Exception ex) { WLog.Line("recv_error", false, ("code", (int)msg.Code), ("err", ex.Message)); } } private void HandleMasterInbound(InboundMessage msg) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { DropMasterInbound(msg, "not_host"); return; } long num = NowUnixMs(); switch (msg.Code) { case 173: { if (_meeting == null) { DropMasterInbound(msg, "no_meeting_session"); break; } if (_checkmate != null && _checkmate.CeremonyStarted) { DropMasterInbound(msg, "checkmate_ceremony"); break; } ConveneKind conveneKind = ((msg.Payload.Length != 0 && (byte)msg.Payload[0] == 1) ? ConveneKind.CorpseReport : ConveneKind.Button); bool lastRunActive = conveneKind == ConveneKind.CorpseReport && LastRunGate.IsLastRunActive(); bool corpseAvailable = conveneKind != ConveneKind.CorpseReport || HostHasUnannouncedCorpse(); _meeting.TryConvene(msg.SenderActor, num, conveneKind, lastRunActive, corpseAvailable); break; } case 164: if (_meeting == null) { DropMasterInbound(msg, "no_meeting_session"); } else { _meeting.CastVote(msg.SenderActor, (int)msg.Payload[0], num); } break; case 174: { byte b = (byte)msg.Payload[0]; int num2 = (int)msg.Payload[1]; switch (b) { case 3: if (_bomber == null) { DropMasterInbound(msg, "no_bomb_session"); } else { HandleBombPlant(msg.SenderActor, num2, num); } break; case 4: if (_bomber == null) { DropMasterInbound(msg, "no_bomb_session"); } else { HandleBombDetonate(msg.SenderActor, num); } break; default: if (_roles == null) { DropMasterInbound(msg, "no_roles_session"); } else { _roles.HandleRoleAction(msg.SenderActor, b, num2, (byte)msg.Payload[2], num); } break; } break; } } } private static void DropMasterInbound(InboundMessage msg, string reason) { WLog.Line("recv_master_dropped", false, ("code", (int)msg.Code), ("sender", msg.SenderActor), ("reason", reason)); } private void HandleCosmeticGrant(object[] p) { if (_cosmeticGrantApplied) { WLog.Line("cosmetic_grant_drop", false, ("reason", "already_applied")); return; } if (!CosmeticGrantWire.TryFromWire(p, out var grant)) { WLog.Line("cosmetic_grant_drop", false, ("reason", "invalid_payload")); return; } _cosmeticGrantApplied = true; int localActorNumber = _bus.LocalActorNumber; if (!grant.TryGetCounts(localActorNumber, out var countsByRarity)) { WLog.Line("cosmetic_grant_absent", false, ("actor", localActorNumber)); return; } if (countsByRarity == null || countsByRarity.Length != 4) { WLog.Line("cosmetic_grant_apply_failed", false, ("actor", localActorNumber), ("counts", countsByRarity ?? Array.Empty()), ("err", "invalid_counts")); return; } _pendingCoinCounts = countsByRarity; int num = 0; for (int i = 0; i < countsByRarity.Length; i++) { num += countsByRarity[i]; } WLog.Line("cosmetic_grant_pending", false, ("actor", localActorNumber), ("coins", num)); } private void ResolveCosmeticPending(string trigger) { int[] array = _pendingCoinCounts; if (array == null) { return; } _pendingCoinCounts = null; int num = 0; for (int i = 0; i < array.Length; i++) { num += array[i]; } if (num <= 0) { return; } bool departedToLobbyMenu = false; bool roundDirectorAlive = false; bool cooldownKnown = false; float num2 = float.MaxValue; try { departedToLobbyMenu = SemiFunc.RunIsLobbyMenu(); roundDirectorAlive = (Object)(object)RoundDirector.instance != (Object)null; RunManager instance = RunManager.instance; if ((Object)(object)instance != (Object)null && GameRefs.RunManager_cosmeticWorldObjectCooldown != null) { num2 = GameRefs.RunManager_cosmeticWorldObjectCooldown.Invoke(instance); cooldownKnown = true; } } catch (Exception ex) { WLog.Line("cosmetic_handoff_probe_error", false, ("err", ex.Message)); } if (CosmeticHandoff.Decide(departedToLobbyMenu, roundDirectorAlive, cooldownKnown, num2, out var fallbackReason) == CosmeticHandoff.Route.Inject) { int num3 = 0; try { RoundDirector instance2 = RoundDirector.instance; for (byte b = 0; b < array.Length; b++) { for (int j = 0; j < array[b]; j++) { instance2.CosmeticWorldObjectExtracted((Rarity)b); num3++; } } WLog.Line("cosmetic_handoff", false, ("route", "inject"), ("trigger", trigger), ("coins", num3), ("cooldown", num2)); return; } catch (Exception ex2) { WLog.Line("cosmetic_handoff_inject_error", false, ("err", ex2.Message), ("injected", num3)); array = CosmeticHandoff.SubtractLeading(array, num3); fallbackReason = "inject_error"; } } CosmeticCoinApplier.ApplyRun applyRun = CosmeticCoinApplier.BeginRun(array); if (applyRun != null) { applyRun.FlushRemaining(); applyRun.Finish(); } WLog.Line("cosmetic_handoff", false, ("route", "direct"), ("trigger", trigger), ("reason", fallbackReason)); } private void ClearCosmeticGrantState(string trigger = "round_boundary") { ResolveCosmeticPending(trigger); _cosmeticGrantApplied = false; } private void TickLobbySettings(long now) { if (now - _lobbyTickAtMs < 2000) { return; } _lobbyTickAtMs = now; if (!PhotonNetwork.InRoom || (!SemiFunc.RunIsLobby() && !SemiFunc.RunIsLobbyMenu())) { return; } string blob; if (SemiFunc.IsMasterClientOrSingleplayer()) { if (Plugin.Bindings == null) { return; } string text = SettingsCatalog.EncodeBlob(Plugin.Bindings.Snapshot()); if (string.Equals(text, _lastPublishedBlob, StringComparison.Ordinal)) { return; } _roomState.PublishSharedSettings(text); _lastPublishedBlob = text; } else if (string.IsNullOrEmpty(_lobbyBlobMirror) && _roomState.TryReadSharedSettings(out blob) && !string.IsNullOrEmpty(blob)) { UpdateLobbyBlobMirror(blob); } UpdateLobbySettingsPanelRows(); } private string ResolveLobbySettingsBlob() { if (!string.IsNullOrEmpty(_debugInjectedBlob)) { return _debugInjectedBlob; } if (SemiFunc.IsMasterClientOrSingleplayer()) { if (Plugin.Bindings == null) { return null; } return SettingsCatalog.EncodeBlob(Plugin.Bindings.Snapshot()); } return _lobbyBlobMirror; } private void UpdateLobbySettingsPanelRows() { //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) string text = ResolveLobbySettingsBlob(); if (string.Equals(text, _lastPanelBlob, StringComparison.Ordinal)) { return; } _lastPanelBlob = text; _lastPanelModeEnabled = false; if (string.IsNullOrEmpty(text)) { return; } if (!SettingsCatalog.TryDecodeBlob(text, out var values)) { WLog.Line("cfgshared_decode_failed", false, ("source", (_debugInjectedBlob != null) ? "injected" : "panel"), ("blobLen", text.Length)); return; } _lastPanelModeEnabled = values.TryGetValue("WerewolfModeEnabled", out var value) && string.Equals(value, "1", StringComparison.Ordinal); try { if (!_uiManager.EnsureCreated(((Component)this).gameObject)) { return; } if (!_lobbySettingsPanel.Exists) { Transform layerRoot = _uiManager.GetLayerRoot("Lobby"); if ((Object)(object)layerRoot == (Object)null) { return; } string toggleKeyName = ((Plugin.LobbySettingsPanelKey != null) ? ((object)Plugin.LobbySettingsPanelKey.Value/*cast due to .constrained prefix*/).ToString() : "F7"); _lobbySettingsPanel.Build(layerRoot, toggleKeyName); } _lobbySettingsPanel.SetRows(SettingsCatalog.BuildRows(values)); } catch (Exception ex) { WLog.Line("lobby_settings_panel_error", false, ("reason", "update_rows_failed"), ("err", ex.Message)); } } private void TickLobbySettingsPanelVisibility() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (_lobbySettingsPanel.Exists) { bool flag = SemiFunc.RunIsLobby() || SemiFunc.RunIsLobbyMenu(); if (flag && InputGate.KeysFree && Plugin.LobbySettingsPanelKey != null && Input.GetKeyDown(Plugin.LobbySettingsPanelKey.Value)) { _lobbyPanelUserHidden = !_lobbyPanelUserHidden; WLog.Line("lobby_settings_panel_toggle", false, ("userHidden", _lobbyPanelUserHidden)); } bool flag2 = flag && !string.IsNullOrEmpty(_lastPanelBlob) && _lastPanelModeEnabled; _lobbySettingsPanel.SetVisibility(flag2 && !_lobbyPanelUserHidden, flag2 && _lobbyPanelUserHidden); } } private void UpdateLobbyBlobMirror(string blob) { if (SettingsCatalog.TryDecodeBlob(blob, out var _)) { _lobbyBlobMirror = blob; WLog.Line("room_props_applied", false, ("key", "cfgshared"), ("blobLen", blob?.Length ?? 0)); } else { _lobbyBlobMirror = null; WLog.Line("cfgshared_decode_failed", false, ("blobLen", blob?.Length ?? 0)); } } internal bool TryInterceptLobbyStart(MenuPageLobby lobbyPage) { try { if ((Object)(object)lobbyPage == (Object)null || !IsModIntegrityLobbyActive() || !IsCurrentMaster()) { return true; } int currentMasterActor = CurrentMasterActor(); long nowUnixMs = NowUnixMs(); if (_modIntegrityHostState.IsInitialized) { _modIntegrityHostState.SyncRoster(CurrentRoomActors(), nowUnixMs); ApplyHostSnapshot(); } if (_lobbyStartAttempt.TryConsumeBypass(((Object)lobbyPage).GetInstanceID(), currentMasterActor, nowUnixMs)) { return true; } if (_lobbyStartAttempt.IsFinalCheckPending) { return false; } LobbyStartDecision decision = CurrentStartDecision(); if (decision.HasPending) { if (_lobbyStartAttempt.TryBeginFinalCheck(hasPending: true, nowUnixMs)) { _lobbyStartHeldPage = lobbyPage; SendManifestRequest(_modIntegrityHostState.GetRetryTargets(nowUnixMs, force: true), nowUnixMs, force: true); WLog.Line("lobby_start_warning", false, ("state", "final_check"), ("pending", decision.PendingCount)); } return false; } if (decision.WarningKind == LobbyStartWarningKind.None) { return true; } _lobbyStartHeldPage = lobbyPage; ShowLobbyStartWarning(decision); return false; } catch (Exception ex) { WLog.Line("lobby_start_warning", false, ("state", "prefix_failed"), ("err", ex.Message)); return true; } } private void TickLobbyStartHold(long nowUnixMs) { if (!_lobbyStartAttempt.IsFinalCheckPending) { return; } try { LobbyStartDecision lobbyStartDecision = CurrentStartDecision(); if (_lobbyStartAttempt.ShouldCompleteFinalCheck(lobbyStartDecision.HasPending, nowUnixMs) && !((Object)(object)_lobbyStartHeldPage == (Object)null)) { lobbyStartDecision = CurrentStartDecision(); if (lobbyStartDecision.WarningKind == LobbyStartWarningKind.None) { ReinvokeLobbyStart(_lobbyStartHeldPage, nowUnixMs, "resolved"); } else { ShowLobbyStartWarning(lobbyStartDecision); } } } catch (Exception ex) { WLog.Line("lobby_start_warning", false, ("state", "hold_failed"), ("err", ex.Message)); FailOpenLobbyStart(nowUnixMs); } } private void ShowLobbyStartWarning(LobbyStartDecision decision) { EnsureModIntegrityUiBuilt(); if (!_lobbyStartWarning.Exists) { throw new InvalidOperationException("warning_build"); } _lobbyStartWarning.Show(decision, CancelLobbyStartWarning, OpenModIntegrityDetailsFromWarning, ContinueLobbyStart); WLog.Line("lobby_start_warning", false, ("state", "shown"), ("kind", decision.WarningKind), ("players", decision.PlayerCount), ("required", decision.RequiredPlayerCount), ("shortfall", decision.HasPlayerShortfall ? 1 : 0), ("werewolfCount", decision.WerewolfCount), ("overflow", decision.HasTeamOverflow ? 1 : 0), ("difference", decision.DifferenceCount), ("unavailable", decision.UnavailableCount), ("pending", decision.PendingCount)); } private void CancelLobbyStartWarning() { _lobbyStartAttempt.Clear(); _lobbyStartHeldPage = null; } private void OpenModIntegrityDetailsFromWarning() { MenuPageLobby lobbyStartHeldPage = _lobbyStartHeldPage; try { _lobbyStartAttempt.Clear(); _modIntegrityPanel.Open(preferNeedsReview: true); _lobbyStartHeldPage = null; } catch (Exception ex) { _lobbyStartHeldPage = lobbyStartHeldPage; WLog.Line("lobby_start_warning", false, ("state", "details_failed"), ("err", ex.Message)); FailOpenLobbyStart(NowUnixMs()); } } private void ContinueLobbyStart() { MenuPageLobby lobbyStartHeldPage = _lobbyStartHeldPage; try { if ((Object)(object)lobbyStartHeldPage != (Object)null) { ReinvokeLobbyStart(lobbyStartHeldPage, NowUnixMs(), "explicit_continue"); } } catch (Exception ex) { _lobbyStartHeldPage = lobbyStartHeldPage; WLog.Line("lobby_start_warning", false, ("state", "continue_failed"), ("err", ex.Message)); FailOpenLobbyStart(NowUnixMs()); } } private void ReinvokeLobbyStart(MenuPageLobby page, long nowUnixMs, string reason) { if ((Object)(object)page == (Object)null || !PhotonNetwork.InRoom || !IsCurrentMaster()) { _lobbyStartAttempt.Clear(); _lobbyStartHeldPage = null; return; } int authorizingHostActor = CurrentMasterActor(); _lobbyStartAttempt.ArmOneShotBypass(((Object)page).GetInstanceID(), authorizingHostActor, nowUnixMs); _lobbyStartHeldPage = null; WLog.Line("lobby_start_warning", false, ("state", "reinvoke"), ("reason", reason)); page.ButtonStart(); } private void FailOpenLobbyStart(long nowUnixMs) { MenuPageLobby lobbyStartHeldPage = _lobbyStartHeldPage; _lobbyStartHeldPage = null; if ((Object)(object)lobbyStartHeldPage != (Object)null && PhotonNetwork.InRoom && IsCurrentMaster()) { ReinvokeLobbyStart(lobbyStartHeldPage, nowUnixMs, "fail_open"); } else { _lobbyStartAttempt.Clear(); } } private LobbyStartDecision CurrentStartDecision() { return LobbyStartGate.Evaluate(_modIntegrityHostState.IsInitialized ? _modIntegrityHostState.BuildSnapshot() : null, _modManifestCollector.State == CollectorState.Ready && _modIntegrityHostState.IsInitialized, LobbyStartPlayerCount(), LiveWerewolfCount(), LiveDebugMode()); } private static bool LiveWerewolfModeEnabled() { if (Plugin.Bindings != null) { return Plugin.Bindings.WerewolfModeEnabled.Value; } if (Plugin.GameConfig != null) { return Plugin.GameConfig.WerewolfModeEnabled; } return false; } private static bool LiveDebugMode() { if (Plugin.Bindings != null) { return Plugin.Bindings.DebugMode.Value; } if (Plugin.GameConfig != null) { return Plugin.GameConfig.DebugMode; } return false; } private static int LiveWerewolfCount() { if (Plugin.Bindings != null) { return Plugin.Bindings.WerewolfCount.Value; } if (Plugin.GameConfig == null) { return 1; } return Plugin.GameConfig.WerewolfCount; } private int LobbyStartPlayerCount() { return ((!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) ? 1 : PhotonNetwork.CurrentRoom.PlayerCount) + PendingBotCount; } private void HandleStartMeeting(int caller, long warpUnixMs, long endUnixMs, ConveneKind kind = ConveneKind.Button) { _clientWarpUnixMs = warpUnixMs; _warpExecuted = false; _votePendulumPlayed = false; _meetingClient.ApplyStartMeeting(caller, warpUnixMs, endUnixMs, kind); PlayerSpawnPatch.MeetingActive = true; _deathRevealPending = true; ResetMeetingChat(); WLog.Line("recv_startmeeting", false, ("caller", caller), ("warpUnixMs", warpUnixMs), ("endUnixMs", endUnixMs)); BeginMeetingUi(); MaybeShowTutorial(TutorialId.MeetingCountdownStarted); _pendingMeetingTutorial = true; } private void TickMeetingClient(long now) { //IL_057f: Unknown result type (might be due to invalid IL or missing references) TryRestoreMeetingFromRoomState(); if ((_session != null || _clientPhase == GamePhase.Play || _clientPhase == GamePhase.Meeting) && SemiFunc.RunIsLevel()) { if (!_meetingButton.Exists && (Object)(object)TruckSafetySpawnPoint.instance != (Object)null) { _meetingButton.Create(); } long readyAtUnixMs = ComputeMeetingReadyAt(); int remaining = 0; _roomState.TryReadRights(LocalActor, out remaining); _meetingButton.Tick(LocalCanConvene(), readyAtUnixMs, now, remaining); EnsurePanelBuilt(_conveneHoldGauge); _conveneHoldGauge.Tick(_meetingButton.HoldCharging, _meetingButton.HoldRatio, _meetingButton.VisualWorldPos); TickCorpseReport(); } else { if (_meetingButton.Exists) { _meetingButton.Destroy(); } if (_conveneHoldGauge.Exists) { _conveneHoldGauge.Tick(visible: false, 0f, null); } } bool meetingActive = _meetingClient.MeetingActive; if (meetingActive && _meetingClient.WarpDone(now)) { TrySetLocalInvincibleForMeeting(); } if (meetingActive && !_meetingClient.WarpDone(now)) { int remainingSeconds = (int)((_clientWarpUnixMs - now + 999) / 1000); EnsurePanelBuilt(_conveneCountdown); if (_conveneCountdown.Exists) { TextId headerFormatId = ((_meetingClient.Kind == ConveneKind.CorpseReport) ? TextId.ConveneCountdownCorpseHeaderFormat : TextId.ConveneCountdownHeaderFormat); IEnumerator enumerator = _conveneCountdown.Tick(ResolveDisplayName(_meetingClient.CallerActor), remainingSeconds, headerFormatId); if (enumerator != null) { if (_conveneTweenCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_conveneTweenCoroutine); } _conveneTweenCoroutine = ((MonoBehaviour)this).StartCoroutine(enumerator); EnsureSfxBuilt(); _sfxPlayer.Play("sfx_countdown"); } } } else if (meetingActive) { if (!_warpExecuted) { _warpExecuted = true; ExecuteWarpMoment(); ValuableMapSyncPatch.RefreshSnapshot("meeting_warp"); if (_deathRevealPending) { _deathRevealPending = false; StartDeathReveal(); } } if (_conveneCountdown.Visible) { HideConveneCountdown(); } if (_pendingBeaconAudit >= 0 && _meetingClient.VotingUiReady(now)) { PushToast(SessionNotice.ForBeaconAudit(_pendingBeaconAudit)); _chatRecapBeaconUses = _pendingBeaconAudit; _pendingBeaconAudit = -1; } if (_pendingMeetingTutorial && _meetingClient.VotingUiReady(now)) { if (LocalIsVillagerTeam) { MaybeShowTutorial(TutorialId.FirstMeetingAsVillager); } else if (_localRole == Role.Werewolf) { MaybeShowTutorial(TutorialId.FirstMeetingAsWerewolf); } else if (_localRole == Role.BlackCat) { MaybeShowTutorial(TutorialId.FirstMeetingAsBlackCat); } _pendingMeetingTutorial = false; } _movementFreezer.Tick(IsLocalAlive()); if (SemiFunc.IsMasterClientOrSingleplayer() && _session != null) { _truckWarper.TickWatchdog(IsSessionAlive, now); } if (!_votePendulumPlayed) { long num = _meetingClient.RemainingMs(now); if (num > 0 && num <= 10000) { _votePendulumPlayed = true; EnsureSfxBuilt(); _sfxPlayer.PlayStoppable("sfx_vote_pendulum"); WLog.Line("vote_pendulum_played", false, ("remainingMs", num)); } } else if (_meetingClient.ClosedEarly(now)) { _sfxPlayer.StopStoppable(); } if (_executionSfxWaitTicks >= 0 && ++_executionSfxWaitTicks >= 3) { _executionSfxWaitTicks = -1; EnsureSfxBuilt(); _sfxPlayer.Play("sfx_execution"); WLog.Line("execution_sfx", false, ("kind", "regular")); } } else { if (_meetingUiActive && _warpExecuted) { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_meeting_end"); } _warpExecuted = false; _votePendulumPlayed = false; _executionSfxWaitTicks = -1; _deathRevealPending = false; _pendingBeaconAudit = -1; _pendingMeetingTutorial = false; HideDeathReveal(); _sfxPlayer.StopStoppable(); _movementFreezer.End(); _enemyFreezer.End(); _truckWarper?.ResetWatchdog(); PlayerSpawnPatch.MeetingActive = false; if (_conveneCountdown.Visible) { HideConveneCountdown(); } if (_meetingUiActive) { _votePanel.EndMeeting(); ResetMeetingChat(); _meetingUiActive = false; } } if (_uiManager.Exists) { _uiManager.Tick(_meetingClient, now); if (_uiManager.IsLayerVisible("Meeting")) { UiKit.KeepCursorFree(); _votePanel.Tick(_meetingClient, now); TickMeetingChat(); } } if (_meetingMapOverlay.Exists) { _meetingMapOverlay.Tick(_meetingClient, now, (KeyCode)((Plugin.MeetingMapKey == null) ? 109 : ((int)Plugin.MeetingMapKey.Value)), (Plugin.Bindings != null) ? Plugin.Bindings.MeetingMapOrthoSize.Value : 15f, (Plugin.Bindings == null) ? 1 : Plugin.Bindings.MeetingMapResolution.Value, Plugin.Bindings == null || Plugin.Bindings.MeetingMapGrid.Value); } } private void ShowConveneHoldHint() { long num = NowUnixMs(); if (num >= _nextConveneHoldHintUnixMs) { PushToast(SessionNotice.ForConveneHoldHint()); int num2 = ((Plugin.GameConfig != null && Plugin.GameConfig.ToastDurationSec > 0) ? Plugin.GameConfig.ToastDurationSec : 9); _nextConveneHoldHintUnixMs = num + (long)num2 * 1000L; } } private void ExecuteWarpMoment() { if (SemiFunc.IsMasterClientOrSingleplayer() && _session != null) { if (!_truckWarper.WarpAll(IsSessionAlive)) { WLog.Line("meeting_warp_failed", false, ("reason", "warp_all_false")); } if (_truckWarper.LocalPlayerWarpTarget.HasValue) { _warpVerifyTarget = _truckWarper.LocalPlayerWarpTarget; _warpVerifyDeadlineMs = NowUnixMs() + 3000; } _enemyFreezer.Begin(); } _movementFreezer.Begin(); } private void TickWarpVerify(long now) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) if (!_warpVerifyTarget.HasValue) { return; } try { PlayerController instance = PlayerController.instance; if (!((Object)(object)instance == (Object)null)) { float num = Vector3.Distance(((Component)instance).transform.position, _warpVerifyTarget.Value); if (num < 1f) { WLog.Line("warp_verify_reached", false, ("distance", num), ("elapsedMs", 3000 - (_warpVerifyDeadlineMs - now))); _warpVerifyTarget = null; } else if (now >= _warpVerifyDeadlineMs) { WLog.Line("warp_verify_timeout", false, ("distance", num), ("pos", TruckWarper.FormatVec(((Component)instance).transform.position)), ("target", TruckWarper.FormatVec(_warpVerifyTarget.Value)), ("isTumbling", TruckWarper.IsTumbling(PlayerAvatar.instance))); _warpVerifyTarget = null; } } } catch (Exception ex) { WLog.Line("warp_verify_error", false, ("err", ex.Message)); _warpVerifyTarget = null; } } private void TryRestoreMeetingFromRoomState() { if (!_meetingClient.MeetingActive && _session == null && SemiFunc.RunIsLevel() && _roomState.TryReadPhase(out var phase) && phase == GamePhase.Meeting && _roomState.TryReadMeeting(out var callerActor, out var meetingEndUnixMs) && callerActor != -1) { _meetingClient.RestoreFromRoomState(callerActor, meetingEndUnixMs); _clientPhase = GamePhase.Meeting; _warpExecuted = false; _deathRevealPending = false; _pendingBeaconAudit = -1; _pendingMeetingTutorial = false; ResetMeetingChat(); _chatVoteBaselinePending = true; PlayerSpawnPatch.MeetingActive = true; WLog.Line("meeting_restored", false, ("caller", callerActor), ("endUnixMs", meetingEndUnixMs)); BeginMeetingUi(); } } private void BeginMeetingUi() { EnsureUiBuilt(); if (_votePanel.Exists) { IReadOnlyList readOnlyList2; if (_session == null) { IReadOnlyList readOnlyList = Registry.BuildRealPlayers(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = _session.Players; } IReadOnlyList roster = readOnlyList2; _votePanel.BeginMeeting(roster, ResolveAvatar, LocalActor); _meetingUiActive = true; } } private void StartDeathReveal() { EnsurePanelBuilt(_deathReveal); IReadOnlyList readOnlyList2; if (_session == null) { IReadOnlyList readOnlyList = Registry.BuildRealPlayers(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = _session.Players; } IReadOnlyList readOnlyList3 = readOnlyList2; List list = new List(); if (readOnlyList3 != null) { foreach (WPlayer item in readOnlyList3) { if (item != null && _meetingClient.IsDeadUnannounced(item.ActorNumber)) { list.Add(item); } } } _meetingClient.MarkAllDeadAnnounced(); _chatRecapDeaths.Clear(); foreach (WPlayer item2 in list) { _chatRecapDeaths.Add(item2.Name ?? $"#{item2.ActorNumber}"); } WLog.Line("death_reveal_start", false, ("deadCount", list.Count), ("panelBuilt", _deathReveal.Exists)); if (!_deathReveal.Exists) { return; } _deathReveal.Show(list, ResolveAvatar); Action onImpact = null; if (list.Count > 0) { EnsureSfxBuilt(); onImpact = delegate { _sfxPlayer.Play("sfx_death_stamp"); }; } if (_deathRevealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_deathRevealCoroutine); } _deathRevealCoroutine = ((MonoBehaviour)this).StartCoroutine(_deathReveal.Play(onImpact)); } private void HideDeathReveal() { if (_deathRevealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_deathRevealCoroutine); _deathRevealCoroutine = null; } if (_deathReveal.Exists && _deathReveal.Visible) { _deathReveal.HideNow(); } } private void EnsureUiBuilt() { EnsurePanelBuilt(_votePanel); EnsurePanelBuilt(_meetingMapOverlay); if (MeetingChatLogEnabled) { EnsurePanelBuilt(_chatPanel); } if (_votePanel.Exists && _votePanel.IsPointerBlocked == null) { _votePanel.IsPointerBlocked = (Vector2 p) => (_meetingMapOverlay.Exists && _meetingMapOverlay.IsPointerOverPanel(p)) || (_manualOverlay.Exists && _manualOverlay.IsPointerOverPanel(p)) || (_chatPanel.Exists && _chatPanel.IsPointerOverPanel(p)); } } public void SendConveneRequest() { SendConveneRequest(ConveneKind.Button); } public void SendConveneRequest(ConveneKind kind) { if (_bus == null) { WLog.Line("convene_send_fail", false, ("reason", "no_bus")); } else { _bus.SendToMaster(173, new object[1] { (byte)kind }); } } public void SendVote(int targetActor) { if (_bus == null) { WLog.Line("vote_send_fail", false, ("reason", "no_bus")); } else { _bus.SendToMaster(164, new object[1] { targetActor }); } } private void TickCorpseReport() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) LocalIsNearUnannouncedCorpse = false; CorpseReportConsumedPress = false; if (_meetingClient.MeetingActive || !LocalCanConvene()) { return; } PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { return; } bool flag = LastRunGate.IsLastRunActive(); try { if (!IsUnannouncedCorpseNear(((Component)instance).transform.position)) { return; } if (flag) { KeyCode val = (KeyCode)((Plugin.CorpseReportKey == null) ? 114 : ((int)Plugin.CorpseReportKey.Value)); if (InputGate.KeysFree && Input.GetKeyDown(val)) { CorpseReportConsumedPress = true; WLog.Line("corpse_report_denied_lastrun_local", false); PushToast(SessionNotice.ForConveneDenied(ConveneRejectReason.CorpseReportLastRun)); } return; } LocalIsNearUnannouncedCorpse = true; MaybeShowTutorial(TutorialId.CorpseDiscovery); KeyCode val2 = (KeyCode)((Plugin.CorpseReportKey == null) ? 114 : ((int)Plugin.CorpseReportKey.Value)); if (InputGate.KeysFree && Input.GetKeyDown(val2)) { CorpseReportConsumedPress = true; WLog.Line("corpse_report_pressed", false); SendConveneRequest(ConveneKind.CorpseReport); } } catch (Exception ex) { WLog.Line("corpse_report_tick_error", false, ("err", ex.Message)); } } private bool IsUnannouncedCorpseNear(Vector3 pos) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: 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) GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return false; } if (Registry == null || !Registry.Available) { return false; } foreach (PlayerAvatar player in instance.PlayerList) { if (!((Object)(object)player == (Object)null)) { int actorNumber = Registry.ResolveActor(player); if (_meetingClient.IsDeadUnannounced(actorNumber) && TruckWarper.TryGetDeathHeadPosition(player, out var pos2) && Vector3.Distance(pos, pos2) <= 3f) { return true; } } } if (FakeBodies.Any) { foreach (var (actorNumber2, val) in FakeBodies.Snapshot()) { if (_meetingClient.IsDeadUnannounced(actorNumber2) && Vector3.Distance(pos, val) <= 3f) { return true; } } } return false; } private bool LocalCanConvene() { if (((_session != null) ? _session.Phase : _clientPhase) == GamePhase.Play) { return IsLocalAlive(); } return false; } private long ComputeMeetingReadyAt() { long num = _gameStartUnixMsClient + (long)ClientConveneSuppressStartSec * 1000L; long num2 = _lastMeetingEndUnixMsClient + (long)ClientConveneSuppressAfterSec * 1000L; if (num <= num2) { return num2; } return num; } private void ResetMeetingChat() { _chatLog.Clear(); _chatRecapDeaths.Clear(); _chatRecapBeaconUses = -1; _chatVoteBaselinePending = false; if (_chatPanel.Exists) { _chatPanel.ResetView(); } } private void RecordMeetingVotesClient(int[] votedActors) { if (!MeetingChatLogEnabled || votedActors == null) { return; } try { if (_chatVoteBaselinePending) { _chatVoteBaselinePending = false; return; } string text = Texts.Get(TextId.ChatLogVoted); IReadOnlyCollection votedActors2 = _meetingClient.VotedActors; foreach (int num in votedActors) { if (!Contains(votedActors2, num)) { _chatLog.AppendVote(num, ResolveDisplayName(num), text); } } } catch (Exception ex) { WLog.Line("chat_log_vote_error", false, ("err", ex.Message)); } } private static bool Contains(IReadOnlyCollection values, int value) { foreach (int value2 in values) { if (value2 == value) { return true; } } return false; } public void RecordMeetingChatClient(PlayerAvatar speaker, string message) { if (!MeetingChatLogEnabled || (Object)(object)speaker == (Object)null) { return; } try { int num = ((Registry != null) ? Registry.ResolveActor(speaker) : (-1)); ChatSpeaker speaker2 = ((_meetingClient.GetRowStatus(num) != RowStatus.Alive) ? ChatSpeaker.Dead : ChatSpeaker.Alive); _chatLog.Append(num, ResolveDisplayName(num), message, speaker2); } catch (Exception ex) { WLog.Line("chat_log_record_error", false, ("err", ex.Message)); } } private void TickMeetingChat() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (MeetingChatLogEnabled && _chatPanel.Exists) { _chatPanel.Tick((KeyCode)((Plugin.MeetingChatLogKey == null) ? 108 : ((int)Plugin.MeetingChatLogKey.Value)), InputGate.KeysFree); MeetingGaugeSnapshot meetingGaugeSnapshot = ((RolesClient != null) ? RolesClient.MeetingGauge : null); MeetingRecapData recap = new MeetingRecapData(_chatRecapDeaths, meetingGaugeSnapshot?.LostDollars ?? (-1), meetingGaugeSnapshot?.ExtractedDollars ?? (-1), meetingGaugeSnapshot?.HaulGoalDollars ?? (-1), _chatRecapBeaconUses); _chatPanel.Render(_chatLog, recap, LocalActor, _chatDebugAvatarFallback ? new Func(ResolveAvatarForChatDebug) : new Func(ResolveAvatar), !IsLocalAlive()); } } private bool IsLocalAlive() { return _meetingClient.GetRowStatus(LocalActor) == RowStatus.Alive; } private void TrySetLocalInvincibleForMeeting() { try { PlayerAvatar instance = PlayerAvatar.instance; if (!((Object)(object)instance == (Object)null)) { PlayerHealth playerHealth = instance.playerHealth; if (!((Object)(object)playerHealth == (Object)null)) { playerHealth.InvincibleSet(0.5f); } } } catch (Exception ex) { WLog.Line("meeting_invincible_error", false, ("err", ex.Message)); } } private bool IsSessionAlive(PlayerAvatar avatar) { if (_session == null || (Object)(object)avatar == (Object)null) { return false; } int num = Registry.ResolveActor(avatar); foreach (WPlayer player in _session.Players) { if (player.ActorNumber == num) { return player.Alive; } } return false; } private void TickModIntegrity(long nowUnixMs) { if (!IsModIntegrityLobbyScope()) { _modIntegrityHostSignal = false; } if (!IsModIntegrityLobbyActive()) { if (_modIntegritySessionActive) { ResetModIntegrity("inactive"); } TickModIntegrityUi(active: false); return; } int num = CurrentMasterActor(); if (num <= 0) { ResetModIntegrity("no_master"); return; } if (_modIntegritySessionActive && _modIntegrityHostActor != num) { ResetModIntegrity("master_changed"); } if (!_modIntegritySessionActive) { _modIntegritySessionActive = true; _modIntegrityHostActor = num; WLog.Line("mod_integrity_epoch", false, ("state", "session_active"), ("master", num)); } EnsureModIntegrityUiBuilt(); _modManifestCollector.EnsureStarted(); if (IsCurrentMaster()) { TickModIntegrityHost(nowUnixMs, num); } else { TickModIntegrityGuest(nowUnixMs, num); } if (_modIntegrityClientState.TickDetailTimeout(nowUnixMs, out var timedOutActor)) { _modIntegrityPanel.SetDetailTimedOut(timedOutActor); WLog.Line("mod_integrity_drop", false, ("reason", "detail_timeout"), ("actor", timedOutActor)); } RefreshModIntegrityUi(); TickLobbyStartHold(nowUnixMs); TickModIntegrityUi(active: true); } private void TickModIntegrityHost(long nowUnixMs, int masterActor) { if (_modManifestCollector.State == CollectorState.Failed || _modManifestCollector.State != CollectorState.Ready) { return; } IReadOnlyList readOnlyList = CurrentRoomActors(); if (readOnlyList.Count == 0) { return; } if (!_modIntegrityHostState.IsInitialized) { int num = NextModIntegrityEpoch(nowUnixMs); _modIntegrityHostState.BeginEpoch(num, masterActor, readOnlyList, _modManifestCollector.Result, nowUnixMs); ApplyHostSnapshot(); SendManifestRequest(readOnlyList, nowUnixMs, force: true); WLog.Line("mod_integrity_epoch", false, ("epoch", num), ("master", masterActor), ("actors", readOnlyList.Count), ("fingerprint", _modManifestCollector.Result.Fingerprint.Substring(0, 8))); } else if (nowUnixMs - _modIntegrityRosterTickAtMs >= 500) { _modIntegrityRosterTickAtMs = nowUnixMs; HashSet hashSet = SnapshotActors(_modIntegrityHostState.BuildSnapshot()); HashSet hashSet2 = new HashSet(readOnlyList); List list = new List(); foreach (int key in _modManifestAssemblies.Keys) { if (!hashSet2.Contains(key)) { list.Add(key); } } for (int i = 0; i < list.Count; i++) { _modManifestAssemblies.Remove(list[i]); } _modIntegrityHostState.SyncRoster(readOnlyList, nowUnixMs); List list2 = new List(); for (int j = 0; j < readOnlyList.Count; j++) { int num2 = readOnlyList[j]; if (num2 != masterActor && !hashSet.Contains(num2)) { _modManifestAssemblies.Remove(num2); list2.Add(num2); } } if (list2.Count > 0) { SendManifestRequest(list2, nowUnixMs, force: true); } } _modIntegrityHostState.TickTimeouts(nowUnixMs); IReadOnlyList retryTargets = _modIntegrityHostState.GetRetryTargets(nowUnixMs, force: false); if (retryTargets.Count > 0) { SendManifestRequest(retryTargets, nowUnixMs, force: false); } ApplyHostSnapshot(); if (_bus != null && _modIntegrityHostState.ShouldPublishSnapshot(nowUnixMs, force: false)) { ModIntegritySnapshot modIntegritySnapshot = _modIntegrityHostState.BuildSnapshot(); if (_bus.SendToAll(184, ModIntegrityWire.BuildSnapshot(modIntegritySnapshot))) { _modIntegrityHostState.MarkSnapshotPublished(nowUnixMs); WLog.Line("mod_integrity_status", false, ("epoch", modIntegritySnapshot.Epoch), ("revision", modIntegritySnapshot.Revision), ("actors", modIntegritySnapshot.Records.Count)); } } } private void TickModIntegrityGuest(long nowUnixMs, int masterActor) { if (_modIntegrityPendingRequest != null && _modManifestCollector.State != CollectorState.Collecting && _modManifestCollector.State != CollectorState.NotStarted) { ModManifestRequestMessage modIntegrityPendingRequest = _modIntegrityPendingRequest; _modIntegrityPendingRequest = null; if (_modManifestCollector.State == CollectorState.Ready) { SendManifestReport(modIntegrityPendingRequest); } else if (_bus != null) { WLog.Line("mod_integrity_report", false, ("epoch", modIntegrityPendingRequest.Epoch), ("state", "collection_failed")); } } } private bool TryHandleModIntegrityInbound(InboundMessage message, long nowUnixMs) { if (message == null || message.Code < 182 || message.Code > 186) { return false; } if (!IsModIntegrityLobbyScope()) { return true; } int num = CurrentMasterActor(); if (ModIntegrityGate.IsHostSignal(IsCurrentMaster(), num, message.SenderActor)) { _modIntegrityHostSignal = true; } if (!IsModIntegrityLobbyActive()) { return true; } switch (message.Code) { case 182: { if (message.SenderActor != num || IsCurrentMaster()) { return true; } if (!ModIntegrityWire.TryReadManifestRequest(message.Payload, out var request, out var error3)) { LogModIntegrityDrop(message, error3); return true; } _modIntegrityPendingRequest = request; _modManifestCollector.EnsureStarted(); if (_modManifestCollector.State == CollectorState.Ready) { _modIntegrityPendingRequest = null; SendManifestReport(request); } return true; } case 183: if (!IsCurrentMaster() || !_modIntegrityHostState.IsInitialized || !IsCurrentRoomActor(message.SenderActor)) { return true; } HandleManifestReport(message, nowUnixMs); return true; case 184: { if (message.SenderActor != num) { return true; } if (!ModIntegrityWire.TryReadSnapshot(message.Payload, out var snapshot, out var error2)) { LogModIntegrityDrop(message, error2); return true; } _modIntegrityClientState.TryApplySnapshot(snapshot, num); return true; } case 185: if (!IsCurrentMaster() || !_modIntegrityHostState.IsInitialized || !IsCurrentRoomActor(message.SenderActor)) { return true; } HandleDetailRequest(message); return true; case 186: { if (message.SenderActor != num) { return true; } if (!ModIntegrityWire.TryReadDetailResponse(message.Payload, out var chunk, out var error)) { LogModIntegrityDrop(message, error); return true; } _modIntegrityClientState.TryApplyDetailChunk(chunk, out var _); return true; } default: return true; } } private void HandleManifestReport(InboundMessage message, long nowUnixMs) { if (!ModIntegrityWire.TryReadManifestReport(message.Payload, out var report, out var error)) { ModUnavailableReason reason = ((error == "protocol") ? ModUnavailableReason.UnsupportedProtocol : (IsOversizedManifestPayload(message.Payload) ? ModUnavailableReason.TooLarge : ModUnavailableReason.InvalidPayload)); _modIntegrityHostState.MarkUnavailable(message.SenderActor, reason); _modManifestAssemblies.Remove(message.SenderActor); LogModIntegrityDrop(message, error); } else { if (report.Epoch != _modIntegrityHostState.Epoch) { return; } if (report.IsFingerprintOnly) { FingerprintReportOutcome fingerprintReportOutcome = _modIntegrityHostState.ApplyFingerprintReport(message.SenderActor, report.ManifestFingerprint, nowUnixMs); if (fingerprintReportOutcome == FingerprintReportOutcome.RetryCurrentRequest) { SendManifestRequest(new int[1] { message.SenderActor }, nowUnixMs, force: true); } WLog.Line("mod_integrity_report", false, ("epoch", report.Epoch), ("actor", message.SenderActor), ("mode", "fingerprint"), ("outcome", fingerprintReportOutcome)); return; } if (!_modManifestAssemblies.TryGetValue(message.SenderActor, out var value) || value.Epoch != report.Epoch) { value = new ModManifestChunkAssembler(report); _modManifestAssemblies[message.SenderActor] = value; } ModManifestChunkAddOutcome modManifestChunkAddOutcome = value.Add(report); switch (modManifestChunkAddOutcome) { case ModManifestChunkAddOutcome.Completed: _modIntegrityHostState.ApplyManifest(message.SenderActor, value.Manifest, nowUnixMs); _modManifestAssemblies.Remove(message.SenderActor); break; case ModManifestChunkAddOutcome.Conflict: case ModManifestChunkAddOutcome.Invalid: _modIntegrityHostState.MarkUnavailable(message.SenderActor, ModUnavailableReason.InvalidPayload); _modManifestAssemblies.Remove(message.SenderActor); break; } WLog.Line("mod_integrity_report", false, ("epoch", report.Epoch), ("actor", message.SenderActor), ("chunk", report.ChunkIndex), ("chunks", report.ChunkCount), ("outcome", modManifestChunkAddOutcome)); } } private void HandleDetailRequest(InboundMessage message) { if (!ModIntegrityWire.TryReadDetailRequest(message.Payload, out var request, out var error)) { LogModIntegrityDrop(message, error); } else { if (request.Epoch != _modIntegrityHostState.Epoch) { return; } ModIntegritySnapshot modIntegritySnapshot = _modIntegrityHostState.BuildSnapshot(); if (modIntegritySnapshot.TryGetRecord(request.Actor, out var _)) { IReadOnlyList readOnlyList = ModIntegrityWire.BuildDetailResponse(modIntegritySnapshot.Epoch, modIntegritySnapshot.Revision, request.Actor, _modIntegrityHostState.GetDifferences(request.Actor)); for (int i = 0; i < readOnlyList.Count; i++) { _bus?.SendToActors(186, readOnlyList[i], new int[1] { message.SenderActor }); } } } } private void RequestModIntegrityDetail(int actor) { ModIntegritySnapshot current = _modIntegrityClientState.Current; if (current == null || !_modIntegrityClientState.BeginDetailRequest(actor, NowUnixMs())) { return; } if (IsCurrentMaster() && _modIntegrityHostState.IsInitialized) { IReadOnlyList readOnlyList = ModIntegrityWire.BuildDetailResponse(current.Epoch, current.Revision, actor, _modIntegrityHostState.GetDifferences(actor)); for (int i = 0; i < readOnlyList.Count; i++) { if (ModIntegrityWire.TryReadDetailResponse(readOnlyList[i], out var chunk, out var _)) { _modIntegrityClientState.TryApplyDetailChunk(chunk, out var _); } } } else { _bus?.SendToMaster(185, ModIntegrityWire.BuildDetailRequest(current.Epoch, actor)); } } internal bool TryGetModIntegrityStatus(int actor, out ModParticipantRecord record) { ModIntegritySnapshot current = _modIntegrityClientState.Current; if (current != null) { return current.TryGetRecord(actor, out record); } record = null; return false; } private void EnsureModIntegrityUiBuilt() { if (_uiManager == null || !_uiManager.EnsureCreated(((Component)this).gameObject)) { return; } if (!_modIntegrityHeader.Exists) { _modIntegrityHeader.Build(_uiManager.GetLayerRoot("ModIntegrity")); _modIntegrityHeader.OnOpenPanel = delegate { _modIntegrityPanel.Open(preferNeedsReview: true); }; } if (!_modIntegrityPanel.Exists) { _modIntegrityPanel.Build(_uiManager.GetLayerRoot("ModIntegrity")); _modIntegrityPanel.OnDetailRequested = RequestModIntegrityDetail; } if (!_lobbyStartWarning.Exists) { _lobbyStartWarning.Build(_uiManager.GetLayerRoot("ModIntegrityModal")); } } private void RefreshModIntegrityUi() { ModIntegritySnapshot current = _modIntegrityClientState.Current; if (current != null && (_modIntegrityUiEpoch != current.Epoch || _modIntegrityUiRevision != current.Revision)) { _modIntegrityUiEpoch = current.Epoch; _modIntegrityUiRevision = current.Revision; int localActor = LocalActorNumber(); _modIntegrityHeader.SetSnapshot(current, localActor); _modIntegrityPanel.SetRows(BuildParticipantViews(current, localActor)); } } private void TickModIntegrityUi(bool active) { _modIntegrityHeader.Tick(active && _modIntegrityClientState.Current != null); _modIntegrityPanel.Tick(_modIntegrityClientState); try { _lobbyStartWarning.Tick(); } catch (Exception ex) { WLog.Line("lobby_start_warning", false, ("state", "callback_failed"), ("err", ex.Message)); if ((Object)(object)_lobbyStartHeldPage != (Object)null) { FailOpenLobbyStart(NowUnixMs()); } } } private void ApplyHostSnapshot() { ModIntegritySnapshot modIntegritySnapshot = _modIntegrityHostState.BuildSnapshot(); if (modIntegritySnapshot != null) { _modIntegrityClientState.TryApplySnapshot(modIntegritySnapshot, _modIntegrityHostState.BaselineActor); } } private void SendManifestRequest(IReadOnlyList actors, long nowUnixMs, bool force) { if (_bus == null || !_modIntegrityHostState.IsInitialized || actors == null || actors.Count == 0) { return; } List list = new List(); for (int i = 0; i < actors.Count; i++) { int num = actors[i]; if (num > 0 && num != _modIntegrityHostState.BaselineActor) { list.Add(num); } } if (list.Count != 0) { object[] payload = ModIntegrityWire.BuildManifestRequest(_modIntegrityHostState.Epoch, _modIntegrityHostState.Baseline.Fingerprint); if (_bus.SendToActors(182, payload, list.ToArray())) { WLog.Line("mod_integrity_request", false, ("epoch", _modIntegrityHostState.Epoch), ("targets", list.Count), ("force", force)); } } } private void SendManifestReport(ModManifestRequestMessage request) { if (_bus != null && request != null && _modManifestCollector.State == CollectorState.Ready) { IReadOnlyList readOnlyList = ModIntegrityWire.BuildManifestReport(request.Epoch, request.BaselineFingerprint, _modManifestCollector.Result); for (int i = 0; i < readOnlyList.Count; i++) { _bus.SendToMaster(183, readOnlyList[i]); } WLog.Line("mod_integrity_report", false, ("epoch", request.Epoch), ("chunks", readOnlyList.Count), ("mode", (_modManifestCollector.Result.Fingerprint == request.BaselineFingerprint) ? "fingerprint" : "manifest")); } } private void ResetModIntegrity(string reason) { _modIntegritySessionActive = false; _modIntegrityHostActor = 0; _modIntegrityRosterTickAtMs = 0L; _modIntegrityPendingRequest = null; _modIntegrityHostSignal = false; _lobbyStartHeldPage = null; _modManifestAssemblies.Clear(); _modIntegrityHostState.Clear(); _modIntegrityClientState.Clear(); _lobbyStartAttempt.Clear(); _modIntegrityUiEpoch = 0; _modIntegrityUiRevision = -1; _lobbyStartWarning.Hide(); _modIntegrityPanel.Close(); _modIntegrityHeader.Tick(active: false); _modManifestCollector.ResetSessionView(); WLog.Line("mod_integrity_epoch", false, ("state", "cleared"), ("reason", reason)); } private static bool IsModIntegrityLobbyScope() { return ModIntegrityGate.IsInScope(PhotonNetwork.InRoom, SemiFunc.RunIsLobbyMenu()); } private bool IsModIntegrityLobbyActive() { bool flag = IsCurrentMaster(); return ModIntegrityGate.IsActive(PhotonNetwork.InRoom, SemiFunc.RunIsLobbyMenu(), flag, flag && LiveWerewolfModeEnabled(), _modIntegrityHostSignal); } private static bool IsCurrentMaster() { if (PhotonNetwork.InRoom) { return PhotonNetwork.IsMasterClient; } return false; } private static int CurrentMasterActor() { if (!PhotonNetwork.InRoom || PhotonNetwork.MasterClient == null) { return 0; } return PhotonNetwork.MasterClient.ActorNumber; } private int LocalActorNumber() { if (PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) { return PhotonNetwork.LocalPlayer.ActorNumber; } return _bus?.LocalActorNumber ?? 1; } private static IReadOnlyList CurrentRoomActors() { List list = new List(); Player[] playerList = PhotonNetwork.PlayerList; if (playerList != null) { for (int i = 0; i < playerList.Length; i++) { if (list.Count >= 100) { break; } Player obj = playerList[i]; int num = ((obj != null) ? obj.ActorNumber : 0); if (num > 0 && !list.Contains(num)) { list.Add(num); } } } list.Sort(); return list.AsReadOnly(); } private static bool IsCurrentRoomActor(int actor) { Player[] playerList = PhotonNetwork.PlayerList; if (playerList == null) { return false; } for (int i = 0; i < playerList.Length; i++) { if (playerList[i] != null && playerList[i].ActorNumber == actor) { return true; } } return false; } private IReadOnlyList BuildParticipantViews(ModIntegritySnapshot snapshot, int localActor) { List list = new List(snapshot.Records.Count); for (int i = 0; i < snapshot.Records.Count; i++) { ModParticipantRecord modParticipantRecord = snapshot.Records[i]; list.Add(new ModParticipantView(modParticipantRecord, ResolveRoomPlayerName(modParticipantRecord.Actor), modParticipantRecord.Actor == localActor, modParticipantRecord.Actor == snapshot.BaselineActor)); } return list.AsReadOnly(); } private static string ResolveRoomPlayerName(int actor) { Player[] playerList = PhotonNetwork.PlayerList; if (playerList != null) { foreach (Player val in playerList) { if (val != null && val.ActorNumber == actor) { if (!string.IsNullOrEmpty(val.NickName)) { return val.NickName; } return $"Actor {actor}"; } } } return $"Actor {actor}"; } private int NextModIntegrityEpoch(long nowUnixMs) { int num = (int)(nowUnixMs & 0x7FFFFFFF); if (num <= 0) { num = 1; } if (num <= _modIntegrityEpochSeed) { num = _modIntegrityEpochSeed + 1; } _modIntegrityEpochSeed = num; return num; } private static HashSet SnapshotActors(ModIntegritySnapshot snapshot) { HashSet hashSet = new HashSet(); if (snapshot == null) { return hashSet; } for (int i = 0; i < snapshot.Records.Count; i++) { hashSet.Add(snapshot.Records[i].Actor); } return hashSet; } private static bool IsOversizedManifestPayload(object[] payload) { if (payload == null || payload.Length != 9) { return false; } if (payload[5] is string[] array) { return array.Length > 32; } return false; } private static void LogModIntegrityDrop(InboundMessage message, string reason) { WLog.Line("mod_integrity_drop", false, ("code", (int)message.Code), ("actor", message.SenderActor), ("reason", reason ?? "invalid")); } private void EnsureClientBus() { bool flag = (Object)(object)GameManager.instance != (Object)null && GameManager.instance.gameMode != 0 && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom.PlayerCount >= 2; if (!flag && _bus == null && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom.PlayerCount >= 2) { long num = NowUnixMs(); if (num - _busWaitLogAtMs >= 10000) { _busWaitLogAtMs = num; WLog.Line("client_bus_waiting", false, ("hasGameManager", (Object)(object)GameManager.instance != (Object)null), ("gameMode", ((Object)(object)GameManager.instance != (Object)null) ? GameManager.instance.gameMode : (-1)), ("players", PhotonNetwork.CurrentRoom.PlayerCount)); } } if (flag) { if (_bus == null) { PhotonNetBus photonNetBus = new PhotonNetBus(); photonNetBus.Register(); _bus = photonNetBus; _bus.OnReceived += ApplyInbound; _bus.OnPlayerLeft += HandlePlayerLeft; photonNetBus.OnLocalDisconnected += HandleLocalDisconnected; photonNetBus.OnRoomPropertiesChanged += HandleRoomPropertiesChanged; WLog.Line("client_bus_created", false, ("localActor", _bus.LocalActorNumber)); } } else if (_bus is PhotonNetBus && _session == null) { WLog.Line("client_bus_teardown", false, ("reason", "room_left")); TeardownBus(); } } private void SelectBus() { int num = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.PlayerCount : 0); bool flag = (Object)(object)GameManager.instance == (Object)null || GameManager.instance.gameMode == 0 || num <= 1; bool flag2 = false; if (flag) { TeardownBus(); int localActorNumber = ((!PhotonNetwork.InRoom) ? 1 : PhotonNetwork.LocalPlayer.ActorNumber); _bus = new LoopbackNetBus(localActorNumber); _bus.OnReceived += ApplyInbound; _bus.OnPlayerLeft += HandlePlayerLeft; } else if (_bus is PhotonNetBus) { flag2 = true; } else { TeardownBus(); PhotonNetBus photonNetBus = new PhotonNetBus(); photonNetBus.Register(); _bus = photonNetBus; _bus.OnReceived += ApplyInbound; _bus.OnPlayerLeft += HandlePlayerLeft; photonNetBus.OnLocalDisconnected += HandleLocalDisconnected; photonNetBus.OnRoomPropertiesChanged += HandleRoomPropertiesChanged; } WLog.Line("bus_select", false, ("mode", flag ? "loopback" : "photon"), ("roomPlayers", num), ("reused", flag2)); } private void TeardownBus() { if (_bus != null) { ClientResetPolicy.ApplyRoomLeft(_meetingClient); _bus.OnReceived -= ApplyInbound; _bus.OnPlayerLeft -= HandlePlayerLeft; if (_bus is PhotonNetBus photonNetBus) { photonNetBus.OnLocalDisconnected -= HandleLocalDisconnected; photonNetBus.OnRoomPropertiesChanged -= HandleRoomPropertiesChanged; photonNetBus.Unregister(); } _bus = null; } } private void HandlePlayerLeft(int actorNumber) { try { if (_session != null || _clientPhase != GamePhase.Lobby) { PushToast(SessionNotice.ForPlayerDisconnected(ResolveDisplayName(actorNumber))); } _modManifestAssemblies.Remove(actorNumber); _meetingClient.ApplyPlayerLeft(actorNumber); _meeting?.NotifyPlayerLeft(actorNumber, NowUnixMs()); if (_bomber != null) { int bomberActor = _bomber.BomberActor; int targetActor = _bomber.TargetActor; _bomber.OnPlayerDisconnected(actorNumber); if (bomberActor == actorNumber && bomberActor >= 0) { WLog.Line("bomb_invalidate", false, ("reason", "bomber_disconnected"), ("actor", actorNumber)); } else if (targetActor == actorNumber && targetActor >= 0) { WLog.Line("bomb_invalidate", false, ("reason", "target_disconnected"), ("actor", actorNumber)); } SendBomberStateIfDirty(); } _session?.NotifyPlayerLeft(actorNumber, NowUnixMs()); } catch (Exception ex) { WLog.Line("player_left_error", false, ("actor", actorNumber), ("err", ex.Message)); } } private void HandleLocalDisconnected() { try { WLog.Line("local_disconnect_cleanup", false, ("hadSession", _session != null), ("clientPhase", _clientPhase)); ResetModIntegrity("local_disconnect"); ClearClientState(); _clientPhase = GamePhase.Lobby; _lastPublishedBlob = null; _lobbyBlobMirror = null; _lobbySettingsPanel.SetVisibility(panelVisible: false, hintVisible: false); _lastPanelBlob = null; _lastPanelModeEnabled = false; _lobbyPanelUserHidden = false; _debugInjectedBlob = null; CrownRoster.Clear(); _autoStartWait.Disarm(); } catch (Exception ex) { WLog.Line("local_disconnect_error", false, ("err", ex.Message)); } } private void HandleRoomPropertiesChanged(Hashtable changed) { try { if (changed != null && ((Dictionary)(object)changed).Count != 0) { WLog.Line("room_props_changed", false, ("count", ((Dictionary)(object)changed).Count)); if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgMinimapHide") && _roomState.TryReadMinimapHide(out var minimapHideEnabled)) { _clientMinimapHideEnabled = minimapHideEnabled; WLog.Line("room_props_applied", false, ("key", "minimapHide"), ("value", minimapHideEnabled ? 1 : 0)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgCatPossible") && _roomState.TryReadCatPossible(out var catPossible)) { _clientCatPossible = catPossible; WLog.Line("room_props_applied", false, ("key", "catPossible"), ("value", catPossible ? 1 : 0)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgValuableMapMode") && _roomState.TryReadValuableMapMode(out var mode)) { _clientValuableMapMode = mode; WLog.Line("room_props_applied", false, ("key", "valuableMapMode"), ("value", (int)mode)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgNecroVoiceMode") && _roomState.TryReadNecroVoiceMode(out var mode2)) { _clientNecroVoiceMode = mode2; WLog.Line("room_props_applied", false, ("key", "necroVoiceMode"), ("value", (int)mode2)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgExtraJump") && _roomState.TryReadExtraJump(out var extraJumpCount)) { _clientExtraJumpCount = extraJumpCount; WLog.Line("room_props_applied", false, ("key", "extraJump"), ("value", extraJumpCount)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgConveneSuppressStart") && _roomState.TryReadConveneSuppressStart(out var seconds)) { _clientConveneSuppressStartSec = seconds; WLog.Line("room_props_applied", false, ("key", "conveneSupStart"), ("value", seconds)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgConveneSuppressAfter") && _roomState.TryReadConveneSuppressAfter(out var seconds2)) { _clientConveneSuppressAfterSec = seconds2; WLog.Line("room_props_applied", false, ("key", "conveneSupAfter"), ("value", seconds2)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgHealInterval") && _roomState.TryReadHealInterval(out var seconds3)) { _clientHealIntervalSec = seconds3; WLog.Line("room_props_applied", false, ("key", "healInterval"), ("value", seconds3)); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgBomb") && _roomState.TryReadBombPack(out var packed)) { _clientBombPack = packed; WLog.Line("room_props_applied", false, ("key", "bomb"), ("bomberPossible", packed[0])); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgShaman") && _roomState.TryReadShamanPack(out var packed2)) { _clientShamanPack = packed2; WLog.Line("room_props_applied", false, ("key", "shaman"), ("gazeFullMs", packed2[0])); } if (((Dictionary)(object)changed).ContainsKey((object)"WW_CfgShared") && _roomState.TryReadSharedSettings(out var blob)) { UpdateLobbyBlobMirror(blob); } } } catch (Exception ex) { WLog.Line("room_props_error", false, ("err", ex.Message)); } } private void InvokeReturnToLobby() { try { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("return_lobby_skipped", false, ("reason", "no_run_manager")); return; } WLog.Line("return_lobby", false, ("via", "result_sequence")); instance.ChangeLevel(true, false, (ChangeLevelType)3); instance.AllPlayersDeadSet(false); } catch (Exception ex) { WLog.Line("return_lobby_error", false, ("err", ex.Message)); } } private void ResetToLobby(string reason) { if (_session != null) { WorldgenApplier.RestoreOnLobbyReturn(); } FakeBodies.Clear(); if (_meeting != null) { _meeting.OnSend -= SendViaBus; _meeting.OnExecutePlayer -= ExecuteVotedPlayer; _meeting.OnPhaseChangeRequest -= HandleMeetingPhaseRequest; _meeting.OnMeetingStateChanged -= _roomState.PublishMeeting; _meeting.OnMeetingStateChanged -= HandleMeetingStateChangedForRolesAndBomber; _meeting.OnVotingStarted -= HandleVotingStartedForRoles; _meeting.OnRightsChanged -= _roomState.PublishRights; _meeting = null; } if (_roles != null) { _roles.OnSend -= SendViaBus; _roles.OnInformantEstablished -= HandleInformantEstablished; _roles.OnInformantEstablished -= HandleInformantDigest; _roles.OnPerkUnlocked -= HandlePerkUnlockedDigest; _roles.OnCurseKill -= ExecuteCurseKill; _roles.OnEnemyIgnoreChanged -= ApplyEnemyIgnore; _roles.OnBeaconTriggered -= TriggerBeacon; _roles.OnGaugePctChanged -= HandleGaugePctChangedForBomber; _roles = null; } _bomber = null; _checkmate = null; _checkmateScanPending = false; _checkmateLineNextSyncUnixMs = 0L; _enemyIgnoreRoster.ClearAll(); _beaconEffect.CancelAll("reset"); _beaconEffect.ResetSummonGate(); if (_session != null) { _session.OnSend -= SendViaBus; _session.OnSessionEvent -= HandleSessionEvent; _session = null; } TeardownBus(); _pendingBots.Clear(); _pendingForcedRoles.Clear(); ClearClientState(); _clientPhase = GamePhase.Lobby; _resetArmed = false; _autoStartWait.Disarm(); _lifecycleGate.ResetForNextRound(); ClearCosmeticGrantState(reason); WipeGuardPatch.ResetLogThrottle(); _meetingButton?.Destroy(); _movementFreezer?.End(); _enemyFreezer?.End(); PlayerSpawnPatch.MeetingActive = false; _meetingUiActive = false; IClientPanel[] roundPanels = _roundPanels; for (int i = 0; i < roundPanels.Length; i++) { roundPanels[i].Destroy(); } _toastQueue = null; _sfxPlayer.Destroy(); WLog.Phase(GamePhase.GameOver, GamePhase.Lobby, reason); StructuredLog.FlushDeferredSecrets("session_reset"); } private void ClearClientState() { ClearResultCountdown(); _nextConveneHoldHintUnixMs = 0L; _localRole = null; _knownWerewolves = null; _knownTeammateRoles = null; _clientRoundEndUnixMs = 0L; _clientWerewolfCount = 0; _deathMirror.Clear(); ResetMeetingChat(); _displayNameCache = null; _clientMinimapHideEnabled = false; _clientCatPossible = false; _clientDebugSession = false; _clientValuableMapMode = ValuableMapMode.MeetingSync; _clientNecroVoiceMode = NecroVoiceMode.NonWerewolfDead; _clientExtraJumpCount = null; _clientConveneSuppressStartSec = null; _clientConveneSuppressAfterSec = null; _clientHealIntervalSec = null; _clientBombPack = null; _clientShamanPack = null; _voiceDriver?.ForceRestore("client_reset"); _voiceDriver?.SanitizeAtRoundStart(); _meetingClient.Reset(); _clientWarpUnixMs = 0L; _warpExecuted = false; _votePendulumPlayed = false; _gameStartUnixMsClient = 0L; _lastMeetingEndUnixMsClient = 0L; _clientRevealDelaySec = 0; _catAwakenGate.Reset(); _tutorialPresenter.Cancel(); _wasRoleRevealVisible = false; _prevBeaconCharges = 0; _truckWarper?.ResetWatchdog(); ResetRolesClient("client_reset"); if (_revealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_revealCoroutine); _revealCoroutine = null; } _revealCinematic.HideNow(); if (_catAwakenToastCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_catAwakenToastCoroutine); _catAwakenToastCoroutine = null; } _catAwakenToast.HideNow(); _revealStarted = false; _awakeningRevealStarted = false; HideConveneCountdown(); _deathRevealPending = false; HideDeathReveal(); HideCheckmateReveal(); _resultScreen.Hide(); ClearClientDigest(); ResetBomberClient(); ResetShamanClient(); } private void ObserveForDigest(OutboundMessage msg) { try { if (!(msg == null) && msg.Target == MessageTarget.All) { _matchDigest.Observe(msg.Code, msg.Payload, NowUnixMs(), _session?.Winner); if (msg.Code == 169 && _bus != null) { TryRecordFinalBalance(); _bus.SendToAll(188, _matchDigest.ToWire()); WLog.Line("digest_sent", false, ("entries", _matchDigest.Entries.Count)); } } } catch (Exception ex) { WLog.Line("digest_observe_error", false, ("err", ex.Message)); } } internal void HostRecordExtractionDone() { try { if (SemiFunc.IsMasterClientOrSingleplayer()) { RoundDirector instance = RoundDirector.instance; if (!((Object)(object)instance == (Object)null)) { _matchDigest.RecordExtractionDone(GameRefs.RoundDirector_extractionPointsCompleted.Invoke(instance), GameRefs.RoundDirector_extractionPoints.Invoke(instance), NowUnixMs()); } } } catch (Exception ex) { WLog.Line("digest_extraction_error", false, ("err", ex.Message)); } } private void HandlePerkUnlockedDigest(PerkId perk) { _matchDigest.RecordPerkUnlocked((byte)perk, NowUnixMs()); } private void HandleInformantDigest() { _matchDigest.RecordInformant(NowUnixMs()); } private void TryRecordFinalBalance() { try { RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance == (Object)null) { return; } int num = GameRefs.RoundDirector_haulGoal.Invoke(instance); int num2 = GameRefs.RoundDirector_extractionPoints.Invoke(instance); int num3 = GameRefs.RoundDirector_extractionPointsCompleted.Invoke(instance); if (num > 0 && num2 > 0) { int num4 = CheckmateJudge.RemainingQuotaDollars(num, num2, num3); if (num4 >= 0) { int deliveredDollars = num / num2 * num3; int obtainableDollars = (int)(ValueTrackPatch.ComputeObtainableDollars() + 0.5f); _matchDigest.RecordFinalBalance(deliveredDollars, num4, obtainableDollars, NowUnixMs()); } } } catch (Exception ex) { WLog.Line("digest_balance_error", false, ("err", ex.Message)); } } private void ApplyResultDigest(object[] payload) { _clientDigestEntries = ResultDigest.FromWire(payload); WLog.Line("recv_digest", false, ("entries", (_clientDigestEntries != null) ? _clientDigestEntries.Count : (-1))); } private void ClearClientDigest() { _clientDigestEntries = null; } private void TickResultReturn() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_008f: Unknown result type (might be due to invalid IL or missing references) if (!_resultScreen.Visible) { return; } _resultScreen.Tick(); int? num = _resultCountdown.RemainingSeconds(NowUnixMs()); int num2 = num ?? (-1); if (num2 != _lastResultCountdownSecond) { _lastResultCountdownSecond = num2; _resultScreen.SetFooter(BuildResultFooterText(num)); } if (SemiFunc.IsMasterClientOrSingleplayer() && _resultSequence.Active) { KeyCode val = (KeyCode)((Plugin.ResultReturnKey == null) ? 286 : ((int)Plugin.ResultReturnKey.Value)); if ((int)val != 0 && SemiFunc.NoTextInputsActive() && Input.GetKeyDown(val)) { WLog.Line("result_return_requested", false); _resultSequence.RequestReturn(); } } } private static string BuildResultFooterText(int? remainingSeconds = null) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) string text2; if (SemiFunc.IsMasterClientOrSingleplayer()) { string text = ((Plugin.ResultReturnKey != null) ? ((object)Plugin.ResultReturnKey.Value/*cast due to .constrained prefix*/).ToString() : ((object)(KeyCode)286/*cast due to .constrained prefix*/).ToString()); text2 = Texts.Format(TextId.ResultReturnPromptFormat, text); } else { text2 = Texts.Get(TextId.ResultWaitingHost); } if (!remainingSeconds.HasValue) { return text2; } return Texts.Format(TextId.ResultFooterWithCountdownFormat, text2, remainingSeconds.Value); } private void FreezeResultAutoReturnConfig() { if (SemiFunc.IsMasterClientOrSingleplayer()) { _roundGameOverAutoReturnSec = Math.Max(0, (Plugin.GameConfig != null) ? Plugin.GameConfig.GameOverAutoReturnSec : 0); return; } _roundGameOverAutoReturnSec = 0; if (SettingsCatalog.TryDecodeBlob(_lobbyBlobMirror, out var values) && values.TryGetValue("GameOverAutoReturnSec", out var value) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { _roundGameOverAutoReturnSec = Math.Max(0, result); } } private void BeginResultCountdown() { _resultCountdown.Begin(NowUnixMs(), _roundGameOverAutoReturnSec); _lastResultCountdownSecond = -1; } private void ClearResultCountdown() { _resultCountdown.Clear(); _roundGameOverAutoReturnSec = 0; _lastResultCountdownSecond = -1; } private void TickRolesClient(long now) { //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Unknown result type (might be due to invalid IL or missing references) Role? role = (IsLocalAlive() ? _localRole : ((Role?)null)); _perkEffects.Tick(RolesClient, role, SendRoleAction, OnWolfModeChangedLocal); bool active = role == Role.Werewolf && RolesClient.HealActive && _clientPhase == GamePhase.Play && !_meetingClient.MeetingActive; _perkEffects.TickHeal(active, now, ClientHealIntervalSec); if (_curseSource != null && !RolesClient.CurseActive(now)) { DetachCurseSource("expired"); } if (IsRoundActiveClient) { EnsureRolesUiBuilt(); } if (_gaugePanel.Exists) { _gaugePanel.Tick(RolesClient, _meetingClient, now); } if (_playGaugePanel.Exists) { _playGaugePanel.TickPlay(RolesClient, _meetingClient, (_session != null) ? _session.Phase : _clientPhase, _localRole, now); } if (_hudPanel.Exists) { ConfigBindings bindings = Plugin.Bindings; if (bindings != null) { _hudPanel.PositionOffset = new Vector2((float)bindings.HudOffsetX.Value, (float)bindings.HudOffsetY.Value); } int remaining = 0; if (_meetingButton != null && _meetingButton.IsLocalPlayerNear) { _roomState.TryReadRights(LocalActor, out remaining); } GamePhase phase = ((_session != null) ? _session.Phase : _clientPhase); HudInput input = new HudInput(phase, _localRole, _clientRoundEndUnixMs, RolesClient, _meetingButton != null && _meetingButton.IsLocalPlayerNear, remaining, now, _meetingClient.MeetingActive, _clientDebugSession); HudState hudState = _hudPanel.Tick(_hudModel, input); if (hudState.BellVolumeScale > 0f) { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_bell", hudState.BellVolumeScale); } } if (_toastPanel.Exists && _toastQueue != null) { _toastPanel.Tick(_toastQueue, now); } bool warpedInMeeting = _meetingClient.MeetingActive && _meetingClient.WarpDone(now); if (_wolfStatusPanel.Exists) { WolfStatusState state = _wolfStatusModel.Compute(RolesClient, role, (_session != null) ? _session.Phase : _clientPhase, warpedInMeeting, now, ClientExtraJumpCount, InfiniteJumpPatch.RefillsThisAirTime, InfiniteJumpPatch.InjectedChargeAvailable); _wolfStatusPanel.Tick(state, (Plugin.WolfModeKey != null) ? ((object)Plugin.WolfModeKey.Value/*cast due to .constrained prefix*/).ToString() : "?", (Plugin.BeaconKey != null) ? ((object)Plugin.BeaconKey.Value/*cast due to .constrained prefix*/).ToString() : "?"); } GamePhase gamePhase = ((_session != null) ? _session.Phase : _clientPhase); bool flag = gamePhase == GamePhase.Play && IsLocalAlive() && !_meetingClient.MeetingActive && !LastRunGate.IsLastRunActive(); CorpseReportHudPanel.Layout layout = ((RolesClient != null && RolesClient.PlayGauge != null) ? CorpseReportHudPanel.Layout.AboveMiniGauge : CorpseReportHudPanel.Layout.AtGaugeSlot); if (_corpseReportHud.Exists) { string keyName = ((Plugin.CorpseReportKey != null) ? ((object)Plugin.CorpseReportKey.Value/*cast due to .constrained prefix*/).ToString() : "?"); _corpseReportHud.Tick(flag, LocalIsNearUnannouncedCorpse, keyName, layout); } TickValuableRecord(gamePhase, warpedInMeeting, flag, layout); } private void TickValuableRecord(GamePhase phase, bool warpedInMeeting, bool corpseHudVisible, CorpseReportHudPanel.Layout corpseHudLayout) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) bool flag = ValuableRecordGate.CanOperate(_localRole, IsLocalAlive(), phase, warpedInMeeting); if (!flag) { _valuableRecordHold.Reset(); } else { KeyCode val = (KeyCode)((Plugin.CorpseReportKey == null) ? 114 : ((int)Plugin.CorpseReportKey.Value)); bool held = InputGate.KeysFree && Input.GetKey(val); if (_valuableRecordHold.Tick(held, CorpseReportConsumedPress, Time.deltaTime) && RolesClient.ToggleValuableRecord(_localRole)) { EnsureSfxBuilt(); _sfxPlayer.Play(RolesClient.ValuableRecordOn ? "sfx_valuable_record_on" : "sfx_valuable_record_off"); } } if (_valuableRecordHud.Exists) { _valuableRecordHud.Tick(flag, RolesClient.ValuableRecordOn, _valuableRecordHold.Ratio, _valuableRecordHold.IsCharging, (Plugin.CorpseReportKey != null) ? ((object)Plugin.CorpseReportKey.Value/*cast due to .constrained prefix*/).ToString() : "?", corpseHudLayout, corpseHudVisible); } } private void EnsureSfxBuilt() { if (!_sfxPlayer.Exists) { _sfxPlayer.Build(((Component)this).gameObject); } } private void OnWolfModeChangedLocal(bool wolfModeOn) { if (wolfModeOn) { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_howl"); } } private void HideConveneCountdown() { if (_conveneTweenCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_conveneTweenCoroutine); _conveneTweenCoroutine = null; } _conveneCountdown.Hide(); } public void DebugPlayConveneCountdown(string callerName, int totalSeconds) { try { EnsurePanelBuilt(_conveneCountdown); if (!_conveneCountdown.Exists) { WLog.Line("cmd_fx_countdown_skipped", false, ("reason", "not_built")); return; } if (totalSeconds < 0) { totalSeconds = 0; } if (_conveneTweenCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_conveneTweenCoroutine); } _conveneTweenCoroutine = ((MonoBehaviour)this).StartCoroutine(_conveneCountdown.PlayStandalone(callerName, totalSeconds)); WLog.Line("cmd_fx_countdown", false, ("caller", callerName ?? ""), ("seconds", totalSeconds)); } catch (Exception ex) { WLog.Line("cmd_fx_countdown_error", false, ("err", ex.Message)); } } public void DebugPlayReveal(Role role) { try { string[] teammateNames = ((role != Role.Werewolf) ? Array.Empty() : new string[2] { "テスト太郎", "テスト次郎" }); RevealContent content = RevealScript.Build(role, teammateNames, blackCatPossible: true, ClientValuableMapMode, IsBlackCatCurseEnabledForClient()); EnsurePanelBuilt(_revealCinematic); if (!_revealCinematic.Exists) { WLog.Line("cmd_fx_reveal_skipped", false, ("reason", "not_built")); return; } if (_revealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_revealCoroutine); _revealCoroutine = null; } _revealCoroutine = ((MonoBehaviour)this).StartCoroutine(_revealCinematic.Play(content)); WLog.Line("cmd_fx_reveal", false, ("role", role)); } catch (Exception ex) { WLog.Line("cmd_fx_reveal_error", false, ("err", ex.Message)); } } public void DebugPlayToast(string message) { try { if (string.IsNullOrEmpty(message)) { message = "テスト通知"; } if (_toastQueue == null) { int durationSec = ((Plugin.GameConfig != null && Plugin.GameConfig.ToastDurationSec > 0) ? Plugin.GameConfig.ToastDurationSec : 9); _toastQueue = new ToastQueue(durationSec); } EnsureRolesUiBuilt(); _toastQueue.Push(message, NowUnixMs()); WLog.Line("cmd_fx_toast", false, ("message", message)); } catch (Exception ex) { WLog.Line("cmd_fx_toast_error", false, ("err", ex.Message)); } } public void DebugPlayResult() { try { ClearResultCountdown(); int[] actors = new int[5] { 1, 2, 3, 4, 5 }; byte[] roles = new byte[5] { 0, 0, 1, 2, 0 }; Dictionary deathMirror = new Dictionary { { 2, DeathCause.Other }, { 4, DeathCause.Vote } }; int[] disconnectedActors = new int[1] { 5 }; IReadOnlyList readOnlyList = ResultModel.Build(1, actors, roles, deathMirror, (int a) => "デバッグ" + a, (IReadOnlyCollection)(object)disconnectedActors); EnsurePanelBuilt(_resultScreen); if (!_resultScreen.Exists) { WLog.Line("cmd_fx_result_skipped", false, ("reason", "not_built")); return; } List digestLines = ResultDigestText.FormatLines(new List { new DigestEntry(DigestKind.MatchStart, 0, 0, 0, 0), new DigestEntry(DigestKind.PerkUnlocked, 60, 0, 0, 0), new DigestEntry(DigestKind.Death, 95, 2, 0, 0), new DigestEntry(DigestKind.MeetingConvened, 130, 1, 1, 0), new DigestEntry(DigestKind.InformantEstablished, 180, 0, 0, 0), new DigestEntry(DigestKind.Executed, 200, 4, 0, 0), new DigestEntry(DigestKind.CurseStarted, 200, 4, 0, 0), new DigestEntry(DigestKind.CurseFollow, 210, 3, 0, 0), new DigestEntry(DigestKind.ExtractionDone, 250, 0, 1, 4), new DigestEntry(DigestKind.MatchEnd, 300, 0, 1, 3), new DigestEntry(DigestKind.FinalBalance, 300, 9500, 12000, 8000) }, (int a) => "デバッグ" + a); _resultScreen.Show(Team.Werewolves, readOnlyList, ResolveAvatar, digestLines, BuildResultFooterText()); PlayResultSfx(Team.Werewolves); SetCrownRosterFromRows(readOnlyList); WLog.Line("cmd_fx_result", false, ("rows", readOnlyList.Count)); } catch (Exception ex) { WLog.Line("cmd_fx_result_error", false, ("err", ex.Message)); } } public void DebugPlaySfx(string clipKey) { try { EnsureSfxBuilt(); _sfxPlayer.Play(clipKey); WLog.Line("cmd_fx_sfx", false, ("key", clipKey), ("canPlay", _sfxPlayer.CanPlay)); } catch (Exception ex) { WLog.Line("cmd_fx_sfx_error", false, ("err", ex.Message)); } } public void DebugClearFx() { try { if (_revealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_revealCoroutine); _revealCoroutine = null; } if (_revealCinematic.Exists) { _revealCinematic.HideNow(); } HideConveneCountdown(); if (_resultScreen.Exists) { _resultScreen.Hide(); } ClearResultCountdown(); CrownRoster.Clear(); WLog.Line("cmd_fx_clear", false); } catch (Exception ex) { WLog.Line("cmd_fx_clear_error", false, ("err", ex.Message)); } } public void DebugInjectCfgBlob(string blob) { _debugInjectedBlob = blob; WLog.Line("cmd_cfg_inject", false, ("len", blob?.Length ?? 0)); } public void DebugClearCfgBlob() { _debugInjectedBlob = null; WLog.Line("cmd_cfg_clear", false); } private void TryStartRoleReveal() { try { if (_revealStarted || !_localRole.HasValue || _clientRoundEndUnixMs == 0L) { return; } Role value = _localRole.Value; bool flag = value == Role.Werewolf || value == Role.Bomber; if (flag && _knownWerewolves == null && _clientWerewolfCount != 1) { return; } string[] array; if (flag && _knownWerewolves != null) { List list = new List(_knownWerewolves.Length); int localActor = LocalActor; for (int i = 0; i < _knownWerewolves.Length; i++) { int num = _knownWerewolves[i]; if (num != localActor) { string text = ResolveDisplayName(num); if (_knownTeammateRoles != null && i < _knownTeammateRoles.Length && _knownTeammateRoles[i] == 3) { text = text + "(" + Texts.Get(TextId.RoleNameBomber) + ")"; } list.Add(text); } } array = list.ToArray(); } else { array = Array.Empty(); } RevealContent content = RevealScript.Build(value, array, _clientCatPossible, ClientValuableMapMode, IsBlackCatCurseEnabledForClient()); EnsurePanelBuilt(_revealCinematic); if (!_revealCinematic.Exists) { _revealStarted = true; WLog.Line("reveal_skip", false, ("reason", "not_built")); return; } if (_revealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_revealCoroutine); _revealCoroutine = null; } _revealCoroutine = ((MonoBehaviour)this).StartCoroutine(_revealCinematic.Play(content)); _revealStarted = true; WLog.Line("reveal_start", true, ("role", value), ("catPossible", _clientCatPossible), ("teammates", array.Length)); } catch (Exception ex) { WLog.Line("reveal_start_error", false, ("err", ex.Message)); } } private void TickCatAwakenToast(long now) { if (_catAwakenGate.ShouldFire(_clientPhase, _clientCatPossible, _gameStartUnixMsClient, _clientRevealDelaySec, now)) { PushToast(SessionNotice.ForCatAwakened()); WLog.Line("cat_awaken_toast", false, ("delaySec", _clientRevealDelaySec)); if (LocalIsVillagerTeam) { MaybeShowTutorial(TutorialId.VillagerSeesCatAwakened); } else if (_localRole == Role.Werewolf) { MaybeShowTutorial(TutorialId.WerewolfSeesCatAwakened); } } } private void TryStartAwakeningReveal() { try { if (_awakeningRevealStarted || _localRole != Role.BlackCat) { return; } RevealContent content = RevealScript.BuildBlackCatAwakening(IsBlackCatCurseEnabledForClient()); if (_revealCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_revealCoroutine); _revealCoroutine = null; } if (_revealCinematic.Exists) { _revealCinematic.HideNow(); } EnsurePanelBuilt(_catAwakenToast); if (!_catAwakenToast.Exists) { _awakeningRevealStarted = true; WLog.Line("awakening_reveal_skip", false, ("reason", "not_built")); return; } if (_catAwakenToastCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_catAwakenToastCoroutine); _catAwakenToastCoroutine = null; } _catAwakenToastCoroutine = ((MonoBehaviour)this).StartCoroutine(PlayBlackCatAwakeningAndTutorial(content)); _awakeningRevealStarted = true; WLog.Line("awakening_reveal_start", true); } catch (Exception ex) { WLog.Line("awakening_reveal_error", false, ("err", ex.Message)); } } private IEnumerator PlayBlackCatAwakeningAndTutorial(RevealContent content) { yield return _catAwakenToast.Play(content); _catAwakenToastCoroutine = null; if (_localRole == Role.BlackCat) { MaybeShowTutorial(TutorialId.BlackCatRoleDrawn); } } private void ShowResultScreen(byte winningTeam, int[] actors, byte[] roles) { try { if (actors == null || roles == null) { WLog.Line("result_screen_skip", false, ("reason", "null_payload")); return; } List list = null; foreach (KeyValuePair row in _meetingClient.Rows) { if (row.Value == RowStatus.Disconnected) { (list ?? (list = new List())).Add(row.Key); } } IReadOnlyList readOnlyList = ResultModel.Build(winningTeam, actors, roles, _deathMirror, ResolveDisplayName, list); SetCrownRosterFromRows(readOnlyList); EnsurePanelBuilt(_resultScreen); if (!_resultScreen.Exists) { WLog.Line("result_screen_skip", false, ("reason", "not_built")); } else { List digestLines = ((_clientDigestEntries != null) ? ResultDigestText.FormatLines(_clientDigestEntries, ResolveDisplayName) : null); _resultScreen.Show((Team)winningTeam, readOnlyList, ResolveAvatar, digestLines, BuildResultFooterText()); PlayResultSfx((Team)winningTeam); } } catch (Exception ex) { WLog.Line("result_screen_error", false, ("err", ex.Message)); } } private void PlayResultSfx(Team winningTeam) { string clipKey; switch (winningTeam) { case Team.Werewolves: clipKey = "sfx_result_wolves_win"; break; case Team.Villagers: clipKey = "sfx_result_villagers_win"; break; default: return; } EnsureSfxBuilt(); _sfxPlayer.Play(clipKey); } private static void SetCrownRosterFromRows(IReadOnlyList rows) { List list = new List(); foreach (ResultRow row in rows) { if (row.IsWinningSide) { list.Add(row.ActorNumber); } } CrownRoster.SetWinners(list); WLog.Line("crown_roster_set", false, ("count", list.Count)); } private void EnsureRolesUiBuilt() { EnsurePanelBuilt(_gaugePanel); EnsurePanelBuilt(_playGaugePanel); EnsurePanelBuilt(_hudPanel); EnsurePanelBuilt(_toastPanel); EnsurePanelBuilt(_wolfStatusPanel); EnsurePanelBuilt(_corpseReportHud); EnsurePanelBuilt(_valuableRecordHud); } private void PushToast(SessionNotice notice) { if (notice == null) { return; } string text = NoticeCatalog.Format(notice); if (!string.IsNullOrEmpty(text)) { if (_toastQueue == null) { int durationSec = ((Plugin.GameConfig != null && Plugin.GameConfig.ToastDurationSec > 0) ? Plugin.GameConfig.ToastDurationSec : 9); _toastQueue = new ToastQueue(durationSec); } EnsureRolesUiBuilt(); _toastQueue.Push(text, NowUnixMs()); EnsureSfxBuilt(); _sfxPlayer.Play(NoticeSfx.Resolve(notice)); WLog.Line("toast_push", false, ("kind", notice.Kind)); } } private void HandleRoleState(byte subtype, int[] data, long timeUnixMs) { switch (subtype) { case 0: { if (data == null || data.Length < 1) { break; } int num2 = data[0]; RolesClient.ApplyCurseStarted(num2, timeUnixMs); PushToast(SessionNotice.ForBlackCatRevealed(ResolveDisplayName(num2))); _executionSfxWaitTicks = -1; EnsureSfxBuilt(); _sfxPlayer.Play("sfx_execution_curse"); WLog.Line("recv_cursestart", false, ("cat", num2), ("deadline", timeUnixMs)); if (num2 == LocalActor) { AttachCurseSource(); MaybeShowTutorial(TutorialId.BlackCatSelectedForExecution); break; } if (_votePanel.Exists) { _votePanel.ShowStatusBanner(Texts.Format(TextId.CurseBlackCatRevealedFormat, ResolveDisplayName(num2))); _votePanel.SetTimeOverride(timeUnixMs); } MaybeShowTutorial(TutorialId.BlackCatExecutionRevealed); break; } case 1: if (data != null && data.Length >= 1) { int num = data[0]; PushToast(SessionNotice.ForCurseVictim(ResolveDisplayName(num))); RolesClient.ApplyCurseResolved(); DetachCurseSource("resolved"); if (_votePanel.Exists) { _votePanel.ShowStatusBanner((num != int.MinValue) ? Texts.Format(TextId.NoticeCurseVictimFormat, ResolveDisplayName(num)) : Texts.Get(TextId.CurseNoVictim)); _votePanel.SetTimeOverride(0L); } WLog.Line("recv_curseresolve", false, ("victim", num)); } break; case 2: RolesClient.ApplyMeetingGauge(data); WLog.Line("recv_meetinggauge", false, ("ratio", (data != null && data.Length != 0) ? data[0] : (-1))); break; } } private void AttachCurseSource() { if (!_votePanel.Exists) { WLog.Line("curse_ui_skipped", false, ("reason", "no_vote_panel")); return; } IReadOnlyList readOnlyList2; if (_session == null) { IReadOnlyList readOnlyList = Registry.BuildRealPlayers(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = _session.Players; } IReadOnlyList roster = readOnlyList2; _curseSource = new CurseTargetSource(RolesClient, _meetingClient, roster, NowUnixMs, SendCurseDesignate); _votePanel.SetSelectionSource(_curseSource); _votePanel.SetTimeOverride(RolesClient.CurseDeadlineUnixMs); WLog.Line("curse_ui_attached", false); } private void DetachCurseSource(string via) { if (_curseSource != null) { _curseSource = null; _votePanel?.SetSelectionSource(null); _votePanel?.SetTimeOverride(0L); WLog.Line("curse_ui_detached", false, ("via", via)); } } private void SendCurseDesignate(int targetActor) { SendRoleAction(0, targetActor, 0); } private void SendRoleAction(byte subtype, int arg, byte flag) { if (_bus == null) { WLog.Line("roleaction_send_fail", true, ("subtype", subtype), ("reason", "no_bus")); } else { _bus.SendToMaster(174, new object[3] { subtype, arg, flag }); } } private void ResetRolesClient(string via) { DetachCurseSource(via); RolesClient.Reset(); _perkEffects.ResetEffects(); _hudPanel?.Hide(); _wolfStatusPanel?.Hide(); _wolfStatusModel.Reset(); } private string ResolveDisplayName(int actorNumber) { if (_session != null) { foreach (WPlayer player in _session.Players) { if (player != null && player.ActorNumber == actorNumber && !string.IsNullOrEmpty(player.Name)) { return player.Name; } } return "#" + actorNumber; } if (_displayNameCache != null && _displayNameCache.TryGetValue(actorNumber, out var value)) { return value; } if (_displayNameCache == null) { _displayNameCache = new Dictionary(); } foreach (WPlayer item in Registry.BuildRealPlayers()) { if (item != null && !string.IsNullOrEmpty(item.Name)) { _displayNameCache[item.ActorNumber] = item.Name; } } if (_displayNameCache.TryGetValue(actorNumber, out var value2)) { return value2; } return "#" + actorNumber; } private void TickShamanClient(long now) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) try { if (!IsRoundActiveClient || _localRole != Role.Shaman) { HideShamanUi(); _shamanLastTickTime = 0f; _shamanStartCooldownArmed = false; return; } float unscaledTime = Time.unscaledTime; float num = ((_shamanLastTickTime > 0f) ? (unscaledTime - _shamanLastTickTime) : 0f); _shamanLastTickTime = unscaledTime; if (num < 0f) { num = 0f; } if (num > 0.5f) { num = 0.5f; } EnsureShamanSense(); if (!_shamanStartCooldownArmed) { _shamanSense.BeginCooldown(); _shamanStartCooldownArmed = true; } bool flag = ShamanSenseGate.ShouldSuspend(ClientPhase, IsLocalAlive(), _meetingClient.MeetingActive, _meetingClient.WarpDone(now), _meetingClient.Kind); float? nearestDistanceMeters = null; bool corpseInView = false; bool stationary = false; if (!flag) { PlayerAvatar val = ResolveAvatar(LocalActor); if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform != (Object)null) { Vector3 position = ((Component)val).transform.position; bool flag2 = false; if (_shamanLastLocalPos.HasValue && num > 0f) { flag2 = Vector3.Distance(position, _shamanLastLocalPos.Value) / num < 0.5f; } _shamanLastLocalPos = position; bool flag3 = false; Camera main = Camera.main; if ((Object)(object)main != (Object)null) { Vector3 forward = ((Component)main).transform.forward; flag3 = _shamanGazeLock.Update(forward.x, forward.y, forward.z); } else { _shamanGazeLock.Reset(); } stationary = flag2 && flag3; float bestDist; Vector3? val2 = FindNearestUnannouncedCorpse(position, out bestDist); if (val2.HasValue) { nearestDistanceMeters = bestDist; corpseInView = IsInLocalViewFrustum(val2.Value); } } else { _shamanLastLocalPos = null; _shamanGazeLock.Reset(); } } else { _shamanLastLocalPos = null; _shamanGazeLock.Reset(); } ShamanStormTier shamanStormTier = _shamanSense.TickStorm(nearestDistanceMeters, flag, ClientShamanStormWeakMeters, ClientShamanStormMediumMeters, ClientShamanStormStrongMeters); bool dripFired; bool num2 = _shamanSense.TickGaze(corpseInView, stationary, num, flag, out dripFired); if (shamanStormTier != ShamanStormTier.None) { MaybeShowTutorial(TutorialId.ShamanStormEntered); } if (dripFired) { EnsureSfxBuilt(); _sfxPlayer.Play("sfx_shaman_drip", 0.7f); EnsurePanelBuilt(_shamanPanel); _shamanPanel.PlayDripRipple(); MaybeShowTutorial(TutorialId.ShamanTranceEntered); } if (num2) { WLog.Line("shaman_ghost", true, ("dist", nearestDistanceMeters.HasValue ? ((int)nearestDistanceMeters.Value) : (-1))); EnsureSfxBuilt(); _sfxPlayer.Play("sfx_shaman_storm"); _sfxPlayer.Play(NextShamanSobKey()); MaybeShowTutorial(TutorialId.ShamanGhostSighted); } EnsurePanelBuilt(_shamanPanel); if (_shamanPanel.Exists) { ShamanStormTier tier = ((!LastRunGate.IsLastRunActive()) ? shamanStormTier : ShamanStormTier.None); _shamanPanel.Tick(tier, _shamanSense.GhostVisible, _shamanSense.GazeArmed, num); } if (shamanStormTier != ShamanStormTier.None) { EnsureSfxBuilt(); _sfxPlayer.PlayLoop("sfx_shaman_storm", StormLoopVolume(shamanStormTier)); } else { _sfxPlayer.StopLoop(); } } catch (Exception ex) { WLog.Line("shaman_client_tick_error", false, ("err", ex.Message)); } } private Vector3? FindNearestUnannouncedCorpse(Vector3 fromPos, out float bestDist) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010d: 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) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) bestDist = float.MaxValue; Vector3? result = null; GameDirector instance = GameDirector.instance; if ((Object)(object)instance != (Object)null && instance.PlayerList != null && Registry != null && Registry.Available) { foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } int actorNumber = Registry.ResolveActor(player); if (_meetingClient.IsDeadUnannounced(actorNumber) && TruckWarper.TryGetDeathHeadPosition(player, out var pos)) { float num = Vector3.Distance(fromPos, pos); if (num < bestDist) { bestDist = num; result = pos; } } } } if (FakeBodies.Any) { foreach (var (actorNumber2, val) in FakeBodies.Snapshot()) { if (_meetingClient.IsDeadUnannounced(actorNumber2)) { float num2 = Vector3.Distance(fromPos, val); if (num2 < bestDist) { bestDist = num2; result = val; } } } } return result; } private static bool IsInLocalViewFrustum(Vector3 worldPos) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return false; } Vector3 val = main.WorldToViewportPoint(worldPos); if (val.z > 0f && val.x >= 0f && val.x <= 1f && val.y >= 0f) { return val.y <= 1f; } return false; } private void EnsureShamanSense() { float num = ClientShamanGazeFullSec; if (num <= 0f) { num = 5f; } float num2 = ClientShamanGhostCooldownSec; if (num2 < 0f) { num2 = 0f; } if (_shamanSense == null || Mathf.Abs(num - _shamanGazeFullSecSnapshot) > 0.01f || Mathf.Abs(num2 - _shamanCooldownSecSnapshot) > 0.01f) { _shamanSense = new ShamanSense(num, num2); _shamanGazeFullSecSnapshot = num; _shamanCooldownSecSnapshot = num2; } } private void HideShamanUi() { _shamanPanel?.Hide(); _sfxPlayer.StopLoop(); } private string NextShamanSobKey() { int num = Random.Range(0, 11); if (num == _lastShamanSobIndex) { num = (num + 1) % 11; } _lastShamanSobIndex = num; return $"sfx_shaman_sob_{num + 1:00}"; } private static float StormLoopVolume(ShamanStormTier tier) { return tier switch { ShamanStormTier.Strong => 1f, ShamanStormTier.Medium => 0.6f, ShamanStormTier.Weak => 0.3f, _ => 0f, }; } private void ResetShamanClient() { _shamanSense?.Reset(); _shamanLastTickTime = 0f; _shamanStartCooldownArmed = false; _lastShamanSobIndex = -1; _shamanLastLocalPos = null; _shamanGazeLock.Reset(); HideShamanUi(); } internal bool IsWerewolfRoundExpected() { if (SemiFunc.IsMasterClientOrSingleplayer()) { return LiveWerewolfModeEnabled(); } return _lastPanelModeEnabled; } private void TickStartHold(long now) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 bool flag = SemiFunc.RunIsLevel(); bool operable = flag && (Object)(object)GameDirector.instance != (Object)null && (int)GameDirector.instance.currentState == 2; StartHoldPhase phase = _startHold.Phase; long num = _startHold.HeldMs(now); StartHoldRelease released; bool flag2 = _startHold.Tick(flag, operable, IsWerewolfRoundExpected(), _gameStartUnixMsClient > 0, now, out released); if (phase != StartHoldPhase.Holding && _startHold.Phase == StartHoldPhase.Holding) { WLog.Line("start_hold_begin", false); if (_uiManager.EnsureCreated(((Component)this).gameObject)) { EnsurePanelBuilt(_startHoldOverlay); } } if (released != StartHoldRelease.None) { WLog.Line("start_hold_end", false, ("reason", (released == StartHoldRelease.GameStart) ? "game_start" : "failsafe"), ("heldMs", num)); } if (_startHold.LateGameStartGapMs >= 0) { WLog.Line("start_hold_late_gamestart", false, ("gapMs", _startHold.LateGameStartGapMs)); } if (flag2) { SemiFunc.InputDisableMovement(); PhysGrabber instance = PhysGrabber.instance; if ((Object)(object)instance != (Object)null) { instance.OverrideGrabDisable(0.1f); } } _startHoldOverlay.Tick(flag2); } internal void MaybeShowTutorial(TutorialId id) { bool blackCatCurseEnabled = IsBlackCatCurseEnabledForClient(); if (!TutorialCatalog.ShouldShow(id, blackCatCurseEnabled)) { return; } ConfigBindings bindings = Plugin.Bindings; if (bindings != null && !bindings.IsTutorialSeen(id) && !_tutorialPresenter.IsPending(id) && (IsLocalAlive() || id == TutorialId.FirstDeath || id == TutorialId.BlackCatSelectedForExecution)) { string text = TutorialCatalog.Format(id, blackCatCurseEnabled); if (!string.IsNullOrEmpty(text)) { _tutorialPresenter.Enqueue(id, text); } } } private bool IsBlackCatCurseEnabledForClient() { if (SettingsCatalog.TryDecodeBlob(ResolveLobbySettingsBlob(), out var values) && values.TryGetValue("BlackCatCurseEnabled", out var value)) { return value == "1"; } return true; } private void TickTutorialPresenter() { ConfigBindings bindings = Plugin.Bindings; if (bindings != null) { _tutorialPresenter.FontScale = bindings.TutorialFontScale.Value; } TutorialId? tutorialId = _tutorialPresenter.Tick(); if (tutorialId.HasValue) { bindings?.MarkTutorialSeen(tutorialId.Value); WLog.Line("tutorial_shown", false, ("id", tutorialId.Value)); } } private void TickTutorialTriggers() { if (!IsRoundActiveClient) { _wasRoleRevealVisible = false; return; } if (_localRole == Role.Werewolf && RolesClient.UnlockedFlags != PerkFlags.None) { MaybeShowTutorial(TutorialId.WolfModeFirstUnlock); } if (_localRole == Role.Werewolf && PerkFlagsUtil.Has(RolesClient.UnlockedFlags, PerkId.EnemyIgnore)) { MaybeShowTutorial(TutorialId.EnemyIgnoreUnlockedAsWerewolf); } if (_localRole == Role.Werewolf && PerkFlagsUtil.Has(RolesClient.UnlockedFlags, PerkId.NaturalHeal)) { MaybeShowTutorial(TutorialId.NaturalHealUnlockedAsWerewolf); } if (_localRole == Role.Werewolf && RolesClient.PlayGauge != null && RolesClient.PlayGauge.InformantPct > 0 && RolesClient.RatioPermille >= RolesClient.PlayGauge.InformantPct * 10) { MaybeShowTutorial(TutorialId.InformantUnlockedAsWerewolf); } if (RolesClient.BeaconCharges > 0) { MaybeShowTutorial(TutorialId.BeaconFirstCharged); } int beaconCharges = RolesClient.BeaconCharges; if (beaconCharges < _prevBeaconCharges && _localRole == Role.Werewolf) { MaybeShowTutorial(TutorialId.BeaconFirstUsedAsWerewolf); } _prevBeaconCharges = beaconCharges; if (LastRunGate.IsLastRunActive()) { MaybeShowTutorial(TutorialId.LastRunApproaching); } if (_clientPhase == GamePhase.Play && !_meetingClient.MeetingActive && _clientRoundEndUnixMs > 0) { long num = _clientRoundEndUnixMs - NowUnixMs(); if (num > 0 && BellSchedule.AlertActive(num)) { MaybeShowTutorial(LocalIsVillagerTeam ? TutorialId.RoundTimeWarningVillager : TutorialId.RoundTimeWarningWerewolf); } } if (LastRunGate.IsAllExtractionCompleted()) { MaybeShowTutorial(LocalIsVillagerTeam ? TutorialId.FinalExtractionVillager : TutorialId.FinalExtractionWerewolf); } bool visible = _revealCinematic.Visible; if (_wasRoleRevealVisible && !visible) { if (_localRole == Role.Werewolf) { MaybeShowTutorial(TutorialId.WerewolfRoleDrawn); } else if (_localRole == Role.BlackCat) { MaybeShowTutorial(TutorialId.BlackCatRoleDrawn); } else if (_localRole == Role.Bomber) { MaybeShowTutorial(TutorialId.BomberRoleDrawn); } else if (_localRole == Role.Shaman) { MaybeShowTutorial(TutorialId.ShamanRoleDrawn); } } _wasRoleRevealVisible = visible; } } internal static class WorldgenApplier { private const string ChargeTotalStatKey = "chargingStationChargeTotal"; private const string PowerCrystalItemKey = "Item Power Crystal"; private const string ItemsPurchasedDictName = "itemsPurchased"; private const string PlayerHealthDictName = "playerHealth"; private const string HealthUpgradeName = "Health"; private const int DefaultPlayerHealth = 100; private static readonly FieldRef DebugLevelRef = GameRefs.RunManager_debugLevel; private static readonly FieldRef RestartingRef = GameRefs.RunManager_restarting; private static readonly FieldRef SaveFileCurrentRef = GameRefs.StatsManager_saveFileCurrent; private static readonly FieldRef>> DictOfDictsRef = GameRefs.StatsManager_dictionaryOfDictionaries; private static readonly FieldRef PunPhotonViewRef = GameRefs.PunManager_photonView; internal static bool DebugLevelArmed { get; private set; } internal static bool CustomEnvironmentActive { get; private set; } internal static string MarkedSaveFileName { get; private set; } = ""; internal static void ClearDebugLevel() { if (!DebugLevelArmed) { return; } DebugLevelArmed = false; try { RunManager instance = RunManager.instance; if ((Object)(object)instance != (Object)null && DebugLevelRef != null) { DebugLevelRef.Invoke(instance) = null; } } catch (Exception ex) { WLog.Line("worldgen_map_error", false, ("part", "clear_debug_level"), ("err", ex.Message)); } } internal static void ClearSessionMarker() { CustomEnvironmentActive = false; MarkedSaveFileName = ""; } internal static void TryApplyOnDeparture(ChangeLevelType changeLevelType) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Invalid comparison between Unknown and I4 //IL_0065: Unknown result type (might be due to invalid IL or missing references) try { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("worldgen_apply_skip", false, ("reason", "no_runmanager")); } else { if ((RestartingRef != null && RestartingRef.Invoke(instance)) || !SemiFunc.IsMasterClientOrSingleplayer() || ((int)changeLevelType != 1 && ((int)changeLevelType != 0 || !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobby)))) { return; } Plugin.RefreshGameConfig(); GameConfig gameConfig = Plugin.GameConfig; if (gameConfig == null) { WLog.Line("worldgen_apply_skip", false, ("reason", "no_config")); } else { if (!gameConfig.WerewolfModeEnabled) { return; } int num = ((!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) ? 1 : PhotonNetwork.CurrentRoom.PlayerCount); if (!PlayerCountGate.IsSatisfied(num, gameConfig.DebugMode)) { WLog.Line("worldgen_apply_skip", false, ("reason", "too_few_players"), ("players", num), ("required", 3)); return; } if (!gameConfig.DebugMode) { IReadOnlyList readOnlyList = ConfigValidator.Validate(gameConfig, num); if (readOnlyList.Count > 0) { WLog.Line("worldgen_apply_skip", false, ("reason", "invalid_config"), ("issues", string.Join(",", readOnlyList)), ("players", num)); return; } } StatsManager instance2 = StatsManager.instance; PunManager instance3 = PunManager.instance; if ((Object)(object)instance2 == (Object)null || (Object)(object)instance3 == (Object)null) { WLog.Line("worldgen_apply_error", false, ("err", ((Object)(object)instance2 == (Object)null) ? "no_statsmanager" : "no_punmanager")); return; } WorldgenPlan worldgenPlan = WorldgenPlanner.BuildPlan(gameConfig, GameItemDatabase.Instance); instance.levelsCompleted = worldgenPlan.LevelsCompleted; instance.UpdateMoonLevel(); SemiFunc.StatSetRunLevel(worldgenPlan.LevelsCompleted); ApplyForcedLevel(instance, gameConfig.StartMapName, worldgenPlan.ForcedLevelName); ApplyItemsAndCharge(instance2, instance3, worldgenPlan); ApplyUpgrades(instance2, instance3, worldgenPlan); CustomEnvironmentActive = true; MarkedSaveFileName = ReadSaveFileName(instance2); WLog.Line("worldgen_applied", false, ("level", worldgenPlan.LevelsCompleted + 1), ("map", worldgenPlan.ForcedLevelName ?? "random"), ("items", worldgenPlan.Items.Count), ("charge", worldgenPlan.ChargeTotal), ("upgrades", worldgenPlan.Upgrades.Count), ("orb", worldgenPlan.OrbDropMax)); } } } catch (Exception ex) { WLog.Line("worldgen_apply_error", false, ("err", ex.Message)); } } private static void ApplyForcedLevel(RunManager run, string configuredMapName, string forcedLevelName) { if (forcedLevelName == null) { if (!string.IsNullOrWhiteSpace(configuredMapName)) { WLog.Line("worldgen_map_fallback", false, ("name", configuredMapName.Trim()), ("reason", "not_found")); } return; } Level val = null; if (run.levels != null) { foreach (Level level in run.levels) { if ((Object)(object)level != (Object)null && string.Equals(((Object)level).name, forcedLevelName, StringComparison.Ordinal)) { val = level; break; } } } if ((Object)(object)val == (Object)null || DebugLevelRef == null) { WLog.Line("worldgen_map_fallback", false, ("name", forcedLevelName), ("reason", ((Object)(object)val == (Object)null) ? "resolve_failed" : "field_unresolved")); } else { DebugLevelRef.Invoke(run) = val; DebugLevelArmed = true; } } private static void ApplyItemsAndCharge(StatsManager stats, PunManager pun, WorldgenPlan plan) { foreach (string item in (stats.itemsPurchased != null) ? new List(stats.itemsPurchased.Keys) : new List()) { if (!(item == "Item Power Crystal") && !plan.Items.ContainsKey(item)) { pun.UpdateStat("itemsPurchased", item, 0); } } foreach (KeyValuePair item2 in plan.Items) { pun.UpdateStat("itemsPurchased", item2.Key, item2.Value); } pun.UpdateStat("itemsPurchased", "Item Power Crystal", plan.PowerCrystals); pun.SetRunStatSet("chargingStationChargeTotal", plan.ChargeTotal); } private static void ApplyUpgrades(StatsManager stats, PunManager pun, WorldgenPlan plan) { //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) if (plan.Upgrades.Count == 0) { return; } if (DictOfDictsRef == null) { WLog.Line("worldgen_upgrades_error", false, ("err", "dict_field_unresolved")); return; } SortedDictionary> dicts = DictOfDictsRef.Invoke(stats); if (dicts == null) { WLog.Line("worldgen_upgrades_error", false, ("err", "dict_null")); return; } bool flag = SemiFunc.IsMultiplayer(); PhotonView val = ((PunPhotonViewRef != null) ? PunPhotonViewRef.Invoke(pun) : null); if (flag && (Object)(object)val == (Object)null) { WLog.Line("worldgen_upgrades_error", false, ("err", "photonview_unresolved")); return; } GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { WLog.Line("worldgen_upgrades_error", false, ("err", "no_playerlist")); return; } foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } string steamID = SemiFunc.PlayerGetSteamID(player); if (string.IsNullOrEmpty(steamID)) { continue; } Dictionary value2; foreach (var item3 in WorldgenUpgradeDeltas.Compute(plan.Upgrades, (string name) => (dicts.TryGetValue("playerUpgrade" + name, out value2) && value2 != null) ? value2.GetValueOrDefault(steamID, 0) : 0)) { string item = item3.Name; int item2 = item3.Delta; int num = -1; if (item == "Health") { num = WorldgenStartHealth.Compute((dicts.TryGetValue("playerHealth", out var value) && value != null) ? value.GetValueOrDefault(steamID, 100) : 100, item2, plan.Upgrades["Health"]); } if (flag) { val.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, item, item2 }); } else { pun.TesterUpgradeCommandRPC(steamID, item, item2, default(PhotonMessageInfo)); } if (num >= 0) { pun.UpdateStat("playerHealth", steamID, num); } } } } internal static void RestoreOnLobbyReturn() { try { if (!SemiFunc.IsMasterClientOrSingleplayer()) { return; } RunManager instance = RunManager.instance; if (!((Object)(object)instance == (Object)null)) { instance.levelsCompleted = 0; SemiFunc.StatSetRunLevel(0); GameConfig gameConfig = Plugin.GameConfig; if (gameConfig != null && gameConfig.WerewolfModeEnabled) { TryResetSessionState(); } ClearDebugLevel(); } } catch (Exception ex) { WLog.Line("worldgen_restore_error", false, ("err", ex.Message)); } } private static void TryResetSessionState() { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { WLog.Line("worldgen_save_reset_skip", false, ("reason", "no_statsmanager")); return; } string text = ReadSaveFileName(instance); bool flag = false; bool flag2 = false; bool flag3 = false; try { instance.ResetAllStats(); flag = true; } catch (Exception ex) { WLog.Line("worldgen_save_reset_error", false, ("step", "reset_all_stats"), ("err", ex.Message)); } try { SemiFunc.SaveFileDelete(text); flag2 = true; } catch (Exception ex2) { WLog.Line("worldgen_save_reset_error", false, ("step", "save_file_delete"), ("err", ex2.Message)); } try { SemiFunc.SaveFileCreate(); flag3 = true; } catch (Exception ex3) { WLog.Line("worldgen_save_reset_error", false, ("step", "save_file_create"), ("err", ex3.Message)); } string item = ReadSaveFileName(instance); WLog.Line("worldgen_save_reset", false, ("old", text), ("new", item), ("resetStats", flag ? 1 : 0), ("deletedDisk", flag2 ? 1 : 0), ("createdNew", flag3 ? 1 : 0)); } internal static string GetCurrentSaveFileName() { try { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return ""; } return (SaveFileCurrentRef != null) ? (SaveFileCurrentRef.Invoke(instance) ?? "") : ""; } catch (Exception ex) { WLog.Line("worldgen_field_error", false, ("type", "StatsManager"), ("field", "saveFileCurrent"), ("err", ex.Message)); return ""; } } private static string ReadSaveFileName(StatsManager stats) { try { return (SaveFileCurrentRef != null) ? (SaveFileCurrentRef.Invoke(stats) ?? "") : ""; } catch (Exception ex) { WLog.Line("worldgen_field_error", false, ("type", "StatsManager"), ("field", "saveFileCurrent"), ("err", ex.Message)); return ""; } } } } namespace Werewolf.Game.Patches { [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class AutoStartPatch { private static void Postfix() { try { WerewolfDirector.Instance?.OnLevelGenerated(); } catch (Exception ex) { WLog.Line("patch_autostart_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(ChatManager), "MessageSend")] internal static class ChatCommandPatch { private static readonly FieldRef ChatMessageRef = GameRefs.ChatManager_chatMessage; private static bool Prefix(ChatManager __instance) { try { if (ChatMessageRef == null) { return true; } string message = ChatMessageRef.Invoke(__instance); if (!CommandGate.TryParse(message, out var _, out var _)) { return true; } CheatCommands.Execute(message); ChatMessageRef.Invoke(__instance) = ""; return false; } catch (Exception ex) { WLog.Line("patch_chat_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSendRPC")] internal static class ChatLogPatch { private static void Postfix(PlayerAvatar __instance, string _message) { try { if (!((Object)(object)__instance == (Object)null) && !string.IsNullOrEmpty(_message)) { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsRoundActiveClient && instance.ClientPhase == GamePhase.Meeting && DeadTextGuard.ShouldShow(__instance)) { instance.RecordMeetingChatClient(__instance, _message); } } } catch (Exception ex) { WLog.Line("chat_log_patch_error", false, ("err", ex.Message)); } } } internal static class CosmeticBoxSpawnPatch { internal static bool Armed; private static bool _blockLogged; private static readonly FieldRef RestartingRef = GameRefs.RunManager_restarting; private static bool IsRunLevelTransition(RunManager run, ChangeLevelType changeLevelType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) if ((int)changeLevelType != 1) { if ((int)changeLevelType == 0) { return (Object)(object)run.levelCurrent == (Object)(object)run.levelLobby; } return false; } return true; } internal static void LogBlockOnce() { if (!_blockLogged) { _blockLogged = true; WLog.Line("cosmetic_spawn_blocked", false); } } private static void ResetLogThrottle() { _blockLogged = false; } internal static void ReevaluateGate(RunManager run, ChangeLevelType changeLevelType) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)run == (Object)null) && (RestartingRef == null || !RestartingRef.Invoke(run)) && SemiFunc.IsMasterClientOrSingleplayer() && IsRunLevelTransition(run, changeLevelType)) { Plugin.RefreshGameConfig(); Armed = Plugin.GameConfig?.WerewolfModeEnabled ?? false; ResetLogThrottle(); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class CosmeticBoxGatePatch { private static void Prefix(RunManager __instance, ChangeLevelType _changeLevelType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { CosmeticBoxSpawnPatch.ReevaluateGate(__instance, _changeLevelType); } catch (Exception ex) { WLog.Line("cosmetic_gate_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(ValuableDirector), "SpawnCosmeticWorldObject")] internal static class CosmeticBoxSkipPatch { private static bool Prefix() { try { if (!CosmeticBoxSpawnPatch.Armed) { return true; } CosmeticBoxSpawnPatch.LogBlockOnce(); return false; } catch (Exception ex) { WLog.Line("cosmetic_skip_error", false, ("err", ex.Message)); return true; } } } internal static class DeadIsolation { internal static bool RoundActive() { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance != (Object)null) { return instance.IsRoundActiveClient; } return false; } } [HarmonyPatch(typeof(PlayerDeathHead), "SpectatedSet")] internal static class DeathHeadSpectatePatch { private static readonly HashSet _loggedInstanceIds = new HashSet(); private static bool _wasRoundActive; private static bool Prefix(bool _active, PlayerDeathHead __instance) { try { if (!_active) { return true; } if (!DeadIsolation.RoundActive()) { if (_wasRoundActive) { _wasRoundActive = false; _loggedInstanceIds.Clear(); } return true; } _wasRoundActive = true; int num = (((Object)(object)__instance != (Object)null) ? ((Object)__instance).GetInstanceID() : 0); if (_loggedInstanceIds.Add(num)) { WLog.Line("deathhead_blocked", false, ("active", _active), ("instId", num)); } return false; } catch (Exception ex) { WLog.Line("patch_deathhead_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(PlayerAvatar), "Revive")] internal static class RevivePatch { private static bool Prefix() { try { if (!DeadIsolation.RoundActive()) { return true; } WLog.Line("revive_blocked", false, ("via", "Revive")); return false; } catch (Exception ex) { WLog.Line("patch_revive_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")] internal static class ReviveRpcPatch { private static bool Prefix() { try { if (!DeadIsolation.RoundActive()) { return true; } WLog.Line("revive_blocked", false, ("via", "ReviveRPC")); return false; } catch (Exception ex) { WLog.Line("patch_revive_error", false, ("err", ex.Message)); return true; } } } internal static class DeadTextGuard { internal static bool ShouldShow(PlayerAvatar player) { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.IsRoundActiveClient) { return true; } int speakerActor = instance.Registry.ResolveActor(player); return instance.ShouldShowDeadTextClient(speakerActor); } } [HarmonyPatch(typeof(WorldSpaceUIParent), "TTS")] internal static class DeadTtsBubblePatch { private static bool Prefix(PlayerAvatar _player) { try { if ((Object)(object)_player == (Object)null) { return true; } if (DeadTextGuard.ShouldShow(_player)) { return true; } WLog.Line("dead_tts_bubble_blocked", false); return false; } catch (Exception ex) { WLog.Line("patch_dead_tts_error", false, ("via", "bubble"), ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSpeak")] internal static class DeadTtsSpeakPatch { private static bool Prefix(PlayerAvatar __instance) { try { if (DeadTextGuard.ShouldShow(__instance)) { return true; } WLog.Line("dead_tts_speak_blocked", false); return false; } catch (Exception ex) { WLog.Line("patch_dead_tts_error", false, ("via", "speak"), ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(RunManager), "Update")] internal static class DirectorRevivePatch { private const int MaxRevives = 10; private static int _reviveCount; private static bool _disabled; private static bool _firstTickLogged; private static void Postfix(RunManager __instance) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!_firstTickLogged) { _firstTickLogged = true; (string, object)[] obj = new(string, object)[2] { ("directorAlive", (Object)(object)WerewolfDirector.Instance != (Object)null), default((string, object)) }; Scene activeScene = SceneManager.GetActiveScene(); obj[1] = ("scene", ((Scene)(ref activeScene)).name); WLog.Line("director_watchdog_first_tick", secret: false, obj); } if (_disabled || (Object)(object)WerewolfDirector.Instance != (Object)null) { return; } try { if (_reviveCount >= 10) { _disabled = true; WLog.Line("director_revive_stopped", false, ("count", _reviveCount)); } else { _reviveCount++; ((Component)__instance).gameObject.AddComponent(); WLog.Line("director_revived", false, ("host", "RunManager"), ("count", _reviveCount)); } } catch (Exception ex) { _disabled = true; WLog.Line("director_revive_error", false, ("err", ex.Message)); } } } internal static class EnemyShadowTargetFilter { internal static bool IsIgnored(PlayerAvatar player) { if ((Object)(object)player == (Object)null || (Object)(object)EnemyDirector.instance == (Object)null) { return false; } string text = SemiFunc.PlayerGetSteamID(player); if (string.IsNullOrEmpty(text)) { return false; } return GameRefs.EnemyDirector_debugNoVision.Invoke(EnemyDirector.instance).Contains(text); } internal static PlayerAvatar PickEligible() { List list = new List(); foreach (PlayerAvatar item in SemiFunc.PlayerGetList()) { if ((Object)(object)item != (Object)null && !GameRefs.PlayerAvatar_isDisabled.Invoke(item) && !IsIgnored(item)) { list.Add(item); } } if (list.Count == 0) { return null; } return list[Random.Range(0, list.Count)]; } } [HarmonyPatch(typeof(EnemyShadow), "UpdatePlayerTarget")] internal static class EnemyShadowUpdatePlayerTargetPatch { private static void Prefix(ref PlayerAvatar _player) { try { if (!((Object)(object)_player == (Object)null) && EnemyShadowTargetFilter.IsIgnored(_player)) { _player = EnemyShadowTargetFilter.PickEligible(); } } catch (Exception ex) { WLog.Line("patch_loomtarget_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(EnemyShadow), "GetAnnoyed")] internal static class EnemyShadowGetAnnoyedPatch { private static bool Prefix(PlayerAvatar _nearbyPlayer) { try { if ((Object)(object)_nearbyPlayer != (Object)null && EnemyShadowTargetFilter.IsIgnored(_nearbyPlayer)) { return false; } } catch (Exception ex) { WLog.Line("patch_loomannoy_error", false, ("err", ex.Message)); } return true; } } [HarmonyPatch(typeof(EnemyShadow), "StateFollow")] internal static class EnemyShadowStateFollowPatch { private static void Prefix(EnemyShadow __instance) { try { PlayerAvatar val = GameRefs.EnemyShadow_playerTarget.Invoke(__instance); if (!((Object)(object)val == (Object)null) && EnemyShadowTargetFilter.IsIgnored(val)) { GameRefs.EnemyShadow_UpdatePlayerTarget.Invoke(__instance, new object[1] { val }); WLog.Line("loom_follow_cut", true); } } catch (Exception ex) { WLog.Line("patch_loomfollow_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(EnemyParent), "Spawn")] internal static class EnemySpawnPatch { internal static volatile bool SuppressSpawns; private static bool Prefix() { try { if (SuppressSpawns) { return false; } } catch (Exception ex) { WLog.Line("patch_enemyspawn_error", false, ("err", ex.Message)); } return true; } } [HarmonyPatch(typeof(PlayerController), "Update")] internal static class InfiniteJumpPatch { private static readonly FieldRef JumpFirst = GameRefs.PlayerController_JumpFirst; private static readonly FieldRef JumpExtraCurrent = GameRefs.PlayerController_JumpExtraCurrent; private static int _refillsThisAirTime; private static bool _injectedChargeAvailable; internal static int RefillsThisAirTime => _refillsThisAirTime; internal static bool InjectedChargeAvailable => _injectedChargeAvailable; private static void Prefix(PlayerController __instance) { try { if (JumpFirst.Invoke(__instance)) { _refillsThisAirTime = 0; _injectedChargeAvailable = false; return; } WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.RolesClient.JumpActive) { _injectedChargeAvailable = false; return; } if (JumpExtraCurrent.Invoke(__instance) > 0) { _injectedChargeAvailable = _refillsThisAirTime > 0; return; } int clientExtraJumpCount = instance.ClientExtraJumpCount; if (clientExtraJumpCount >= 0 && _refillsThisAirTime >= clientExtraJumpCount) { _injectedChargeAvailable = false; return; } JumpExtraCurrent.Invoke(__instance) = 1; _refillsThisAirTime++; _injectedChargeAvailable = true; } catch (Exception ex) { WLog.Line("patch_jump_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(InventorySpot), "AttemptEquipItem")] internal static class InventorySpotEquipContestPatch { private static bool Prefix() { try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return true; } PhysGrabber instance2 = PhysGrabber.instance; if ((Object)(object)instance2 == (Object)null || !instance2.grabbed) { return true; } PhysGrabObject val = GameRefs.PhysGrabber_grabbedPhysGrabObject.Invoke(instance2); if ((Object)(object)val == (Object)null) { return true; } bool grabbedByOtherPlayer = IsGrabbedByOther(val, instance2); if (!CombatRules.ShouldBlockEquipContestedItem(instance.ClientPhase, grabbedByOtherPlayer)) { return true; } instance.MaybeShowTutorial(TutorialId.EquipBlockedByOtherGrabber); WLog.Line("equip_blocked_contested", false, ("phase", instance.ClientPhase)); return false; } catch (Exception ex) { WLog.Line("patch_inventory_equip_error", false, ("err", ex.Message)); return true; } } private static bool IsGrabbedByOther(PhysGrabObject grabbed, PhysGrabber self) { List playerGrabbing = grabbed.playerGrabbing; if (playerGrabbing == null) { return false; } foreach (PhysGrabber item in playerGrabbing) { if ((Object)(object)item != (Object)null && (Object)(object)item != (Object)(object)self) { return true; } } return false; } } internal static class ItemMeleeFriendlyFire { private static readonly FieldRef HurtColliderRef = GameRefs.ItemMelee_hurtCollider; internal static void Apply(ItemMelee melee) { try { if ((Object)(object)melee == (Object)null || melee.swingLogicOnly) { return; } WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return; } HurtCollider val = HurtColliderRef.Invoke(melee); if (!((Object)(object)val == (Object)null)) { int num = CombatRules.OverrideMeleePlayerDamage(instance.ClientPhase, val.playerDamage, val.enemyDamage); if (num != val.playerDamage) { val.playerDamage = num; } } } catch (Exception ex) { WLog.Line("patch_itemmelee_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(ItemMelee), "Start")] internal static class ItemMeleeStartPatch { private static void Postfix(ItemMelee __instance) { ItemMeleeFriendlyFire.Apply(__instance); } } [HarmonyPatch(typeof(ItemMelee), "StateSwinging")] internal static class ItemMeleeStateSwingingPatch { private static void Postfix(ItemMelee __instance) { ItemMeleeFriendlyFire.Apply(__instance); } } [HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")] internal static class ItemMeleePvpDurabilityPatch { private static void Prefix(ref bool _playerHit) { try { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null)) { bool arenaOrShop = SemiFunc.RunIsArena() || SemiFunc.RunIsShop(); bool flag = CombatRules.OverrideMeleePvpDurabilityHit(instance.ClientPhase, _playerHit, arenaOrShop); if (flag != _playerHit) { _playerHit = flag; WLog.Line("melee_pvp_durability", false, ("phase", instance.ClientPhase)); } } } catch (Exception ex) { WLog.Line("patch_itemmelee_durability_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(ItemMelee), "PlayerSwingHit")] internal static class ItemMeleeDisarmPatch { private const float DisarmGrabDisableSeconds = 0.5f; private static void Postfix(ItemMelee __instance) { try { if ((Object)(object)__instance == (Object)null || __instance.swingLogicOnly) { return; } WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null)) { PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); if (!((Object)(object)val == (Object)null) && !IsGrabbing(val, __instance)) { DisarmGrabbed(instance, val); SpillInventory(instance, val); } } } catch (Exception ex) { WLog.Line("patch_itemmelee_disarm_error", false, ("err", ex.Message)); } } private static bool IsGrabbing(PlayerAvatar victim, ItemMelee melee) { PhysGrabber physGrabber = victim.physGrabber; if ((Object)(object)physGrabber == (Object)null || !physGrabber.grabbed) { return false; } PhysGrabObject val = GameRefs.PhysGrabber_grabbedPhysGrabObject.Invoke(physGrabber); if ((Object)(object)val != (Object)null) { return (Object)(object)((Component)val).gameObject == (Object)(object)((Component)melee).gameObject; } return false; } private static void DisarmGrabbed(WerewolfDirector dir, PlayerAvatar victim) { PhysGrabber physGrabber = victim.physGrabber; if ((Object)(object)physGrabber == (Object)null || !physGrabber.grabbed) { return; } PhysGrabObject val = GameRefs.PhysGrabber_grabbedPhysGrabObject.Invoke(physGrabber); if (!((Object)(object)val == (Object)null)) { bool grabbedIsMelee = GameRefs.PhysGrabObject_isMelee.Invoke(val); if (CombatRules.ShouldDisarmGrabbedOnMeleeHit(dir.ClientPhase, grabbing: true, grabbedIsMelee)) { PhotonView component = ((Component)val).GetComponent(); physGrabber.OverrideGrabRelease(((Object)(object)component != (Object)null) ? component.ViewID : (-1), 0.5f); physGrabber.OverrideGrabDisable(0.5f); WLog.Line("melee_disarm_grabbed", false, ("phase", dir.ClientPhase)); } } } private static void SpillInventory(WerewolfDirector dir, PlayerAvatar victim) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if (!CombatRules.ShouldSpillInventoryOnMeleeHit(dir.ClientPhase)) { return; } Inventory instance = Inventory.instance; if ((Object)(object)instance == (Object)null) { return; } List list = new List(); foreach (InventorySpot allSpot in instance.GetAllSpots()) { if (!((Object)(object)allSpot == (Object)null) && allSpot.IsOccupied() && (Object)(object)allSpot.CurrentItem != (Object)null) { list.Add(allSpot.CurrentItem); } } int num = CombatRules.PickSpillSlotIndex(list.Count, Random.value); if (num >= 0) { PhysGrabber physGrabber = victim.physGrabber; int num2 = ((SemiFunc.IsMultiplayer() && (Object)(object)physGrabber != (Object)null) ? physGrabber.photonView.ViewID : (-1)); list[num].ForceUnequip(((Component)victim).transform.position, num2); WLog.Line("melee_disarm_inventory", false, ("phase", dir.ClientPhase), ("slot", num), ("occupied", list.Count)); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class LevelPatch { private static void Prefix(bool _completedLevel, bool _levelFailed) { try { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsHostSessionActive && SemiFunc.IsMasterClientOrSingleplayer() && (instance.HostPhase == GamePhase.Play || instance.HostPhase == GamePhase.Meeting) && SemiFunc.RunIsLevel()) { instance.HostNotifyExtraction(_completedLevel, _levelFailed); } } catch (Exception ex) { WLog.Line("patch_level_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(TruckScreenText), "ChatMessageLevel")] internal static class MailSendWinPatch { private static readonly FieldRef StateRef = GameRefs.TruckScreenText_playerChatBoxState; private static void Postfix(TruckScreenText __instance) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Invalid comparison between Unknown and I4 //IL_009d: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance == (Object)null) { return; } PlayerChatBoxState val = StateRef.Invoke(__instance); if ((int)val != 3 && (int)val != 2) { return; } WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsHostSessionActive && SemiFunc.IsMasterClientOrSingleplayer() && instance.HostPhase == GamePhase.Play && SemiFunc.RunIsLevel()) { if ((int)val == 3) { instance.HostNotifyMailDeparture(); WLog.Line("mail_send_win", false, ("via", "chat_message_level"), ("state", val)); } else { WLog.Line("mail_send_slackers", false, ("via", "chat_message_level")); } } } catch (Exception ex) { WLog.Line("patch_mail_send_error", false, ("err", ex.Message)); } } } internal static class MapHidePatch { private static readonly FieldRef DeathHeadRef = GameRefs.PlayerAvatar_playerDeathHead; internal static bool GateOpen() { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return false; } return MapHideGate.ShouldSuppress(instance.IsRoundActiveClient, instance.ClientMinimapHideEnabled); } internal static void Tick() { try { if (!GateOpen()) { return; } GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null) { return; } List playerList = instance.PlayerList; if (playerList == null) { return; } for (int i = 0; i < playerList.Count; i++) { PlayerAvatar val = playerList[i]; if ((Object)(object)val == (Object)null) { continue; } PlayerDeathHead val2 = DeathHeadRef.Invoke(val); if (!((Object)(object)val2 == (Object)null)) { MapCustom mapCustom = val2.mapCustom; if (!((Object)(object)mapCustom == (Object)null)) { mapCustom.Hide(); } } } } catch (Exception ex) { WLog.Line("maphide_tick_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(MenuCursor), "Show")] internal static class MenuCursorHidePatch { private static bool Prefix() { return !CursorMirror.SuppressVanillaCursor; } } [HarmonyPatch(typeof(MenuPlayerListed), "Update")] internal static class MenuPlayerCrownPatch { private static readonly FieldRef PlayerAvatarRef = GameRefs.MenuPlayerListed_playerAvatar; private static bool _errorLogged; private static int _loggedVersion = -1; private static readonly HashSet _loggedActors = new HashSet(); private static void Postfix(MenuPlayerListed __instance) { try { if (!CrownRoster.HasWinners || __instance.forceCrown) { return; } PlayerAvatar val = PlayerAvatarRef.Invoke(__instance); if ((Object)(object)val == (Object)null) { return; } int num = PlayerCrownPatch.ResolveActor(val); if (!CrownRoster.IsWinner(num)) { return; } bool flag = false; if ((Object)(object)__instance.leftCrown != (Object)null && !__instance.leftCrown.activeSelf) { __instance.leftCrown.SetActive(true); flag = true; } if ((Object)(object)__instance.rightCrown != (Object)null && !__instance.rightCrown.activeSelf) { __instance.rightCrown.SetActive(true); flag = true; } if (flag) { if (_loggedVersion != CrownRoster.Version) { _loggedVersion = CrownRoster.Version; _loggedActors.Clear(); } if (_loggedActors.Add(num)) { WLog.Line("menu_crown_forced", false, ("actor", num)); } } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; WLog.Line("patch_menu_crown_error", false, ("err", ex.Message)); } } } } [HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")] internal static class ModIntegrityLobbyStartPatch { private static bool Prefix(MenuPageLobby __instance) { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null)) { return instance.TryInterceptLobbyStart(__instance); } return true; } } [HarmonyPatch(typeof(MenuPlayerListed), "Update")] internal static class ModIntegrityPlayerBadgePatch { private static readonly FieldRef PlayerAvatarRef = GameRefs.MenuPlayerListed_playerAvatar; private static bool _errorLogged; private static void Postfix(MenuPlayerListed __instance) { try { if (!SemiFunc.RunIsLobbyMenu() || __instance.forceCrown) { ((Component)__instance).GetComponent()?.Hide(); return; } ModIntegrityPlayerBadge orCreate = ModIntegrityPlayerBadge.GetOrCreate(__instance); PlayerAvatar val = PlayerAvatarRef.Invoke(__instance); int num = (((Object)(object)val != (Object)null) ? PlayerCrownPatch.ResolveActor(val) : (-1)); WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || num <= 0 || !instance.TryGetModIntegrityStatus(num, out var record)) { orCreate?.Hide(); } else { orCreate?.SetRecord(record, instance.ModIntegrityRevision); } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; WLog.Line("patch_mod_integrity_badge_error", false, ("err", ex.Message)); } } } } [HarmonyPatch(typeof(MoonUI), "Check")] internal static class MoonUiSkipPatch { private static bool Prefix() { try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.IsWerewolfRoundExpected()) { return true; } RunManager instance2 = RunManager.instance; if ((Object)(object)instance2 == (Object)null || !SemiFunc.RunIsLevel()) { return true; } if (GameRefs.RunManager_moonLevelChanged == null) { return true; } if (!GameRefs.RunManager_moonLevelChanged.Invoke(instance2)) { return true; } GameRefs.RunManager_moonLevelChanged.Invoke(instance2) = false; WLog.Line("moon_ui_skipped", false); return false; } catch (Exception ex) { WLog.Line("patch_moonui_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(WorldSpaceUIPlayerName), "Update")] internal static class NameMarkerPatch { private static void Postfix(WorldSpaceUIPlayerName __instance) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: 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) try { if ((Object)(object)__instance == (Object)null) { return; } WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return; } Role? localRoleClient = instance.LocalRoleClient; if (localRoleClient != Role.BlackCat && localRoleClient != Role.Werewolf && localRoleClient != Role.Bomber) { return; } PlayerAvatar val = ResolvePlayerAvatar(__instance); if ((Object)(object)val == (Object)null) { return; } Role? role = instance.MarkedTeammateRoleForAvatar(val); if (role.HasValue) { TextMeshProUGUI text = __instance.text; if (!((Object)(object)text == (Object)null)) { Color color = ((Graphic)text).color; ((Graphic)text).color = ((role == Role.Bomber) ? new Color(1f, 0.6f, 0.3f, color.a) : new Color(1f, 0.25f, 0.25f, color.a)); } } } catch (Exception ex) { WLog.Line("patch_name_marker_error", false, ("err", ex.Message)); } } private static PlayerAvatar ResolvePlayerAvatar(WorldSpaceUIPlayerName instance) { return GameRefs.WorldSpaceUIPlayerName_playerAvatar.Invoke(instance); } } [HarmonyPatch(typeof(EnemyHealth), "Awake")] internal static class OrbDropPatch { private static bool _errorLogged; private static void Postfix(EnemyHealth __instance) { try { if (!((Object)(object)__instance == (Object)null)) { GameConfig gameConfig = Plugin.GameConfig; if (gameConfig != null && gameConfig.OrbDropMax != 3 && gameConfig.WerewolfModeEnabled && SemiFunc.IsMasterClientOrSingleplayer()) { __instance.spawnValuableMax = gameConfig.OrbDropMax; } } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; WLog.Line("worldgen_orbdrop_error", false, ("err", ex.Message)); } } } } [HarmonyPatch(typeof(PlayerCrown), "FetchLogic")] internal static class PlayerCrownPatch { private static readonly FieldRef PlayerAvatarRef = GameRefs.PlayerCrown_playerAvatar; private static readonly FieldRef ActiveRef = GameRefs.PlayerCrown_active; private static bool _errorLogged; private static int _loggedVersion = -1; private static readonly HashSet _loggedActors = new HashSet(); private static void Postfix(PlayerCrown __instance) { try { if (!CrownRoster.HasWinners) { return; } PlayerAvatar val = PlayerAvatarRef.Invoke(__instance); if ((Object)(object)val == (Object)null) { return; } int num = ResolveActor(val); if (!CrownRoster.IsWinner(num)) { return; } ActiveRef.Invoke(__instance) = true; if ((Object)(object)__instance.crownMesh != (Object)null && !((Component)__instance.crownMesh).gameObject.activeSelf) { ((Component)__instance.crownMesh).gameObject.SetActive(true); if (_loggedVersion != CrownRoster.Version) { _loggedVersion = CrownRoster.Version; _loggedActors.Clear(); } if (_loggedActors.Add(num)) { WLog.Line("crown_forced", false, ("actor", num)); } } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; WLog.Line("patch_crown_error", false, ("err", ex.Message)); } } } internal static int ResolveActor(PlayerAvatar avatar) { if (!SemiFunc.IsMultiplayer()) { return 1; } if ((Object)(object)avatar.photonView != (Object)null && avatar.photonView.Owner != null) { return avatar.photonView.Owner.ActorNumber; } return -1; } } [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")] internal static class PlayerDeathPatch { private static void Postfix(PlayerAvatar __instance) { try { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsHostSessionActive && SemiFunc.IsMasterClientOrSingleplayer() && instance.Registry.IsDeadSet(__instance)) { instance.HostRecordDeath(__instance); } } catch (Exception ex) { WLog.Line("patch_death_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(PlayerHealth), "Hurt")] internal static class PlayerHurtPatch { private static bool Prefix(ref bool savingGrace, int damage) { try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return true; } bool flag = CombatRules.OverrideSavingGrace(instance.ClientPhase, savingGrace); if ((flag != savingGrace) & savingGrace) { WLog.Line("savinggrace_off", false, ("phase", instance.ClientPhase), ("damage", damage)); } savingGrace = flag; return true; } catch (Exception ex) { WLog.Line("patch_playerhurt_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(PlayerAvatar), "SpawnRPC")] internal static class PlayerSpawnPatch { internal static volatile bool MeetingActive; private static void Prefix(PlayerAvatar __instance) { try { if (MeetingActive && !((Object)(object)PlayerAvatar.instance == (Object)null) && !((Object)(object)__instance != (Object)(object)PlayerAvatar.instance)) { PhysGrabber instance = PhysGrabber.instance; if ((Object)(object)instance != (Object)null) { instance.OverrideGrabRelease(-1, 0.1f); } } } catch (Exception ex) { WLog.Line("patch_playerspawn_error", false, ("err", ex.Message)); } } private static void Postfix(PlayerAvatar __instance) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) try { if (MeetingActive && !((Object)(object)PlayerAvatar.instance == (Object)null) && !((Object)(object)__instance != (Object)(object)PlayerAvatar.instance)) { PlayerController instance = PlayerController.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.rb == (Object)null)) { instance.rb.position = ((Component)instance).transform.position; instance.rb.rotation = ((Component)instance).transform.rotation; instance.rb.velocity = Vector3.zero; instance.rb.angularVelocity = Vector3.zero; } } } catch (Exception ex) { WLog.Line("patch_playerspawn_postfix_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(DataDirector), "SaveDeleteCheck")] internal static class SaveGuardPatch { private static void Postfix(bool _leaveGame) { try { if (_leaveGame) { bool customEnvironmentActive = WorldgenApplier.CustomEnvironmentActive; string text = WorldgenApplier.MarkedSaveFileName ?? ""; string currentSaveFileName = WorldgenApplier.GetCurrentSaveFileName(); if (WorldgenSaveGuard.ShouldDelete(_leaveGame, customEnvironmentActive, text, currentSaveFileName)) { SemiFunc.SaveFileDelete(text); WLog.Line("worldgen_saveguard_deleted", false, ("name", text)); } WorldgenApplier.ClearSessionMarker(); } } catch (Exception ex) { WLog.Line("worldgen_saveguard_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(TruckScreenText), "PlayerChatBoxStateUpdateRPC")] internal static class TruckLockWinPatch { private static void Postfix(PlayerChatBoxState _state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 try { if ((int)_state == 3) { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsHostSessionActive && SemiFunc.IsMasterClientOrSingleplayer() && (instance.HostPhase == GamePhase.Play || instance.HostPhase == GamePhase.Meeting) && SemiFunc.RunIsLevel()) { instance.HostNotifyMailDeparture(); WLog.Line("truck_lock_win", false, ("via", "chat_box_state_rpc"), ("phase", instance.HostPhase)); } } } catch (Exception ex) { WLog.Line("patch_truck_lock_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(PhysGrabber), "DiscoverLogic")] internal static class ValuableDiscoverSuppressPatch { private static bool Prefix(PhysGrabber __instance, ValuableObject _valuableObject) { try { if ((Object)(object)_valuableObject == (Object)null) { return true; } if ((Object)(object)__instance == (Object)null || !__instance.isLocal) { return true; } WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.LocalValuableDiscoverSuppressed) { return true; } instance.MaybeShowTutorial(TutorialId.ValuableRecordSuppressed); return false; } catch (Exception ex) { WLog.Line("patch_valuable_discover_error", false, ("err", ex.Message)); return true; } } } internal static class ValuableMapSyncPatch { private struct GhostRef { public GameObject Holder; public GameObject EntityGo; } private static readonly Dictionary _ghosts = new Dictionary(); private static readonly FieldRef DiscoveredRef = GameRefs.ValuableObject_discovered; private static readonly FieldRef MapCustomEntityRef = GameRefs.MapCustom_mapCustomEntity; private static ValuableMapMode _prevMode = ValuableMapMode.MeetingSync; private static bool _prevRoundActive; internal static void RefreshSnapshot(string reason) { //IL_0118: Unknown result type (might be due to invalid IL or missing references) WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)Map.Instance == (Object)null || !ValuableMapGate.ShouldRefreshSnapshotAtInventoryPoint(instance.ClientValuableMapMode, instance.IsRoundActiveClient)) { return; } int num = 0; if ((Object)(object)Map.Instance.OverLayerParent != (Object)null) { MapValuable[] componentsInChildren = ((Component)Map.Instance.OverLayerParent).GetComponentsInChildren(true); foreach (MapValuable val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Object.Destroy((Object)(object)((Component)val).gameObject); num++; } } } int num2 = 0; int num3 = 0; List list = null; foreach (KeyValuePair ghost in _ghosts) { if ((Object)(object)ghost.Key == (Object)null) { DestroyGhost(ghost.Value); (list ?? (list = new List())).Add(ghost.Key); num2++; } else if ((Object)(object)ghost.Value.Holder != (Object)null) { ghost.Value.Holder.transform.position = ((Component)ghost.Key).transform.position; num3++; } } if (list != null) { foreach (ValuableObject item in list) { _ghosts.Remove(item); } } int num4 = 0; ValuableObject[] array = Object.FindObjectsOfType(); foreach (ValuableObject val2 in array) { if (!((Object)(object)val2 == (Object)null) && !_ghosts.ContainsKey(val2) && DiscoveredRef.Invoke(val2) && PlaceSnapshotGhost(val2)) { num4++; } } WLog.Line("valuablemap_snapshot", false, ("reason", reason), ("removed", num2), ("moved", num3), ("added", num4), ("vanilla_removed", num)); } internal static void Tick() { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return; } ValuableMapMode clientValuableMapMode = instance.ClientValuableMapMode; bool isRoundActiveClient = instance.IsRoundActiveClient; bool num = _prevMode == ValuableMapMode.MeetingSync && _prevRoundActive; bool flag = clientValuableMapMode == ValuableMapMode.MeetingSync && isRoundActiveClient; if (!num && flag) { RefreshSnapshot("gate_open_meeting_sync"); } if (num && !flag) { int num2 = ClearAllGhosts(); if (num2 > 0) { WLog.Line("valuablemap_sweep", false, ("reason", "gate_close_meeting_sync"), ("removed", num2)); } RestoreDiscoveredValuables(); } bool num3 = _prevMode == ValuableMapMode.Hidden && _prevRoundActive; bool flag2 = clientValuableMapMode == ValuableMapMode.Hidden && isRoundActiveClient; if (num3 && !flag2) { RestoreDiscoveredValuables(); } _prevMode = clientValuableMapMode; _prevRoundActive = isRoundActiveClient; } internal static void RestoreDiscoveredValuables() { if ((Object)(object)Map.Instance == (Object)null) { return; } int num = 0; ValuableObject[] array = Object.FindObjectsOfType(); foreach (ValuableObject val in array) { if (!((Object)(object)val == (Object)null) && DiscoveredRef.Invoke(val)) { Map.Instance.AddValuable(val); num++; } } WLog.Line("valuablemap_restore", false, ("restored", num)); } internal static bool PlaceSnapshotGhost(ValuableObject valuable) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Invalid comparison between Unknown and I4 //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: 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) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Map.Instance == (Object)null || (Object)(object)valuable == (Object)null) { return false; } if (_ghosts.ContainsKey(valuable)) { return false; } try { Sprite val = null; Color val2 = Color.white; MapValuable val3 = (((Object)(object)Map.Instance.ValuableObject != (Object)null) ? Map.Instance.ValuableObject.GetComponent() : null); if ((Object)(object)val3 != (Object)null) { val = (((int)valuable.volumeType <= 2) ? val3.spriteSmall : val3.spriteBig); if ((Object)(object)val3.spriteRenderer != (Object)null) { val2 = val3.spriteRenderer.color; } } GameObject val4 = new GameObject("WW_ValuableGhost"); val4.transform.SetPositionAndRotation(((Component)valuable).transform.position, Quaternion.identity); MapCustom val5 = val4.AddComponent(); val5.autoAdd = false; val5.sprite = val; val5.color = val2; Map.Instance.AddCustom(val5, val, val2); MapCustomEntity val6 = MapCustomEntityRef.Invoke(val5); GameObject entityGo = (((Object)(object)val6 != (Object)null) ? ((Component)val6).gameObject : null); _ghosts[valuable] = new GhostRef { Holder = val4, EntityGo = entityGo }; (string, object)[] obj = new(string, object)[2] { ("name", ((Object)((Component)valuable).gameObject).name), default((string, object)) }; Vector3 position = ((Component)valuable).transform.position; obj[1] = ("pos", ((Vector3)(ref position)).ToString("F1")); WLog.Line("valuablemap_ghost_placed", secret: false, obj); return true; } catch (Exception ex) { WLog.Line("valuablemap_ghost_error", false, ("err", ex.Message)); return false; } } private static void DestroyGhost(GhostRef g) { if ((Object)(object)g.EntityGo != (Object)null) { Object.Destroy((Object)(object)g.EntityGo); } if ((Object)(object)g.Holder != (Object)null) { Object.Destroy((Object)(object)g.Holder); } } private static int ClearAllGhosts() { int num = 0; foreach (KeyValuePair ghost in _ghosts) { if ((Object)(object)ghost.Value.EntityGo != (Object)null || (Object)(object)ghost.Value.Holder != (Object)null) { num++; } DestroyGhost(ghost.Value); } _ghosts.Clear(); return num; } } [HarmonyPatch(typeof(Map), "AddValuable")] internal static class MapAddValuablePatch { private static bool Prefix(ValuableObject _valuable) { try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { return true; } bool isRoundActiveClient = instance.IsRoundActiveClient; if (isRoundActiveClient) { instance.MaybeShowTutorial(TutorialId.FirstValuableSeen); } ValuableMapMode clientValuableMapMode = instance.ClientValuableMapMode; if (!ValuableMapGate.ShouldSuppressAdd(clientValuableMapMode, isRoundActiveClient)) { return true; } if (ValuableMapGate.ShouldSnapshotOnDiscover(clientValuableMapMode, isRoundActiveClient)) { ValuableMapSyncPatch.PlaceSnapshotGhost(_valuable); } return false; } catch (Exception ex) { WLog.Line("patch_valuablemap_add_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(RoundDirector), "ExtractionCompleted")] internal static class ExtractionCompletedPatch { private static void Postfix() { try { ValuableMapSyncPatch.RefreshSnapshot("extraction_completed"); } catch (Exception ex) { WLog.Line("patch_valuablemap_extraction_error", false, ("err", ex.Message)); } } } internal static class ValueTrackPatch { private static readonly FieldRef DollarValueCurrent = GameRefs.ValuableObject_dollarValueCurrent; private static readonly FieldRef DollarValueOriginal = GameRefs.ValuableObject_dollarValueOriginal; private static readonly FieldRef ValuableRef = GameRefs.PhysGrabObjectImpactDetector_valuableObject; private static readonly FieldRef PhysGrabObjectRef = GameRefs.PhysGrabObjectImpactDetector_physGrabObject; private static readonly HashSet _breakAccountedIds = new HashSet(); private static readonly HashSet _boxAccountedIds = new HashSet(); private static readonly HaulFreeze _haulFreeze = new HaulFreeze(); internal static void MarkBreakAccounted(int instanceId) { _breakAccountedIds.Add(instanceId); } internal static bool ConsumeBreakAccounted(int instanceId) { return _breakAccountedIds.Remove(instanceId); } internal static void MarkBoxAccounted(int instanceId) { _boxAccountedIds.Add(instanceId); } internal static bool ConsumeBoxAccounted(int instanceId) { return _boxAccountedIds.Remove(instanceId); } internal static ValuableObject ResolveValuable(PhysGrabObjectImpactDetector detector) { return ValuableRef.Invoke(detector); } internal static PhysGrabObject ResolvePhysGrabObject(PhysGrabObjectImpactDetector detector) { return PhysGrabObjectRef.Invoke(detector); } internal static float ReadCurrent(ValuableObject valuable) { return DollarValueCurrent.Invoke(valuable); } internal static float ReadOriginal(ValuableObject valuable) { return DollarValueOriginal.Invoke(valuable); } internal static void ScanAndFreezeBase() { CloseHaulFreeze(); WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsHostSessionActive && SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.RunIsLevel()) { float num = 0f; ValuableObject[] array = Object.FindObjectsOfType(); foreach (ValuableObject val in array) { num += DollarValueCurrent.Invoke(val); } ItemValuableBox[] array2 = Object.FindObjectsOfType(); foreach (ItemValuableBox val2 in array2) { num += val2.CurrentValue; } instance.HostFreezeGaugeBase(num); instance.HostRequestCheckmateScan(); } } internal static float ComputeObtainableDollars() { float num = 0f; ValuableObject[] array = Object.FindObjectsOfType(); foreach (ValuableObject val in array) { num += DollarValueCurrent.Invoke(val); } ItemValuableBox[] array2 = Object.FindObjectsOfType(true); foreach (ItemValuableBox val2 in array2) { num += val2.CurrentValue; } RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance != (Object)null) { num += (float)GameRefs.RoundDirector_extractionPointSurplus.Invoke(instance); } return num; } internal static void NoteHaulSuck() { _haulFreeze.NoteSuck(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); } internal static void CloseHaulFreeze() { _haulFreeze.Close(); } internal static bool InHaulSuckWindow(long nowUnixMs) { return _haulFreeze.IsHolding(nowUnixMs); } } [HarmonyPatch(typeof(ExtractionPoint), "DestroyTheFirstPhysObjectsInHaulList")] internal static class HaulSuckPatch { private static void Postfix() { try { ValueTrackPatch.NoteHaulSuck(); } catch (Exception ex) { WLog.Line("patch_haulsuck_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(RoundDirector), "ExtractionCompleted")] internal static class CheckmateExtractionPatch { private static void Postfix() { try { ValueTrackPatch.CloseHaulFreeze(); WerewolfDirector.Instance?.HostRecordExtractionDone(); WerewolfDirector.Instance?.HostRequestCheckmateScan(); } catch (Exception ex) { WLog.Line("patch_checkmate_extract_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class LevelGenerateDonePatch { private static void Postfix() { try { ValueTrackPatch.ScanAndFreezeBase(); } catch (Exception ex) { WLog.Line("patch_gaugebase_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "BreakRPC")] internal static class ValuableBreakPatch { private static void Prefix(PhysGrabObjectImpactDetector __instance, float valueLost, bool _loseValue) { try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.IsHostSessionActive || !SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsLevel() || !_loseValue) { return; } ValuableObject val = ValueTrackPatch.ResolveValuable(__instance); if ((Object)(object)val == (Object)null) { return; } float num = ValueTrackPatch.ReadCurrent(val); float num2 = ValueTrackPatch.ReadOriginal(val); float num3 = PerkGauge.ComputeRealLoss(num, valueLost, num2); if (!(num3 <= 0f)) { bool isOrb = (Object)(object)((Component)__instance).GetComponent() != (Object)null; instance.HostAddValueLoss(num3, isOrb); if (num - valueLost < num2 * 0.15f) { ValueTrackPatch.MarkBreakAccounted(((Object)val).GetInstanceID()); } } } catch (Exception ex) { WLog.Line("patch_break_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "DestroyObject")] internal static class ValuableDestroyPatch { private static void Prefix(PhysGrabObjectImpactDetector __instance) { try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.IsHostSessionActive || !SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsLevel() || __instance.destroyDisable) { return; } PhysGrabObject val = ValueTrackPatch.ResolvePhysGrabObject(__instance); if ((Object)(object)val != (Object)null && val.dead) { return; } ValuableObject val2 = ValueTrackPatch.ResolveValuable(__instance); if ((Object)(object)val2 == (Object)null) { AccountValuableBoxResidual(__instance, instance); } else if (!ValueTrackPatch.ConsumeBreakAccounted(((Object)val2).GetInstanceID())) { float num = ValueTrackPatch.ReadCurrent(val2); if (!(num <= 0f)) { bool isOrb = (Object)(object)((Component)__instance).GetComponent() != (Object)null; instance.HostAddValueLoss(num, isOrb); } } } catch (Exception ex) { WLog.Line("patch_destroy_error", false, ("err", ex.Message)); } } private static void AccountValuableBoxResidual(PhysGrabObjectImpactDetector __instance, WerewolfDirector dir) { ItemValuableBox componentInChildren = ((Component)__instance).GetComponentInChildren(true); if (!((Object)(object)componentInChildren == (Object)null)) { float currentValue = componentInChildren.CurrentValue; if (!(currentValue <= 0f)) { dir.HostAddValueLoss(currentValue, isOrb: false); ValueTrackPatch.MarkBoxAccounted(((Object)componentInChildren).GetInstanceID()); } } } } [HarmonyPatch(typeof(ItemValuableBox), "ExplodeValuableBox")] internal static class ValuableBoxExplodePatch { private static void Prefix(ItemValuableBox __instance) { try { WerewolfDirector instance = WerewolfDirector.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsHostSessionActive && SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.RunIsLevel()) { float currentValue = __instance.CurrentValue; if (!ValueTrackPatch.ConsumeBoxAccounted(((Object)__instance).GetInstanceID()) && !(currentValue <= 0f)) { instance.HostAddValueLoss(currentValue, isOrb: false); } } } catch (Exception ex) { WLog.Line("patch_boxexplode_error", false, ("err", ex.Message)); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class WipeGuardPatch { private static bool _blockLogged; private static bool _departBlockLogged; internal static void ResetLogThrottle() { _blockLogged = false; _departBlockLogged = false; } private static bool Prefix(bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) try { WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null || !instance.IsHostSessionActive) { ResetLogThrottle(); return true; } if (!SemiFunc.RunIsLevel()) { return true; } if (_levelFailed) { if (!_blockLogged) { _blockLogged = true; WLog.Line("wipe_transition_blocked", false); } return false; } if (_completedLevel && (int)_changeLevelType == 0) { if (!_departBlockLogged) { _departBlockLogged = true; WLog.Line("depart_transition_blocked", false); } return false; } return true; } catch (Exception ex) { WLog.Line("wipe_guard_error", false, ("err", ex.Message)); return true; } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class WorldgenLevelPatch { private static void Prefix(ChangeLevelType _changeLevelType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { WorldgenApplier.TryApplyOnDeparture(_changeLevelType); } catch (Exception ex) { WLog.Line("worldgen_prefix_error", false, ("err", ex.Message)); } } private static void Postfix() { try { if (WorldgenApplier.DebugLevelArmed) { WorldgenApplier.ClearDebugLevel(); } } catch (Exception ex) { WLog.Line("worldgen_postfix_error", false, ("err", ex.Message)); } } } } namespace Werewolf.Debugging { internal static class CheatCommands { private const string Usage = "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >"; internal static void Execute(string message) { if (!CommandGate.TryParse(message, out var command, out var args)) { return; } bool isHost = SemiFunc.IsMasterClientOrSingleplayer(); bool debugMode = Plugin.GameConfig != null && Plugin.GameConfig.DebugMode; CommandGateVerdict commandGateVerdict = CommandGate.Decide(command, isHost, debugMode); if (commandGateVerdict != CommandGateVerdict.Allowed) { WLog.Line("cmd_rejected", false, ("name", command), ("reason", (commandGateVerdict == CommandGateVerdict.RejectedNotHost) ? "not_host" : "debug_mode_disabled")); return; } (string, object)[] obj = new(string, object)[2] { ("name", command), default((string, object)) }; object item; switch (command) { default: item = args; break; case "role": case "vote": case "gauge": case "beacon": case "perk": case "curse": item = new string[1] { "" }; break; } obj[1] = ("args", item); WLog.Line("cmd", secret: false, obj); WerewolfDirector instance = WerewolfDirector.Instance; if ((Object)(object)instance == (Object)null) { WLog.Line("cmd_rejected", false, ("name", command), ("reason", "no_director")); return; } switch (command) { case "start": HandleStart(instance); break; case "bot": HandleBot(instance, args); break; case "role": HandleRole(instance, args); break; case "skiptimer": instance.HostForceExpireTimer(); break; case "checkmate": if (!instance.DebugForceCheckmate()) { WLog.Line("cmd_rejected", false, ("name", "checkmate"), ("reason", "no_session_or_not_host")); } break; case "reveal": HandleReveal(instance, args); break; case "kill": HandleKill(instance, args); break; case "phase": HandlePhase(instance, args); break; case "meeting": instance.SendConveneRequest(); break; case "report": instance.SendConveneRequest(ConveneKind.CorpseReport); break; case "body": if (args.Length >= 1 && args[0].ToLowerInvariant() == "clear") { instance.DebugClearFakeBodies(); } else { instance.DebugSpawnFakeBodies(); } break; case "spawnbag": { int result = 5000; if (args.Length >= 1 && (!int.TryParse(args[0], out result) || result < 1)) { WLog.Line("cmd_error", false, ("name", "spawnbag"), ("reason", "bad_value"), ("arg", args[0])); } else { instance.DebugSpawnMoneyBag(result); } break; } case "vote": HandleVote(instance, args); break; case "leave": HandleLeave(instance, args); break; case "meetingstatus": instance.DumpMeetingStatus(); break; case "status": instance.DumpStatus(); break; case "selftest": SelfTest.RunAll(); break; case "gauge": HandleGauge(instance, args); break; case "beacon": HandleBeacon(instance, args); break; case "perk": HandlePerk(instance, args); break; case "informant": instance.DebugRolesInformant(); break; case "curse": HandleCurse(instance, args); break; case "bomb": HandleBomb(instance, args); break; case "fx": HandleFx(instance, args); break; case "cfg": HandleCfg(instance, args); break; case "echo": instance.DebugToggleSelfEcho(); break; case "lang": HandleLang(args); break; case "chat": HandleChat(instance, args); break; case "res": HandleRes(args); break; case "": WLog.Line("cmd_usage", false, ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); break; default: WLog.Line("cmd_rejected", false, ("name", command), ("reason", "unknown_command")); break; } } private static void HandleBot(WerewolfDirector director, string[] args) { int result = 1; if (args.Length >= 1 && (!int.TryParse(args[0], out result) || result < 1)) { WLog.Line("cmd_error", false, ("name", "bot"), ("reason", "bad_count"), ("arg", args[0])); return; } int pendingBotCount = director.PendingBotCount; for (int i = 0; i < result; i++) { director.AddPendingBot(new WPlayer { ActorNumber = -101 - (pendingBotCount + i), Name = "Bot" + (pendingBotCount + i + 1), IsBot = true }); } WLog.Line("cmd_bot", false, ("added", result), ("pendingBots", director.PendingBotCount)); } private static void HandleRole(WerewolfDirector director, string[] args) { if (args.Length < 2 || !int.TryParse(args[0], out var result)) { WLog.Line("cmd_error", false, ("name", "role"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); return; } Role role; switch (args[1].ToLowerInvariant()) { case "villager": role = Role.Villager; break; case "werewolf": role = Role.Werewolf; break; case "blackcat": role = Role.BlackCat; break; case "bomber": role = Role.Bomber; break; case "shaman": role = Role.Shaman; break; default: WLog.Line("cmd_error", false, ("name", "role"), ("reason", "bad_role"), ("arg", args[1])); return; } director.ReserveForcedRole(result, role); WLog.Line("cmd_role", true, ("actor", result), ("role", role)); } private static void HandleReveal(WerewolfDirector director, string[] args) { if (args.Length < 1) { WLog.Line("cmd_error", false, ("name", "reveal"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); return; } string text = args[0].ToLowerInvariant(); if (!(text == "selfcat")) { if (text == "mates") { director.HostNotifyDisclosure(DisclosureKind.BlackCatSeesWerewolves); return; } WLog.Line("cmd_error", false, ("name", "reveal"), ("reason", "bad_kind"), ("arg", args[0])); } else { director.HostNotifyDisclosure(DisclosureKind.BlackCatSelfAwareness); } } private static void HandleKill(WerewolfDirector director, string[] args) { if (args.Length < 1 || !int.TryParse(args[0], out var result)) { WLog.Line("cmd_error", false, ("name", "kill"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else { bool asVote = args.Length >= 2 && string.Equals(args[1], "vote", StringComparison.OrdinalIgnoreCase); director.HostRecordDeathByActor(result, asVote); } } private static void HandleVote(WerewolfDirector director, string[] args) { if (args.Length < 1) { WLog.Line("cmd_error", false, ("name", "vote"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); return; } int result; if (string.Equals(args[0], "skip", StringComparison.OrdinalIgnoreCase)) { result = -1; } else { if (!int.TryParse(args[0], out result)) { WLog.Line("cmd_error", false, ("name", "vote"), ("reason", "bad_target")); return; } if (result == -1) { WLog.Line("cmd_vote_note", true, ("note", "target -1 is the skip sentinel; treated as skip")); } } if (args.Length >= 2) { if (!int.TryParse(args[1], out var result2)) { WLog.Line("cmd_error", false, ("name", "vote"), ("reason", "bad_as_actor")); } else { director.HostCastVoteAsActor(result2, result); } } else { director.SendVote(result); } } private static void HandleLeave(WerewolfDirector director, string[] args) { if (args.Length < 1 || !int.TryParse(args[0], out var result)) { WLog.Line("cmd_error", false, ("name", "leave"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else { director.SimulatePlayerLeave(result); } } private static void HandlePhase(WerewolfDirector director, string[] args) { if (args.Length < 1) { WLog.Line("cmd_error", false, ("name", "phase"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); return; } GamePhase gamePhase; switch (args[0].ToLowerInvariant()) { case "play": gamePhase = GamePhase.Play; break; case "meeting": gamePhase = GamePhase.Meeting; break; case "gameover": gamePhase = GamePhase.GameOver; break; default: WLog.Line("cmd_error", false, ("name", "phase"), ("reason", "bad_phase"), ("arg", args[0])); return; } PhaseChangeResult phaseChangeResult = director.HostRequestPhaseChange(gamePhase); WLog.Line("cmd_phase", false, ("target", gamePhase), ("result", phaseChangeResult.Success ? "ok" : "rejected"), ("reason", phaseChangeResult.Reason)); } private static void HandleGauge(WerewolfDirector director, string[] args) { if (args.Length < 1 || !int.TryParse(args[0], out var result) || result < 1) { WLog.Line("cmd_error", false, ("name", "gauge"), ("reason", "bad_pct"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else { director.DebugRolesGauge(result); } } private static void HandleBeacon(WerewolfDirector director, string[] args) { string text = ((args.Length >= 1) ? args[0].ToLowerInvariant() : ""); if (!(text == "charge")) { if (text == "use") { director.DebugRolesBeaconUse(); return; } WLog.Line("cmd_error", false, ("name", "beacon"), ("reason", "bad_op"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else { int result = 1; if (args.Length >= 2 && (!int.TryParse(args[1], out result) || result < 1)) { WLog.Line("cmd_error", false, ("name", "beacon"), ("reason", "bad_count")); } else { director.DebugRolesBeaconCharge(result); } } } private static void HandlePerk(WerewolfDirector director, string[] args) { int num = ((args.Length >= 1 && string.Equals(args[0], "unlock", StringComparison.OrdinalIgnoreCase)) ? 1 : 0); switch ((args.Length > num) ? args[num].ToLowerInvariant() : "") { case "stamina": director.DebugRolesPerkUnlock(PerkId.InfiniteStamina); break; case "jump": director.DebugRolesPerkUnlock(PerkId.InfiniteJump); break; case "ghost": director.DebugRolesPerkUnlock(PerkId.EnemyIgnore); break; case "heal": director.DebugRolesPerkUnlock(PerkId.NaturalHeal); break; case "all": director.DebugRolesPerkUnlock(PerkId.InfiniteStamina); director.DebugRolesPerkUnlock(PerkId.InfiniteJump); director.DebugRolesPerkUnlock(PerkId.EnemyIgnore); director.DebugRolesPerkUnlock(PerkId.NaturalHeal); break; default: WLog.Line("cmd_error", false, ("name", "perk"), ("reason", "bad_perk"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); break; } } private static void HandleCurse(WerewolfDirector director, string[] args) { int? actorNumber = null; if (args.Length >= 1) { if (!int.TryParse(args[0], out var result)) { WLog.Line("cmd_error", false, ("name", "curse"), ("reason", "bad_actor")); return; } actorNumber = result; } director.DebugRolesCurse(actorNumber); } private static void HandleBomb(WerewolfDirector director, string[] args) { switch ((args.Length >= 1) ? args[0].ToLowerInvariant() : "") { case "gauge": director.DebugBomberFillGauge(); break; case "plant": { if (args.Length < 2 || !int.TryParse(args[1], out var result2)) { WLog.Line("cmd_error", false, ("name", "bomb"), ("sub", "plant"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else { director.DebugBomberPlant(result2); } break; } case "detonate": director.DebugBomberDetonate(); break; case "ammo": { int result = 1; if (args.Length >= 2 && (!int.TryParse(args[1], out result) || result < 1)) { WLog.Line("cmd_error", false, ("name", "bomb"), ("sub", "ammo"), ("reason", "bad_count")); } else { director.DebugBomberGrantAmmo(result); } break; } default: WLog.Line("cmd_error", false, ("name", "bomb"), ("reason", "bad_op"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); break; } } private static void HandleFx(WerewolfDirector director, string[] args) { string text = ((args.Length >= 1) ? args[0].ToLowerInvariant() : ""); string[] array = ((args.Length >= 2) ? args[1..] : Array.Empty()); switch (text) { case "reveal": HandleFxReveal(director, array); return; case "toast": director.DebugPlayToast((array.Length >= 1) ? string.Join(" ", array) : "テスト通知"); return; case "countdown": HandleFxCountdown(director, array); return; case "result": director.DebugPlayResult(); return; case "sfx": HandleFxSfx(director, array); return; case "clear": director.DebugClearFx(); return; } WLog.Line("cmd_error", false, ("name", "fx"), ("reason", "bad_kind"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } private static void HandleFxReveal(WerewolfDirector director, string[] rest) { Role role = Role.Werewolf; if (rest.Length >= 1) { switch (rest[0].ToLowerInvariant()) { case "villager": role = Role.Villager; break; case "werewolf": role = Role.Werewolf; break; case "blackcat": role = Role.BlackCat; break; case "bomber": role = Role.Bomber; break; case "shaman": role = Role.Shaman; break; default: WLog.Line("cmd_error", false, ("name", "fx"), ("reason", "bad_reveal_role"), ("arg", rest[0])); return; } } director.DebugPlayReveal(role); } private static void HandleFxCountdown(WerewolfDirector director, string[] rest) { int result = 10; if (rest.Length >= 1 && (!int.TryParse(rest[0], out result) || result < 0)) { WLog.Line("cmd_error", false, ("name", "fx"), ("reason", "bad_countdown_sec")); } else { director.DebugPlayConveneCountdown("デバッグ", result); } } private static void HandleFxSfx(WerewolfDirector director, string[] rest) { string text = ((rest.Length >= 1) ? rest[0].ToLowerInvariant() : "toast"); string clipKey; switch (text) { case "countdown": clipKey = "sfx_countdown"; break; case "howl": clipKey = "sfx_howl"; break; case "toast": clipKey = "sfx_toast"; break; case "notice_convene": clipKey = "sfx_notice_convene"; break; case "bell": clipKey = "sfx_bell"; break; default: WLog.Line("cmd_error", false, ("name", "fx"), ("reason", "bad_sfx_kind"), ("arg", text)); return; } director.DebugPlaySfx(clipKey); } private static void HandleCfg(WerewolfDirector director, string[] args) { string text = ((args.Length >= 1) ? args[0].ToLowerInvariant() : ""); if (!(text == "inject")) { if (text == "clear") { director.DebugClearCfgBlob(); return; } WLog.Line("cmd_error", false, ("name", "cfg"), ("reason", "bad_op"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else if (args.Length < 2) { WLog.Line("cmd_error", false, ("name", "cfg"), ("reason", "bad_args"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); } else { string text2 = string.Join(" ", args[1..]); string blob = ((!LooksLikeVersionedBlob(text2)) ? (1 + "|" + text2) : text2); director.DebugInjectCfgBlob(blob); } } private static void HandleLang(string[] args) { if (((args.Length >= 1) ? args[0].ToLowerInvariant() : "") != "export") { WLog.Line("cmd_error", false, ("name", "lang"), ("reason", "bad_op"), ("usage", "/ww |skiptimer|checkmate|reveal |kill [vote]|phase |meeting|vote [asActor]|leave |meetingstatus|status|selftest|gauge |beacon |perk |informant|curse [actor]|body [clear]|spawnbag [dollars]|bomb |detonate|ammo [n]>|fx |cfg |clear>|echo|lang export|chat |as |name |spam [n]|vote [actor]|baseline|late [ms] [text]|gate|avatar|state|clear>|res >")); return; } try { string location = typeof(CheatCommands).Assembly.Location; string text = (string.IsNullOrEmpty(location) ? null : Path.GetDirectoryName(location)); if (string.IsNullOrEmpty(text)) { WLog.Line("cmd_error", false, ("name", "lang"), ("reason", "no_dll_dir")); return; } string text2 = Path.Combine(text, "Lang"); Directory.CreateDirectory(text2); string text3 = Path.Combine(text2, "template.txt"); File.WriteAllText(text3, Texts.ExportTemplate(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); WLog.Line("cmd_lang_export", false, ("path", text3)); } catch (Exception ex) { WLog.Line("cmd_error", false, ("name", "lang"), ("reason", "export_failed"), ("detail", ex.GetType().Name)); } } private static void HandleChat(WerewolfDirector director, string[] args) { string text = ((args.Length >= 1) ? args[0].ToLowerInvariant() : "state"); switch (text) { case "say": director.DebugInjectChat(director.DebugLocalActor, null, JoinFrom(args, 1, "テスト発言")); break; case "as": { if (args.Length >= 2 && int.TryParse(args[1], out var result2)) { director.DebugInjectChat(result2, null, JoinFrom(args, 2, "テスト発言")); break; } WLog.Line("cmd_error", false, ("name", "chat as"), ("reason", "bad_actor")); break; } case "name": { if (args.Length >= 3 && int.TryParse(args[1], out var result4)) { director.DebugInjectChat(result4, args[2], JoinFrom(args, 3, "テスト発言")); break; } WLog.Line("cmd_error", false, ("name", "chat name"), ("reason", "usage: /ww chat name ")); break; } case "spam": { director.DebugSpamChat((args.Length >= 2 && int.TryParse(args[1], out var result3)) ? result3 : 20); break; } case "vote": { director.DebugInjectVoteLine((args.Length >= 2 && int.TryParse(args[1], out var result5)) ? result5 : director.DebugLocalActor); break; } case "baseline": director.DebugArmVoteBaseline(); break; case "late": { director.DebugScheduleChat((args.Length >= 2 && long.TryParse(args[1], out var result)) ? result : 1000, JoinFrom(args, 2, "終了間際の発言")); break; } case "gate": director.DebugToggleInputGateProbe(); break; case "avatar": director.DebugToggleChatAvatarFallback(); break; case "clear": director.DebugClearChat(); break; case "state": director.DebugDumpChatState(); break; default: WLog.Line("cmd_error", false, ("name", "chat"), ("reason", "unknown_sub"), ("sub", text)); break; } } private static string JoinFrom(string[] args, int from, string fallback) { if (args.Length <= from) { return fallback; } return string.Join(" ", args, from, args.Length - from); } private static void HandleRes(string[] args) { if (args.Length < 2 || !int.TryParse(args[0], out var result) || !int.TryParse(args[1], out var result2) || result < 640 || result2 < 360) { WLog.Line("cmd_error", false, ("name", "res"), ("reason", "usage: /ww res =640.. =360..")); } else { Screen.SetResolution(result, result2, Screen.fullScreen); WLog.Line("cmd_res", false, ("w", result), ("h", result2), ("fullScreen", Screen.fullScreen)); } } private static bool LooksLikeVersionedBlob(string s) { if (string.IsNullOrEmpty(s)) { return false; } int num = s.IndexOf('|'); if (num <= 0) { return false; } for (int i = 0; i < num; i++) { if (s[i] < '0' || s[i] > '9') { return false; } } return true; } private static void HandleStart(WerewolfDirector director) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 if (!SemiFunc.RunIsLevel() || (Object)(object)GameDirector.instance == (Object)null || (int)GameDirector.instance.currentState != 2) { WLog.Line("cmd_start", false, ("result", "rejected"), ("reason", "not_in_level")); return; } StartResult startResult = director.StartHosted(); if (startResult.Success) { WLog.Line("cmd_start", false, ("result", "ok")); } else { WLog.Line("cmd_start", false, ("result", "rejected"), ("reason", startResult.Reason)); } } } public enum CommandGateVerdict { Allowed, RejectedNotHost, RejectedDebugModeDisabled } public static class CommandGate { private static readonly char[] Separators = new char[2] { ' ', '\t' }; public static bool TryParse(string message, out string command, out string[] args) { command = ""; args = Array.Empty(); if (string.IsNullOrWhiteSpace(message)) { return false; } string[] array = message.Trim().Split(Separators, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0 || !string.Equals(array[0], "/ww", StringComparison.OrdinalIgnoreCase)) { return false; } if (array.Length >= 2) { command = array[1].ToLowerInvariant(); } if (array.Length >= 3) { args = new string[array.Length - 2]; Array.Copy(array, 2, args, 0, args.Length); } return true; } public static CommandGateVerdict Decide(string command, bool isHost, bool debugMode) { if (!isHost) { return CommandGateVerdict.RejectedNotHost; } if (!debugMode) { return CommandGateVerdict.RejectedDebugModeDisabled; } return CommandGateVerdict.Allowed; } } public sealed class SecretLogBuffer { public const int DefaultCapacity = 4000; private readonly object _lock = new object(); private readonly Queue _lines = new Queue(); private readonly int _capacity; private int _dropped; public int Count { get { lock (_lock) { return _lines.Count; } } } public SecretLogBuffer(int capacity = 4000) { _capacity = ((capacity < 1) ? 1 : capacity); } public void Add(string line, DateTime capturedAt) { string item = line + " t=" + capturedAt.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); lock (_lock) { _lines.Enqueue(item); if (_lines.Count > _capacity) { _lines.Dequeue(); _dropped++; } } } public List Flush(out int dropped) { lock (_lock) { dropped = _dropped; _dropped = 0; List result = new List(_lines); _lines.Clear(); return result; } } } public sealed class SelfTestResult { public string Name { get; } public bool Pass { get; } public string Detail { get; } public SelfTestResult(string name, bool pass, string detail) { Name = name; Pass = pass; Detail = detail; } } public static class SelfTest { private sealed class Harness { public readonly GameSession Session = new GameSession(); public readonly LoopbackNetBus Bus = new LoopbackNetBus(); public readonly List Sent = new List(); public readonly List Received = new List(); public Harness() { Bus.OnReceived += delegate(InboundMessage m) { Received.Add(m); }; Session.OnSend += delegate(OutboundMessage m) { Sent.Add(m); switch (m.Target) { case MessageTarget.All: Bus.SendToAll(m.Code, m.Payload); break; case MessageTarget.Actors: Bus.SendToActors(m.Code, m.Payload, m.TargetActors); break; case MessageTarget.Master: Bus.SendToMaster(m.Code, m.Payload); break; } }; } public static List SelfPlusBots(int total) { List list = new List { new WPlayer { ActorNumber = 1, Name = "Self" } }; for (int i = 1; i < total; i++) { list.Add(new WPlayer { ActorNumber = -i, Name = "Bot" + i, IsBot = true }); } return list; } public void Start(int totalPlayers, int seed, params (int Actor, Role Role)[] forced) { Start(totalPlayers, seed, 1, forced); } public void Start(int totalPlayers, int seed, int teamTotal, params (int Actor, Role Role)[] forced) { for (int i = 0; i < forced.Length; i++) { var (actorNumber, role) = forced[i]; Session.ReserveForcedRole(actorNumber, role); } GameConfig config = new GameConfig { WerewolfCount = teamTotal, RoundSeconds = 600, BlackCatRevealDelaySec = 60, BlackCatChancePercent = 100, BomberChancePercent = 0 }; StartResult startResult = Session.Start(config, SelfPlusBots(totalPlayers), 1000000L, new Random(seed)); if (!startResult.Success) { throw new InvalidOperationException("start rejected: " + startResult.Reason); } } public int CountSent(byte code) { int num = 0; foreach (OutboundMessage item in Sent) { if (item.Code == code) { num++; } } return num; } public WPlayer Player(int actor) { foreach (WPlayer player in Session.Players) { if (player.ActorNumber == actor) { return player; } } return null; } public int CountRole(Role role) { int num = 0; foreach (WPlayer player in Session.Players) { if (player.Role == role) { num++; } } return num; } } private sealed class MeetingHarness { public readonly GameSession Game = new GameSession(); public readonly MeetingClientState Client = new MeetingClientState(); public readonly LoopbackNetBus Bus = new LoopbackNetBus(); public readonly List HostSent = new List(); public readonly List Received = new List(); public readonly List<(int Caller, long End)> RoomMeeting = new List<(int, long)>(); public readonly List ExecutedActors = new List(); public readonly MeetingSession Meeting; public long Clock = 1000000L; public MeetingHarness(int totalPlayers, params (int Actor, Role Role)[] forced) : this(new GameConfig { RoundSeconds = 600, MeetingRightsPerPlayer = 1, ConveneSuppressStartSec = 0, ConveneSuppressAfterSec = 0, MeetingCountdownSec = 5, MeetingDurationSec = 120, VoteTimeCutEnabled = true, ResultDisplaySec = 6 }, totalPlayers, forced) { } public MeetingHarness(GameConfig config, int totalPlayers, params (int Actor, Role Role)[] forced) { Bus.OnReceived += ApplyInbound; Bus.OnPlayerLeft += delegate(int actor) { Client.ApplyPlayerLeft(actor); Meeting?.NotifyPlayerLeft(actor, Clock); }; for (int num = 0; num < forced.Length; num++) { var (actorNumber, role) = forced[num]; Game.ReserveForcedRole(actorNumber, role); } Game.OnSend += SendViaBus; StartResult startResult = Game.Start(config, Harness.SelfPlusBots(totalPlayers), 1000000L, new Random(6)); if (!startResult.Success) { throw new InvalidOperationException("start rejected: " + startResult.Reason); } Meeting = new MeetingSession(config, Game, Game.Players, 1000000L); Meeting.OnSend += SendViaBus; Meeting.OnPhaseChangeRequest += delegate(GamePhase phase) { Game.RequestPhaseChange(phase, Clock); }; Meeting.OnMeetingStateChanged += delegate(int caller, long end) { RoomMeeting.Add((caller, end)); }; Meeting.OnExecutePlayer += delegate(int actor) { ExecutedActors.Add(actor); Game.MarkNextDeathAsVote(actor); Game.RecordDeath(actor, Clock); Meeting.NotifyPlayerDied(actor); }; } public void Convene() { Bus.SendToMaster(173, new object[1] { (byte)0 }); } public void SelfVote(int targetActor) { Bus.SendToMaster(164, new object[1] { targetActor }); } public long StartVoting() { Convene(); InboundMessage inboundMessage = LastReceived(163); if (inboundMessage == null) { throw new InvalidOperationException("163 not received after convene"); } Clock = (long)inboundMessage.Payload[1]; Meeting.Tick(Clock); return Clock; } public InboundMessage LastReceived(byte code) { for (int num = Received.Count - 1; num >= 0; num--) { if (Received[num].Code == code) { return Received[num]; } } return null; } public int CountReceived(byte code) { int num = 0; foreach (InboundMessage item in Received) { if (item.Code == code) { num++; } } return num; } public void SendHostMessage(OutboundMessage m) { SendViaBus(m); } private void SendViaBus(OutboundMessage m) { HostSent.Add(m); switch (m.Target) { case MessageTarget.All: Bus.SendToAll(m.Code, m.Payload); break; case MessageTarget.Actors: Bus.SendToActors(m.Code, m.Payload, m.TargetActors); break; case MessageTarget.Master: Bus.SendToMaster(m.Code, m.Payload); break; } } private void ApplyInbound(InboundMessage msg) { Received.Add(msg); object[] payload = msg.Payload; if (EventCodes.IsMasterInbound(msg.Code)) { if (Meeting != null) { switch (msg.Code) { case 173: Meeting.TryConvene(msg.SenderActor, Clock); break; case 164: Meeting.CastVote(msg.SenderActor, (int)payload[0], Clock); break; } } return; } switch (msg.Code) { case 163: Client.ApplyStartMeeting((int)payload[0], (long)payload[1], (long)payload[2], (ConveneKind)(byte)payload[3]); break; case 165: Client.ApplyVoteProgress((int[])payload[0], (long)payload[1]); break; case 166: Client.ApplyResult(new MeetingOutcome { ExecutedActor = (int)payload[0], TargetActors = (int[])payload[1], VoteCounts = (int[])payload[2] }); break; case 168: Client.ApplyPlayerDied((int)payload[0], (DeathCause)(byte)payload[1]); break; case 172: Client.ApplyPhase((GamePhase)(byte)payload[0]); break; } } } private sealed class RolesHarness { public readonly MeetingHarness M; public readonly RolesSession Roles; public readonly List<(int Actor, bool Enabled)> EnemyIgnoreChanges = new List<(int, bool)>(); public readonly List BeaconTriggers = new List(); public readonly List CurseKills = new List(); public int InformantFires; public RolesHarness(int totalPlayers, params (int Actor, Role Role)[] forced) : this(RolesConfig(), totalPlayers, forced) { } public RolesHarness(GameConfig config, int totalPlayers, params (int Actor, Role Role)[] forced) { M = new MeetingHarness(config, totalPlayers, forced); Roles = new RolesSession(config, M.Game, 1000000L, new Random(7)); Roles.OnSend += M.SendHostMessage; Roles.OnInformantEstablished += delegate { InformantFires++; M.Game.NotifyDisclosureCondition(DisclosureKind.BlackCatSeesWerewolves); }; Roles.OnEnemyIgnoreChanged += delegate(int actor, bool enabled) { EnemyIgnoreChanges.Add((actor, enabled)); }; Roles.OnBeaconTriggered += delegate(int actor) { BeaconTriggers.Add(actor); }; Roles.OnCurseKill += delegate(int actor) { CurseKills.Add(actor); M.Game.RecordDeath(actor, M.Clock); M.Meeting.NotifyPlayerDied(actor); }; M.Meeting.OnExecutePlayer += delegate(int actor) { if (Roles.TryStartCurse(actor, M.Clock, out var holdExtensionMs, M.Meeting.VotersFor(actor))) { M.Meeting.ExtendClosingHold(holdExtensionMs); } }; M.Meeting.OnVotingStarted += delegate { Roles.OnMeetingStarted(M.Clock); }; M.Meeting.OnMeetingStateChanged += delegate(int caller, long _) { if (caller < 0) { Roles.OnMeetingEnded(M.Clock); } }; } public int CountHostSent(byte code, int subtype = -1) { int num = 0; foreach (OutboundMessage item in M.HostSent) { if (item.Code == code && (subtype < 0 || (byte)item.Payload[0] == (byte)subtype)) { num++; } } return num; } public int CountGaugeSentTo(int actor) { int num = 0; foreach (OutboundMessage item in M.HostSent) { if (item.Code == 171 && item.Target == MessageTarget.Actors && item.TargetActors != null && ContainsInt(item.TargetActors, actor)) { num++; } } return num; } } private sealed class BombHarness { public readonly GameConfig Config; public readonly List Players; public readonly LoopbackNetBus Bus; public readonly BombSession Bomb; public readonly BombClientState Client = new BombClientState(); public readonly List Received = new List(); public BombHarness(GameConfig cfg, List players, long now) { Config = cfg; Players = players; int localActorNumber = 1; foreach (WPlayer player in players) { if (player.Role == Role.Bomber) { localActorNumber = player.ActorNumber; break; } } Bus = new LoopbackNetBus(localActorNumber); Bomb = new BombSession(cfg, players, now); Bus.OnReceived += delegate(InboundMessage m) { Received.Add(m); if (m.Code == 181) { Client.ApplyState(new BomberStateSnapshot((int)m.Payload[0], (byte)m.Payload[1], (long)m.Payload[3], (long)m.Payload[4], (BombDenyReason)(byte)m.Payload[2])); } else if (m.Code == 180) { Client.ApplyPendingDetonation((int)m.Payload[0], (long)m.Payload[1]); } }; FlushBomberState(); } public void FlushBomberState() { if (Bomb.Dirty) { int bomberActor = Bomb.BomberActor; BomberStateSnapshot bomberStateSnapshot = Bomb.BuildSnapshot(); if (bomberActor >= 0) { Bus.SendToActors(181, new object[5] { bomberStateSnapshot.TargetActor, bomberStateSnapshot.Ammo, (byte)bomberStateSnapshot.LastDeny, bomberStateSnapshot.PlantReadyUnixMs, bomberStateSnapshot.DetonateReadyUnixMs }, new int[1] { bomberActor }); } } } public void SendDetonation(int targetActor, long detonateAtUnixMs) { Bus.SendToAll(180, new object[2] { targetActor, detonateAtUnixMs }); } public int CountReceived(byte code) { int num = 0; foreach (InboundMessage item in Received) { if (item.Code == code) { num++; } } return num; } public InboundMessage LastReceived(byte code) { for (int num = Received.Count - 1; num >= 0; num--) { if (Received[num].Code == code) { return Received[num]; } } return null; } public WPlayer Player(int actor) { foreach (WPlayer player in Players) { if (player.ActorNumber == actor) { return player; } } return null; } } private const long Now = 1000000L; private const int RoundSeconds = 600; private const long RoundEnd = 1600000L; private const int DelaySec = 60; private const byte WWMeetingCodesStartMeeting = 163; private const byte WWCastVoteCode = 164; private const byte WWVoteProgressCode = 165; private const byte WWMeetingResultCode = 166; private const byte WWMeetingRequestCode = 173; public static IReadOnlyList RunAll() { List list = new List { Run("role_distribution_3", delegate(List f) { ScenarioDistribution(f, 3, 1, 1, 1); }), Run("role_distribution_5", delegate(List f) { ScenarioDistribution(f, 5, 2, 2, 1); }), Run("role_distribution_7", delegate(List f) { ScenarioDistribution(f, 7, 3, 3, 1); }), Run("role_distribution_10", delegate(List f) { ScenarioDistribution(f, 10, 4, 4, 1); }), Run("forced_role", ScenarioForcedRole), Run("win_priority_simultaneous", ScenarioWinPrioritySimultaneous), Run("blackcat_excluded_from_wolf_count", ScenarioBlackCatExcludedFromWolfCount), Run("blackcat_death_continues", ScenarioBlackCatDeathContinues), Run("blackcat_shares_wolf_win", ScenarioBlackCatSharesWolfWin), Run("disclosure_order_and_dedup", ScenarioDisclosureOrderAndDedup), Run("meeting_pause_and_resume_extend", ScenarioMeetingPauseAndResumeExtend), Run("timer_expiry_wolf_win", ScenarioTimerExpiryWolfWin), Run("meeting_full_flow", ScenarioMeetingFullFlow), Run("meeting_vote_secrecy", ScenarioMeetingVoteSecrecy), Run("meeting_leave_no_execution", ScenarioMeetingLeaveNoExecution), Run("meeting_restore_from_room_state", ScenarioMeetingRestoreFromRoomState), Run("meeting_vote_reject_reenable", ScenarioMeetingVoteRejectReenable), Run("roles_gauge_freeze_and_floor", ScenarioRolesGaugeFreezeAndFloor), Run("roles_perk_unlock_order_once", ScenarioRolesPerkUnlockOrderOnce), Run("roles_beacon_gate", ScenarioRolesBeaconGate), Run("roles_beacon_meeting_end_sync", ScenarioRolesBeaconMeetingEndSync), Run("roles_informant_conditions", ScenarioRolesInformantConditions), Run("roles_curse_resolution", ScenarioRolesCurseResolution), Run("roles_curse_win_judge", ScenarioRolesCurseWinJudge), Run("roles_action_validation", ScenarioRolesActionValidation), Run("roles_full_curse_flow", ScenarioRolesFullCurseFlow), Run("roles_gauge_distribution_targets", ScenarioRolesGaugeDistributionTargets), Run("roles_meeting_gauge_snapshot", ScenarioRolesMeetingGaugeSnapshot), Run("bomber_full_flow", ScenarioBomberFullFlow) }; int num = 0; foreach (SelfTestResult item in list) { if (item.Pass) { num++; } } WLog.Line("selftest_summary", false, ("total", list.Count), ("pass", num), ("fail", list.Count - num)); return list; } private static SelfTestResult Run(string name, Action> scenario) { List list = new List(); try { scenario(list); } catch (Exception ex) { list.Add("exception: " + ex.Message); } string text = ((list.Count == 0) ? "" : string.Join("; ", list)); SelfTestResult selfTestResult = new SelfTestResult(name, list.Count == 0, text); WLog.Line("selftest", false, ("name", name), ("result", selfTestResult.Pass ? "PASS" : "FAIL"), ("detail", text)); return selfTestResult; } private static void Check(List fails, bool condition, string what) { if (!condition) { fails.Add(what); } } private static void ScenarioDistribution(List fails, int total, int teamTotal, int wolves, int cats) { Harness harness = new Harness(); harness.Start(total, total, teamTotal); Check(fails, harness.CountRole(Role.Werewolf) == wolves, $"wolves={harness.CountRole(Role.Werewolf)} expected={wolves}"); Check(fails, harness.CountRole(Role.BlackCat) == cats, $"cats={harness.CountRole(Role.BlackCat)} expected={cats}"); Check(fails, harness.CountSent(160) == total, $"assignRoleSent={harness.CountSent(160)} expected={total}"); int num = 0; foreach (InboundMessage item in harness.Received) { if (item.Code == 160) { num++; } } Check(fails, num == 1, $"selfAssignRoleReceived={num} expected=1"); } private static void ScenarioForcedRole(List fails) { Harness harness = new Harness(); harness.Start(5, 42, (-1, Role.Werewolf), (-2, Role.BlackCat)); Check(fails, harness.Player(-1).Role == Role.Werewolf, "actor-1 not werewolf"); Check(fails, harness.Player(-2).Role == Role.BlackCat, "actor-2 not blackcat"); Check(fails, harness.CountRole(Role.Werewolf) == 1 && harness.CountRole(Role.BlackCat) == 1, "5p distribution not 1W/1C after forcing"); } private static void ScenarioWinPrioritySimultaneous(List fails) { WinResult winResult = WinJudge.Judge(new List { new WPlayer { ActorNumber = 1, Role = Role.Werewolf, Alive = false }, new WPlayer { ActorNumber = 2, Role = Role.Villager, Alive = false }, new WPlayer { ActorNumber = 3, Role = Role.BlackCat, Alive = false } }); Check(fails, winResult != null, "no winner on simultaneous eradication"); Check(fails, winResult != null && winResult.WinningTeam == Team.Villagers, "winner is not villagers"); Check(fails, winResult != null && winResult.Reason == WinReason.WerewolvesEradicated, "reason is not WerewolvesEradicated"); } private static void ScenarioBlackCatExcludedFromWolfCount(List fails) { Harness harness = new Harness(); harness.Start(5, 1, (-1, Role.Werewolf), (-2, Role.BlackCat)); harness.Session.RecordDeath(-1, 1001000L); Check(fails, harness.Session.Winner != null, "no winner after last wolf died"); Check(fails, harness.Session.Winner != null && harness.Session.Winner.WinningTeam == Team.Villagers, "winner is not villagers"); Check(fails, harness.Player(-2).Alive, "blackcat unexpectedly dead"); Check(fails, harness.CountSent(169) == 1, "EV_GameOver not sent exactly once"); } private static void ScenarioBlackCatDeathContinues(List fails) { Harness harness = new Harness(); harness.Start(5, 1, (-1, Role.Werewolf), (-2, Role.BlackCat)); harness.Session.RecordDeath(-2, 1001000L); Check(fails, harness.Session.Winner == null, "winner confirmed by blackcat-only death"); Check(fails, harness.Session.Phase == GamePhase.Play, "phase left Play"); Check(fails, harness.CountSent(168) == 1, "EV_PlayerDied not sent"); Check(fails, harness.CountSent(169) == 0, "EV_GameOver sent unexpectedly"); } private static void ScenarioBlackCatSharesWolfWin(List fails) { Harness harness = new Harness(); harness.Start(5, 1, (-1, Role.Werewolf), (-2, Role.BlackCat)); harness.Session.RecordDeath(1, 1001000L); harness.Session.RecordDeath(-3, 1002000L); harness.Session.RecordDeath(-4, 1003000L); Check(fails, harness.Session.Winner != null, "no winner after villagers eradicated"); Check(fails, harness.Session.Winner != null && harness.Session.Winner.WinningTeam == Team.Werewolves, "winner is not werewolves"); Check(fails, harness.Player(-2).Alive, "blackcat unexpectedly dead"); Check(fails, RoleDistribution.TeamOf(Role.BlackCat) == Team.Werewolves, "blackcat team mapping is not werewolves"); } private static void ScenarioDisclosureOrderAndDedup(List fails) { Harness harness = new Harness(); harness.Start(7, 7, (-1, Role.Werewolf), (-2, Role.Werewolf), (-3, Role.BlackCat)); Check(fails, harness.Sent.Count == 11, $"sentCount={harness.Sent.Count} expected=11"); for (int i = 0; i < 7 && i < harness.Sent.Count; i++) { Check(fails, harness.Sent[i].Code == 160, $"sent[{i}] is not 160"); } if (harness.Sent.Count == 11) { Check(fails, harness.Sent[7].Code == 162, "sent[7] is not 162"); Check(fails, harness.Sent[8].Code == 162, "sent[8] is not 162"); Check(fails, harness.Sent[9].Code == 170, "sent[9] is not 170"); Check(fails, harness.Sent[10].Code == 172, "sent[10] is not 172"); } foreach (OutboundMessage item in harness.Sent) { if (item.Code == 160 && item.TargetActors[0] == -3) { Check(fails, (byte)item.Payload[0] == 0, "blackcat initial notice is not Villager"); } } harness.Session.NotifyDisclosureCondition(DisclosureKind.BlackCatSelfAwareness); harness.Session.NotifyDisclosureCondition(DisclosureKind.BlackCatSelfAwareness); harness.Session.Tick(1060001L); Check(fails, harness.CountSent(161) == 1, $"selfRoleSent={harness.CountSent(161)} expected=1"); int num = harness.CountSent(162); harness.Session.NotifyDisclosureCondition(DisclosureKind.BlackCatSeesWerewolves); harness.Session.NotifyDisclosureCondition(DisclosureKind.BlackCatSeesWerewolves); Check(fails, harness.CountSent(162) == num + 1, "werewolves-to-blackcat reveal not issued exactly once"); } private static void ScenarioMeetingPauseAndResumeExtend(List fails) { Harness harness = new Harness(); harness.Start(5, 1, (-1, Role.Werewolf), (-2, Role.BlackCat)); long num = 1010000L; long num2 = 1710000L; long num3 = 1600000 + (num2 - num); Check(fails, harness.Session.RequestPhaseChange(GamePhase.Meeting, num).Success, "meeting transition rejected"); harness.Session.Tick(1700000L); Check(fails, harness.Session.Winner == null, "expired during meeting"); Check(fails, harness.Session.RequestPhaseChange(GamePhase.Play, num2).Success, "play transition rejected"); OutboundMessage outboundMessage = null; foreach (OutboundMessage item in harness.Sent) { if (item.Code == 172) { outboundMessage = item; } } Check(fails, outboundMessage != null && (long)outboundMessage.Payload[2] == num3, "PhaseChanged payload does not carry extended round end"); harness.Session.Tick(num3 - 1); Check(fails, harness.Session.Winner == null, "expired before extended end"); harness.Session.Tick(num3); Check(fails, harness.Session.Winner != null && harness.Session.Winner.Reason == WinReason.TimerExpired, "no timer expiry win at extended end"); } private static void ScenarioTimerExpiryWolfWin(List fails) { Harness harness = new Harness(); harness.Start(5, 1, (-1, Role.Werewolf), (-2, Role.BlackCat)); harness.Session.Tick(1599999L); Check(fails, harness.Session.Winner == null, "expired too early"); harness.Session.Tick(1600000L); Check(fails, harness.Session.Winner != null, "no winner at round end"); Check(fails, harness.Session.Winner != null && harness.Session.Winner.WinningTeam == Team.Werewolves, "winner is not werewolves"); Check(fails, harness.Session.Winner != null && harness.Session.Winner.Reason == WinReason.TimerExpired, "reason is not TimerExpired"); Check(fails, harness.Session.Phase == GamePhase.GameOver, "phase is not GameOver"); int num = -1; int num2 = -1; for (int i = 0; i < harness.Sent.Count; i++) { if (harness.Sent[i].Code == 169) { num = i; } if (harness.Sent[i].Code == 172) { num2 = i; } } Check(fails, num >= 0 && num < num2, "EV_GameOver is not before final EV_PhaseChanged"); } private static bool ContainsInt(IEnumerable values, int value) { if (values == null) { return false; } foreach (int value2 in values) { if (value2 == value) { return true; } } return false; } private static void ScenarioMeetingFullFlow(List fails) { MeetingHarness meetingHarness = new MeetingHarness(4, (-3, Role.Werewolf)); meetingHarness.Convene(); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Countdown, "stage is not Countdown after convene"); InboundMessage inboundMessage = meetingHarness.LastReceived(163); Check(fails, inboundMessage != null, "163 not received"); if (inboundMessage != null) { Check(fails, (int)inboundMessage.Payload[0] == 1, "163 caller is not 1"); long clock = (long)inboundMessage.Payload[1]; long num = (long)inboundMessage.Payload[2]; Check(fails, meetingHarness.Client.MeetingActive, "client state not active after 163"); InboundMessage inboundMessage2 = meetingHarness.LastReceived(172); Check(fails, inboundMessage2 != null && (byte)inboundMessage2.Payload[0] == 2, "172(Meeting) not received"); meetingHarness.Clock = clock; meetingHarness.Meeting.Tick(meetingHarness.Clock); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Voting, "stage is not Voting at warp"); Check(fails, meetingHarness.Client.WarpDone(meetingHarness.Clock), "client warp not done"); meetingHarness.SelfVote(-2); InboundMessage inboundMessage3 = meetingHarness.LastReceived(165); Check(fails, inboundMessage3 != null, "165 not received after self vote"); if (inboundMessage3 != null) { Check(fails, ContainsInt((int[])inboundMessage3.Payload[0], 1), "165 voted set missing self"); Check(fails, (long)inboundMessage3.Payload[1] < num, "meeting end not shortened by vote"); } Check(fails, ContainsInt(meetingHarness.Client.VotedActors, 1), "client voted set missing self"); meetingHarness.Meeting.CastVote(-1, -2, meetingHarness.Clock); meetingHarness.Meeting.CastVote(-2, -1, meetingHarness.Clock); meetingHarness.Meeting.CastVote(-3, -2, meetingHarness.Clock); meetingHarness.Meeting.Tick(meetingHarness.Clock); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Closing, "stage is not Closing after all votes"); InboundMessage inboundMessage4 = meetingHarness.LastReceived(166); Check(fails, inboundMessage4 != null && (int)inboundMessage4.Payload[0] == -2, "166 executed is not -2"); Check(fails, meetingHarness.ExecutedActors.Count == 1 && meetingHarness.ExecutedActors[0] == -2, "execute instruction not issued exactly once"); InboundMessage inboundMessage5 = meetingHarness.LastReceived(168); Check(fails, inboundMessage5 != null && (int)inboundMessage5.Payload[0] == -2 && (byte)inboundMessage5.Payload[1] == 0, "168 with cause=Vote not received"); Check(fails, meetingHarness.Client.GetRowStatus(-2) == RowStatus.Executed, "row status is not Executed"); Check(fails, meetingHarness.Client.Result != null && meetingHarness.Client.Result.ExecutedActor == -2, "client result not applied"); meetingHarness.Clock += 6000L; meetingHarness.Meeting.Tick(meetingHarness.Clock); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Idle, "stage is not Idle after ClosingHold"); InboundMessage inboundMessage6 = meetingHarness.LastReceived(172); Check(fails, inboundMessage6 != null && (byte)inboundMessage6.Payload[0] == 1, "172(Play) not received"); Check(fails, !meetingHarness.Client.MeetingActive, "client state still active after Play"); int condition; if (meetingHarness.RoomMeeting.Count >= 2) { (int, long) tuple = meetingHarness.RoomMeeting[meetingHarness.RoomMeeting.Count - 1]; condition = ((tuple.Item1 == -1 && tuple.Item2 == 0) ? 1 : 0); } else { condition = 0; } Check(fails, (byte)condition != 0, "room property not cleared at meeting end"); Check(fails, meetingHarness.Game.Winner == null, "unexpected game over"); } } private static void ScenarioMeetingVoteSecrecy(List fails) { List<(string Line, bool Secret)> recorded = new List<(string, bool)>(); Action prev = WLog.Sink; WLog.Sink = delegate(string line, bool secret) { recorded.Add((line, secret)); prev?.Invoke(line, secret); }; try { MeetingHarness meetingHarness = new MeetingHarness(4, (-3, Role.Werewolf)); meetingHarness.StartVoting(); meetingHarness.SelfVote(-2); int num = 0; foreach (var (text, flag) in recorded) { if (text.Contains("code=164")) { num++; if (!flag) { fails.Add("164 bus record is not secret: " + text); } } } Check(fails, num >= 1, "no 164 bus record captured"); foreach (OutboundMessage item in meetingHarness.HostSent) { if (item.Code == 164) { fails.Add("host re-sent code 164"); } } InboundMessage inboundMessage = meetingHarness.LastReceived(165); Check(fails, inboundMessage != null && inboundMessage.Payload.Length == 2, "165 payload shape unexpected"); if (inboundMessage != null) { Check(fails, ContainsInt((int[])inboundMessage.Payload[0], 1), "165 voted set missing voter"); } meetingHarness.Meeting.CastVote(-1, -1, meetingHarness.Clock); meetingHarness.Meeting.CastVote(-2, -1, meetingHarness.Clock); meetingHarness.Meeting.CastVote(-3, -1, meetingHarness.Clock); meetingHarness.Meeting.Tick(meetingHarness.Clock); InboundMessage inboundMessage2 = meetingHarness.LastReceived(166); Check(fails, inboundMessage2 != null && inboundMessage2.Payload.Length == 3, "166 payload shape unexpected"); Check(fails, EventCodes.IsSecret(164), "164 not classified as secret"); Check(fails, EventCodes.IsMasterInbound(164), "164 not master-inbound"); Check(fails, EventCodes.IsMasterInbound(173), "173 not master-inbound"); } finally { WLog.Sink = prev; } } private static void ScenarioMeetingLeaveNoExecution(List fails) { MeetingHarness meetingHarness = new MeetingHarness(4, (-3, Role.Werewolf)); meetingHarness.StartVoting(); meetingHarness.SelfVote(-2); meetingHarness.Meeting.CastVote(-1, -2, meetingHarness.Clock); int num = meetingHarness.CountReceived(165); meetingHarness.Bus.SimulatePlayerLeft(-2); Check(fails, meetingHarness.CountReceived(165) == num + 1, "165 not resent on player left"); Check(fails, meetingHarness.Client.GetRowStatus(-2) == RowStatus.Disconnected, "row status is not Disconnected"); meetingHarness.Meeting.Tick(meetingHarness.Clock); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Voting, "closed before remaining voters voted"); meetingHarness.Meeting.CastVote(-3, -1, meetingHarness.Clock); meetingHarness.Meeting.Tick(meetingHarness.Clock); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Closing, "not closed after all remaining votes"); InboundMessage inboundMessage = meetingHarness.LastReceived(166); Check(fails, inboundMessage != null && (int)inboundMessage.Payload[0] == -1, "166 executed is not -1 (no execution)"); if (inboundMessage != null) { int[] array = (int[])inboundMessage.Payload[1]; int[] array2 = (int[])inboundMessage.Payload[2]; bool condition = false; for (int i = 0; i < array.Length; i++) { if (array[i] == -2 && array2[i] == 2) { condition = true; } } Check(fails, condition, "vote counts for disconnected target not disclosed"); } Check(fails, meetingHarness.ExecutedActors.Count == 0, "execute instruction issued unexpectedly"); meetingHarness.Clock += 6000L; meetingHarness.Meeting.Tick(meetingHarness.Clock); Check(fails, meetingHarness.Meeting.Stage == MeetingStage.Idle, "stage is not Idle after ClosingHold"); InboundMessage inboundMessage2 = meetingHarness.LastReceived(172); Check(fails, inboundMessage2 != null && (byte)inboundMessage2.Payload[0] == 1, "172(Play) not received"); } private static void ScenarioMeetingRestoreFromRoomState(List fails) { MeetingHarness meetingHarness = new MeetingHarness(4, (-3, Role.Werewolf)); meetingHarness.Convene(); Check(fails, meetingHarness.RoomMeeting.Count == 1, "room property publish not issued on convene"); if (meetingHarness.RoomMeeting.Count != 0) { var (num, num2) = meetingHarness.RoomMeeting[meetingHarness.RoomMeeting.Count - 1]; Check(fails, num == 1, "published caller is not 1"); Check(fails, num2 == meetingHarness.Meeting.EndUnixMs, "published end differs from session end"); InboundMessage inboundMessage = meetingHarness.LastReceived(163); meetingHarness.Clock = (long)inboundMessage.Payload[1]; meetingHarness.Meeting.Tick(meetingHarness.Clock); meetingHarness.SelfVote(-2); MeetingClientState meetingClientState = new MeetingClientState(); meetingClientState.RestoreFromRoomState(num, num2); Check(fails, meetingClientState.MeetingActive, "restored client not active"); Check(fails, meetingClientState.CallerActor == 1, "restored caller is not 1"); Check(fails, meetingClientState.WarpDone(meetingHarness.Clock), "restore did not treat warp as done"); Check(fails, meetingClientState.RemainingMs(meetingHarness.Clock) > 0, "no remaining time after restore"); Check(fails, meetingClientState.VotedActors.Count == 0, "restored voted set is not empty"); InboundMessage inboundMessage2 = meetingHarness.LastReceived(165); Check(fails, inboundMessage2 != null, "165 not received"); if (inboundMessage2 != null) { long num3 = meetingClientState.RemainingMs(meetingHarness.Clock); meetingClientState.ApplyVoteProgress((int[])inboundMessage2.Payload[0], (long)inboundMessage2.Payload[1]); Check(fails, ContainsInt(meetingClientState.VotedActors, 1), "voted set not synced by next 165"); Check(fails, meetingClientState.RemainingMs(meetingHarness.Clock) < num3, "shortened end not synced by next 165"); } } } private static void ScenarioMeetingVoteRejectReenable(List fails) { MeetingHarness meetingHarness = new MeetingHarness(4, (-3, Role.Werewolf)); meetingHarness.StartVoting(); meetingHarness.Bus.SimulatePlayerLeft(-2); int num = meetingHarness.CountReceived(165); Check(fails, num >= 1, "165 not resent on player left"); meetingHarness.SelfVote(-2); Check(fails, meetingHarness.CountReceived(165) == num + 1, "vote for disconnected target did not emit 165"); Check(fails, ContainsInt(meetingHarness.Client.VotedActors, 1), "voter for disconnected target not marked as voted"); } private static GameConfig RolesConfig() { return new GameConfig { RoundSeconds = 600, BlackCatRevealDelaySec = 60, WerewolfCount = 2, BlackCatChancePercent = 50, BomberChancePercent = 50, MeetingRightsPerPlayer = 2, ConveneSuppressStartSec = 0, ConveneSuppressAfterSec = 0, MeetingCountdownSec = 5, MeetingDurationSec = 120, ResultDisplaySec = 6, BeaconSuppressStartSec = 0, CurseWaitSec = 10, StaminaUnlockPct = 15, JumpUnlockPct = 30, EnemyIgnoreUnlockPct = 50, HealUnlockPct = 70, BeaconChargePct = 10, InformantThresholdPct = 60, BomberAmmoRefillPct = 30 }; } private static int LastBeaconAuditCount(RolesHarness h) { for (int num = h.M.HostSent.Count - 1; num >= 0; num--) { if (h.M.HostSent[num].Code == 167) { return (byte)h.M.HostSent[num].Payload[0]; } } return -1; } private static void ScenarioRolesGaugeFreezeAndFloor(List fails) { Check(fails, PerkGauge.ComputeRealLoss(600f, 700f, 1000f) == 600f, "floor loss is not full remaining"); Check(fails, PerkGauge.ComputeRealLoss(600f, 100f, 1000f) == 100f, "normal loss is not valueLost"); PerkGauge perkGauge = new PerkGauge(RolesConfig()); Check(fails, perkGauge.AddLoss(100f).Count == 0, "loss counted before freeze"); Check(fails, perkGauge.DisplayPermille == 0, "permille not 0 before freeze"); perkGauge.FreezeBase(10000f); perkGauge.FreezeBase(99999f); Check(fails, perkGauge.BaseDollars == 10000f, "base changed by second freeze"); perkGauge.AddLoss(15000f); Check(fails, perkGauge.DisplayPermille == 1000, "permille not capped at 1000"); Check(fails, perkGauge.LostDollars == 15000f, "internal total unexpectedly capped"); GameConfig gameConfig = RolesConfig(); gameConfig.OrbGaugeEnabled = false; RolesHarness rolesHarness = new RolesHarness(gameConfig, 4, (-1, Role.Werewolf)); rolesHarness.Roles.FreezeBase(10000f); rolesHarness.Roles.AddValueLoss(1000f, isOrb: true); Check(fails, rolesHarness.Roles.Gauge.LostDollars == 0f, "orb loss counted despite disabled"); rolesHarness.Roles.AddValueLoss(1000f, isOrb: false); Check(fails, rolesHarness.Roles.Gauge.LostDollars == 1000f, "normal loss not counted"); } private static void ScenarioRolesPerkUnlockOrderOnce(List fails) { PerkGauge perkGauge = new PerkGauge(RolesConfig()); perkGauge.FreezeBase(10000f); IReadOnlyList readOnlyList = perkGauge.AddLoss(2000f); Check(fails, readOnlyList.Count == 2, $"20pct events={readOnlyList.Count} expected=2"); Check(fails, readOnlyList.Count == 2 && readOnlyList[0].Kind == GaugeEventKind.PerkUnlocked && readOnlyList[0].Perk == PerkId.InfiniteStamina, "first unlock is not stamina"); Check(fails, readOnlyList.Count == 2 && readOnlyList[1].Kind == GaugeEventKind.BeaconCharged && readOnlyList[1].BeaconChargeCount == 2, "charge count at 20pct is not 2"); Check(fails, perkGauge.AddLoss(500f).Count == 0, "duplicate events on 25pct"); readOnlyList = perkGauge.AddLoss(3000f); Check(fails, readOnlyList.Count == 3, $"55pct events={readOnlyList.Count} expected=3"); Check(fails, readOnlyList.Count == 3 && readOnlyList[0].Perk == PerkId.InfiniteJump, "second unlock is not jump"); Check(fails, readOnlyList.Count == 3 && readOnlyList[1].Perk == PerkId.EnemyIgnore, "third unlock is not enemyIgnore"); Check(fails, readOnlyList.Count == 3 && readOnlyList[2].Kind == GaugeEventKind.BeaconCharged && readOnlyList[2].BeaconChargeCount == 3, "charge count at 55pct is not 3"); readOnlyList = perkGauge.AddLoss(1000f); bool condition = false; foreach (GaugeEvent item in readOnlyList) { if (item.Kind == GaugeEventKind.InformantReady) { condition = true; } } Check(fails, condition, "informant not fired at 65pct"); readOnlyList = perkGauge.AddLoss(1000f); bool condition2 = false; foreach (GaugeEvent item2 in readOnlyList) { Check(fails, item2.Kind != GaugeEventKind.InformantReady, "informant fired twice"); if (item2.Kind == GaugeEventKind.PerkUnlocked) { Check(fails, item2.Perk == PerkId.NaturalHeal, "unexpected perk at 75pct"); condition2 = true; } } Check(fails, condition2, "heal not unlocked at 75pct"); readOnlyList = perkGauge.AddLoss(1000f); foreach (GaugeEvent item3 in readOnlyList) { Check(fails, item3.Kind != GaugeEventKind.InformantReady, "informant fired twice"); Check(fails, item3.Kind != GaugeEventKind.PerkUnlocked, "perk unlocked twice"); } Check(fails, perkGauge.UnlockedFlags == (PerkFlags.InfiniteStamina | PerkFlags.InfiniteJump | PerkFlags.EnemyIgnore | PerkFlags.NaturalHeal), "unlocked flags incomplete"); } private static void ScenarioRolesBeaconGate(List fails) { GameConfig gameConfig = RolesConfig(); gameConfig.BeaconCooldownSec = 60; BeaconState beaconState = new BeaconState(gameConfig); Check(fails, beaconState.TryUse(1000000L) == BeaconStatus.NoCharge, "empty use not NoCharge"); beaconState.AddCharges(2); beaconState.Suppress(1030000L); Check(fails, beaconState.TryUse(1000000L) == BeaconStatus.Suppressed, "suppressed use not rejected"); Check(fails, beaconState.Charges == 2, "charge consumed while suppressed"); Check(fails, beaconState.ReadyUnixMs == 1030000, "readyUnixMs not suppression end"); long num = 1030000L; Check(fails, beaconState.TryUse(num) == BeaconStatus.Ok, "use after suppression failed"); Check(fails, beaconState.Charges == 1, "charge not consumed on ok"); Check(fails, beaconState.TryUse(num + 1000) == BeaconStatus.Cooldown, "cooldown not enforced"); Check(fails, beaconState.Charges == 1, "charge consumed during cooldown"); Check(fails, beaconState.ReadyUnixMs == num + 60000, "readyUnixMs not cooldown end"); Check(fails, beaconState.TryUse(num + 60000) == BeaconStatus.Ok, "use after cooldown failed"); Check(fails, beaconState.Charges == 0, "charge count wrong after second use"); } private static void ScenarioRolesBeaconMeetingEndSync(List fails) { GameConfig gameConfig = RolesConfig(); gameConfig.BeaconSuppressAfterMeetingSec = 45; RolesHarness rolesHarness = new RolesHarness(gameConfig, 4, (1, Role.Werewolf)); int num = rolesHarness.CountGaugeSentTo(1); rolesHarness.Roles.OnMeetingEnded(rolesHarness.M.Clock); Check(fails, rolesHarness.CountGaugeSentTo(1) == num + 1, "171 not sent on meeting end"); long num2 = LastGaugeReadyUnixMsTo(rolesHarness, 1); Check(fails, num2 == rolesHarness.M.Clock + 45000, "readyUnixMs not meeting-end suppression end"); } private static long LastGaugeReadyUnixMsTo(RolesHarness h, int actor) { long result = -1L; foreach (OutboundMessage item in h.M.HostSent) { if (item.Code == 171 && item.Target == MessageTarget.Actors && item.TargetActors != null && ContainsInt(item.TargetActors, actor)) { result = (long)item.Payload[4]; } } return result; } private static void ScenarioRolesInformantConditions(List fails) { RolesHarness rolesHarness = new RolesHarness(5, (-1, Role.Werewolf), (-2, Role.BlackCat)); rolesHarness.M.Game.RecordDeath(-2, 1001000L); rolesHarness.Roles.FreezeBase(10000f); rolesHarness.Roles.AddValueLoss(7000f, isOrb: false); Check(fails, rolesHarness.InformantFires == 0, "informant fired though cat dead"); Check(fails, !rolesHarness.Roles.InformantEstablished, "informant established though cat dead"); RolesHarness rolesHarness2 = new RolesHarness(5, (-1, Role.Werewolf), (-2, Role.BlackCat)); rolesHarness2.Roles.FreezeBase(10000f); rolesHarness2.Roles.AddValueLoss(7000f, isOrb: false); rolesHarness2.Roles.AddValueLoss(1000f, isOrb: false); Check(fails, rolesHarness2.InformantFires == 1, $"informant fires={rolesHarness2.InformantFires} expected=1"); Check(fails, rolesHarness2.Roles.InformantEstablished, "informant not established"); bool condition = false; foreach (OutboundMessage item in rolesHarness2.M.HostSent) { if (item.Code == 162 && item.Target == MessageTarget.Actors && item.TargetActors != null && ContainsInt(item.TargetActors, -2)) { condition = true; } } Check(fails, condition, "162 not sent to blackcat on informant"); } private static void ScenarioRolesCurseResolution(List fails) { List list = new List { new WPlayer { ActorNumber = 1, Role = Role.Villager }, new WPlayer { ActorNumber = -1, Role = Role.Werewolf }, new WPlayer { ActorNumber = -2, Role = Role.BlackCat, Alive = false }, new WPlayer { ActorNumber = -3, Role = Role.Villager } }; long num = 1010000L; CurseSession curseSession = new CurseSession(-2, num); Check(fails, !curseSession.Designate(1, -1, 1001000L), "non-cat designate accepted"); Check(fails, curseSession.Designate(-2, -1, 1001000L), "cat designate rejected"); Check(fails, curseSession.Designate(-2, -3, 1005000L), "overwrite rejected"); Check(fails, curseSession.TryResolve(num - 1, list, informantEstablished: false, new Random(1)) == null, "resolved before deadline"); Check(fails, curseSession.ResolveAtUnixMs == num, "deadline changed by designation"); CurseResolution curseResolution = curseSession.TryResolve(num, list, informantEstablished: false, new Random(1)); Check(fails, curseResolution != null && curseResolution.VictimActor == -3 && curseResolution.WasDesignated, "designated victim not -3"); Check(fails, curseSession.TryResolve(num + 1000, list, informantEstablished: false, new Random(1)) == null, "resolved twice"); HashSet hashSet = new HashSet(); for (int i = 0; i < 40; i++) { CurseResolution curseResolution2 = new CurseSession(-2, num).TryResolve(num, list, informantEstablished: false, new Random(i)); if (curseResolution2 != null) { hashSet.Add(curseResolution2.VictimActor); } } Check(fails, hashSet.Contains(-1), "pre-informant random never hits werewolf"); Check(fails, !hashSet.Contains(-2), "random hit the executed cat"); hashSet.Clear(); for (int j = 0; j < 40; j++) { CurseResolution curseResolution3 = new CurseSession(-2, num).TryResolve(num, list, informantEstablished: true, new Random(j)); if (curseResolution3 != null) { hashSet.Add(curseResolution3.VictimActor); } } Check(fails, !hashSet.Contains(-1), "post-informant random hit werewolf"); Check(fails, hashSet.Contains(1) && hashSet.Contains(-3), "post-informant range too narrow"); CurseSession curseSession2 = new CurseSession(-2, num); curseSession2.Designate(-2, -1, 1001000L); list[1].Alive = false; CurseResolution curseResolution4 = curseSession2.TryResolve(num, list, informantEstablished: false, new Random(3)); Check(fails, curseResolution4 != null && !curseResolution4.WasDesignated && (curseResolution4.VictimActor == 1 || curseResolution4.VictimActor == -3), "dead designation did not fall back to random"); List players = new List { new WPlayer { ActorNumber = 1, Role = Role.Villager }, new WPlayer { ActorNumber = -1, Role = Role.Werewolf }, new WPlayer { ActorNumber = -2, Role = Role.BlackCat, Alive = false }, new WPlayer { ActorNumber = -3, Role = Role.Villager } }; int[] voterActors = new int[2] { 1, -1 }; CurseSession curseSession3 = new CurseSession(-2, num, voterActors); Check(fails, !curseSession3.Designate(-2, -3, 1001000L), "non-voter designation accepted"); Check(fails, curseSession3.Designate(-2, 1, 1001000L), "voter designation rejected"); hashSet.Clear(); for (int k = 0; k < 40; k++) { CurseResolution curseResolution5 = new CurseSession(-2, num, voterActors).TryResolve(num, players, informantEstablished: false, new Random(k)); if (curseResolution5 != null) { hashSet.Add(curseResolution5.VictimActor); } } Check(fails, hashSet.Contains(1) && hashSet.Contains(-1) && !hashSet.Contains(-3), "restricted random not limited to voters"); hashSet.Clear(); for (int l = 0; l < 40; l++) { CurseResolution curseResolution6 = new CurseSession(-2, num, voterActors).TryResolve(num, players, informantEstablished: true, new Random(l)); if (curseResolution6 != null) { hashSet.Add(curseResolution6.VictimActor); } } Check(fails, hashSet.Contains(1) && !hashSet.Contains(-1), "post-informant restricted random did not prefer villager voters"); CurseResolution curseResolution7 = new CurseSession(-2, num, new int[1] { -1 }).TryResolve(num, players, informantEstablished: true, new Random(1)); Check(fails, curseResolution7 != null && curseResolution7.VictimActor == -1, "wolf-only voters did not hit wolf post-informant"); List players2 = new List { new WPlayer { ActorNumber = 1, Role = Role.Villager }, new WPlayer { ActorNumber = -2, Role = Role.BlackCat, Alive = false }, new WPlayer { ActorNumber = -3, Role = Role.Villager, Alive = false } }; CurseResolution curseResolution8 = new CurseSession(-2, num, new int[1] { -3 }).TryResolve(num, players2, informantEstablished: false, new Random(1)); Check(fails, curseResolution8 != null && !curseResolution8.HasVictim, "no alive voters did not fizzle (expected NoVictim)"); } private static void ScenarioRolesCurseWinJudge(List fails) { GameConfig gameConfig = RolesConfig(); gameConfig.WerewolfCount = 1; RolesHarness rolesHarness = new RolesHarness(gameConfig, 4, (-1, Role.Werewolf), (-2, Role.BlackCat)); rolesHarness.M.Game.MarkNextDeathAsVote(-2); rolesHarness.M.Game.RecordDeath(-2, rolesHarness.M.Clock); Check(fails, rolesHarness.Roles.TryStartCurse(-2, rolesHarness.M.Clock, out var holdExtensionMs), "curse not started"); long num = 7000L; Check(fails, holdExtensionMs == num, $"holdMs={holdExtensionMs} expected={num}"); rolesHarness.Roles.HandleRoleAction(-2, 0, -1, 0, rolesHarness.M.Clock + 1000); rolesHarness.M.Clock += 10000L; rolesHarness.Roles.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.CurseKills.Count == 1 && rolesHarness.CurseKills[0] == -1, "curse kill not issued to -1"); Check(fails, rolesHarness.M.Game.Winner != null && rolesHarness.M.Game.Winner.WinningTeam == Team.Villagers, "villagers did not win after curse eradication"); Check(fails, rolesHarness.CountHostSent(175, 1) == 1, "175(resolved) not sent exactly once"); } private static void ScenarioRolesActionValidation(List fails) { GameConfig gameConfig = RolesConfig(); gameConfig.WerewolfCount = 1; RolesHarness rolesHarness = new RolesHarness(gameConfig, 5, (-1, Role.Werewolf), (-2, Role.BlackCat)); rolesHarness.Roles.DebugChargeBeacon(3); rolesHarness.Roles.HandleRoleAction(1, 1, 0, 0, rolesHarness.M.Clock); rolesHarness.Roles.HandleRoleAction(-2, 1, 0, 0, rolesHarness.M.Clock); Check(fails, rolesHarness.BeaconTriggers.Count == 0, "non-wolf beacon accepted"); rolesHarness.Roles.HandleRoleAction(1, 2, 0, 1, rolesHarness.M.Clock); Check(fails, rolesHarness.EnemyIgnoreChanges.Count == 0, "non-wolf wolfmode accepted"); rolesHarness.Roles.HandleRoleAction(-1, 1, 0, 0, rolesHarness.M.Clock); Check(fails, rolesHarness.BeaconTriggers.Count == 1 && rolesHarness.BeaconTriggers[0] == -1, "wolf beacon not triggered"); Check(fails, rolesHarness.CountHostSent(167) == 0, "167 sent on use"); int num = rolesHarness.CountGaugeSentTo(-1); rolesHarness.M.Convene(); Check(fails, rolesHarness.M.Game.Phase == GamePhase.Meeting, "phase did not become Meeting"); rolesHarness.Roles.HandleRoleAction(-1, 1, 0, 0, rolesHarness.M.Clock); Check(fails, rolesHarness.BeaconTriggers.Count == 1, "beacon triggered during meeting"); Check(fails, rolesHarness.CountHostSent(167) == 0, "167 sent during meeting"); Check(fails, rolesHarness.CountGaugeSentTo(-1) == num + 1, "MeetingActive 171 not returned to caller"); InboundMessage inboundMessage = rolesHarness.M.LastReceived(163); Check(fails, inboundMessage != null, "163 not received after convene"); if (inboundMessage != null) { rolesHarness.M.Clock = (long)inboundMessage.Payload[1]; rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.CountHostSent(167) == 1, "audit 167 not sent on voting start"); Check(fails, LastBeaconAuditCount(rolesHarness) == 1, "audit count is not 1"); rolesHarness.Roles.OnMeetingEnded(rolesHarness.M.Clock); rolesHarness.Roles.OnMeetingStarted(rolesHarness.M.Clock); Check(fails, rolesHarness.CountHostSent(167) == 2, "second audit 167 not sent"); Check(fails, LastBeaconAuditCount(rolesHarness) == 0, "audit ledger not reset after disclosure"); } rolesHarness.Roles.HandleRoleAction(-1, 2, 0, 1, rolesHarness.M.Clock); Check(fails, rolesHarness.EnemyIgnoreChanges.Count == 1 && !rolesHarness.EnemyIgnoreChanges[0].Enabled, "enemy ignore effective without unlock"); rolesHarness.Roles.DebugUnlockPerk(PerkId.EnemyIgnore); Check(fails, rolesHarness.EnemyIgnoreChanges.Count == 2 && rolesHarness.EnemyIgnoreChanges[1].Enabled && rolesHarness.EnemyIgnoreChanges[1].Actor == -1, "enemy ignore not enabled on unlock during wolfmode"); rolesHarness.Roles.OnPlayerDied(-1); Check(fails, rolesHarness.EnemyIgnoreChanges.Count == 3 && !rolesHarness.EnemyIgnoreChanges[2].Enabled, "enemy ignore not disabled on death"); RolesClientState rolesClientState = new RolesClientState(); Check(fails, !rolesClientState.TryToggleWolfMode(Role.Werewolf), "toggle succeeded with no unlocks"); Check(fails, !rolesClientState.TryToggleWolfMode(Role.Villager), "toggle succeeded for villager"); rolesClientState.ApplyGaugeSync(200, 1, 0, 0, 0L, null, 0L); Check(fails, !rolesClientState.WolfMode, "wolfmode auto-on by unlock"); Check(fails, rolesClientState.TryToggleWolfMode(Role.Werewolf), "toggle failed with unlock"); Check(fails, rolesClientState.StaminaActive && !rolesClientState.JumpActive, "effective state wrong"); rolesClientState.ApplyGaugeSync(400, 3, 0, 0, 0L, null, 0L); Check(fails, rolesClientState.JumpActive, "new unlock not effective immediately during wolfmode"); rolesClientState.Reset(); Check(fails, !rolesClientState.WolfMode && rolesClientState.UnlockedFlags == PerkFlags.None, "reset incomplete"); } private static void ScenarioRolesFullCurseFlow(List fails) { RolesHarness rolesHarness = new RolesHarness(5, (-2, Role.BlackCat), (-3, Role.Werewolf)); long num = 0L; foreach (OutboundMessage item in rolesHarness.M.HostSent) { if (item.Code == 170) { num = (long)item.Payload[0]; } } rolesHarness.M.StartVoting(); long clock = rolesHarness.M.Clock; rolesHarness.M.Meeting.CastVote(1, -2, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-1, -2, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-2, -3, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-3, -2, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-4, -3, rolesHarness.M.Clock); rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.M.Meeting.Stage == MeetingStage.Closing, "not Closing after votes"); CurseSession activeCurse = rolesHarness.Roles.ActiveCurse; Check(fails, activeCurse != null && activeCurse.CatActor == -2, "curse not started for cat"); Check(fails, rolesHarness.CountHostSent(175, 0) == 1, "175(started) not sent exactly once"); if (activeCurse != null) { rolesHarness.M.Clock += 6500L; rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.M.Meeting.Stage == MeetingStage.Closing, "closing ended despite curse hold"); Check(fails, rolesHarness.CountHostSent(179) == 0, "179 sent for bot cat"); rolesHarness.Roles.HandleRoleAction(-2, 0, -4, 0, rolesHarness.M.Clock); rolesHarness.Roles.HandleRoleAction(-2, 0, -1, 0, rolesHarness.M.Clock); rolesHarness.M.Clock = activeCurse.ResolveAtUnixMs; rolesHarness.Roles.Tick(rolesHarness.M.Clock); rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.CurseKills.Count == 1 && rolesHarness.CurseKills[0] == -1, "designated voter victim not killed (non-voter rejection broken?)"); Check(fails, rolesHarness.CountHostSent(175, 1) == 1, "175(resolved) not sent"); InboundMessage inboundMessage = rolesHarness.M.LastReceived(168); Check(fails, inboundMessage != null && (int)inboundMessage.Payload[0] == -1 && (byte)inboundMessage.Payload[1] == 1, "curse death not recorded as non-vote"); Check(fails, rolesHarness.M.Meeting.Stage == MeetingStage.Closing, "closing ended at resolve time"); rolesHarness.M.Clock = clock + 13000; rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.M.Meeting.Stage == MeetingStage.Idle, "meeting not finished after hold"); InboundMessage inboundMessage2 = rolesHarness.M.LastReceived(172); Check(fails, inboundMessage2 != null && (byte)inboundMessage2.Payload[0] == 1, "172(Play) not received"); if (inboundMessage2 != null && num != 0L) { long num2 = (long)inboundMessage2.Payload[2]; long num3 = rolesHarness.M.Clock - clock; Check(fails, num2 >= num + num3, $"round end not extended by curse hold: newEnd={num2} expected>={num + num3}"); } Check(fails, rolesHarness.M.Game.Winner == null, "unexpected game over"); } } private static void ScenarioRolesGaugeDistributionTargets(List fails) { List players = new List { new WPlayer { ActorNumber = 1, Name = "V", Role = Role.Villager }, new WPlayer { ActorNumber = 2, Name = "W", Role = Role.Werewolf }, new WPlayer { ActorNumber = 3, Name = "C", Role = Role.BlackCat } }; GameSession gameSession = new GameSession(); List list = new List(); gameSession.OnSend += list.Add; gameSession.ReserveForcedRole(1, Role.Villager); gameSession.ReserveForcedRole(2, Role.Werewolf); gameSession.ReserveForcedRole(3, Role.BlackCat); GameConfig gameConfig = RolesConfig(); if (!gameSession.Start(gameConfig, players, 1000000L, new Random(9)).Success) { throw new InvalidOperationException("start rejected"); } RolesSession rolesSession = new RolesSession(gameConfig, gameSession, 1000000L, new Random(9)); rolesSession.OnSend += list.Add; list.Clear(); rolesSession.FreezeBase(10000f); int toWolf = 0; int toCat = 0; int toVillager = 0; CountGaugeTargets(list, ref toWolf, ref toCat, ref toVillager); Check(fails, toWolf == 1, $"gauge to wolf={toWolf} expected=1"); Check(fails, toCat == 0, "gauge sent to unawakened cat"); Check(fails, toVillager == 0, "gauge sent to villager"); gameSession.NotifyDisclosureCondition(DisclosureKind.BlackCatSelfAwareness); list.Clear(); rolesSession.AddValueLoss(500f, isOrb: false); toWolf = 0; toCat = 0; toVillager = 0; CountGaugeTargets(list, ref toWolf, ref toCat, ref toVillager); Check(fails, toWolf == 1 && toCat == 0, $"gauge on change wolf={toWolf} cat={toCat} expected wolf=1 cat=0(periodic)"); Check(fails, toVillager == 0, "gauge sent to villager after awaken"); rolesSession.Tick(1001000L); toWolf = 0; toCat = 0; toVillager = 0; CountGaugeTargets(list, ref toWolf, ref toCat, ref toVillager); Check(fails, toCat == 1, $"periodic tick cat={toCat} expected=1"); int[] array = null; foreach (OutboundMessage item in list) { if (item.Code == 171 && item.TargetActors != null && ContainsInt(item.TargetActors, 3)) { array = (int[])item.Payload[5]; } } Check(fails, array != null && array.Length >= 7 && array[6] == gameConfig.CatGaugeSyncIntervalSec, "cat gaugeMeta[6] not next-update seconds"); rolesSession.Tick(1002000L); rolesSession.AddValueLoss(500f, isOrb: false); toWolf = 0; toCat = 0; toVillager = 0; CountGaugeTargets(list, ref toWolf, ref toCat, ref toVillager); Check(fails, toCat == 1, $"cat resynced within interval cat={toCat} expected=1"); foreach (OutboundMessage item2 in list) { if (item2.Code == 171) { Check(fails, item2.Target == MessageTarget.Actors && item2.TargetActors != null && item2.TargetActors.Length == 1, "171 not single-target"); } } } private static void CountGaugeTargets(List sent, ref int toWolf, ref int toCat, ref int toVillager) { foreach (OutboundMessage item in sent) { if (item.Code == 171 && item.TargetActors != null) { if (ContainsInt(item.TargetActors, 2)) { toWolf++; } if (ContainsInt(item.TargetActors, 3)) { toCat++; } if (ContainsInt(item.TargetActors, 1)) { toVillager++; } } } } private static void ScenarioRolesMeetingGaugeSnapshot(List fails) { RolesHarness rolesHarness = new RolesHarness(4, (-1, Role.Werewolf)); rolesHarness.Roles.FreezeBase(10000f); rolesHarness.Roles.AddValueLoss(2500f, isOrb: false); rolesHarness.M.StartVoting(); Check(fails, rolesHarness.CountHostSent(175, 2) == 1, "175(gauge) not sent exactly once at meeting start"); OutboundMessage outboundMessage = null; foreach (OutboundMessage item in rolesHarness.M.HostSent) { if (item.Code == 175 && (byte)item.Payload[0] == 2) { outboundMessage = item; } } Check(fails, outboundMessage != null && outboundMessage.Target == MessageTarget.All, "175(gauge) not to all"); if (outboundMessage != null) { int[] array = (int[])outboundMessage.Payload[1]; Check(fails, array.Length == 13, $"gauge data length={array.Length} expected=13"); Check(fails, array[0] == 250, $"gauge permille={array[0]} expected=250"); Check(fails, array[1] == 10000, $"gauge base={array[1]} expected=10000"); Check(fails, array[7] == 2500, $"gauge lostDollars={array[7]} expected=2500"); Check(fails, array[8] == -1 && array[9] == -1, $"gauge delivery defaults=({array[8]},{array[9]}) expected=(-1,-1)"); Check(fails, array[10] == 30, $"gauge bombRefillPct={array[10]} expected=30"); Check(fails, array[11] == -1, $"gauge checkmateLine={array[11]} expected=-1"); Check(fails, array[12] == 70, $"gauge healPct={array[12]} expected=70"); } rolesHarness.Roles.AddValueLoss(2000f, isOrb: false); Check(fails, rolesHarness.CountHostSent(175, 2) == 1, "175(gauge) resent during meeting"); rolesHarness.M.Meeting.CastVote(1, -1, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-1, -1, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-2, -1, rolesHarness.M.Clock); rolesHarness.M.Meeting.CastVote(-3, -1, rolesHarness.M.Clock); rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); rolesHarness.M.Clock += 7000L; rolesHarness.M.Meeting.Tick(rolesHarness.M.Clock); Check(fails, rolesHarness.M.Meeting.Stage == MeetingStage.Idle, "meeting not finished"); rolesHarness.M.Clock += 1000L; rolesHarness.M.StartVoting(); Check(fails, rolesHarness.CountHostSent(175, 2) == 2, "175(gauge) not sent on second meeting"); } private static void ScenarioBomberFullFlow(List fails) { GameConfig gameConfig = new GameConfig { RoundSeconds = 600, BomberInitialCooldownSec = 60, BomberCooldownSec = 30, BomberAmmoRefillPct = 30, BomberWarningSec = 1f }; List players = new List { new WPlayer { ActorNumber = 1, Name = "Self", Role = Role.Bomber }, new WPlayer { ActorNumber = 2, Name = "Bot1", IsBot = true, Role = Role.Werewolf }, new WPlayer { ActorNumber = 3, Name = "Bot2", IsBot = true, Role = Role.Villager }, new WPlayer { ActorNumber = 4, Name = "Bot3", IsBot = true, Role = Role.Villager } }; BombHarness bombHarness = new BombHarness(gameConfig, players, 1000000L); Check(fails, bombHarness.Bomb.BomberActor == 1, "bomber actor not resolved to 1"); Check(fails, bombHarness.CountReceived(181) == 1, $"initial 181 count={bombHarness.CountReceived(181)} expected=1"); Check(fails, bombHarness.Client.Ammo == 1, "client ammo not 1 after initial 181"); Check(fails, !bombHarness.Client.HasBomb, "client HasBomb not false initially"); Check(fails, bombHarness.Client.PlantReadyUnixMs == 1000000 + (long)gameConfig.BomberInitialCooldownSec * 1000L, $"initial plant ready={bombHarness.Client.PlantReadyUnixMs}"); Check(fails, bombHarness.Client.DetonateReadyUnixMs == 0, $"initial detonate ready={bombHarness.Client.DetonateReadyUnixMs} expected=0"); bombHarness.Bomb.OnGaugeChanged(30f); bombHarness.FlushBomberState(); Check(fails, bombHarness.Bomb.Ammo == 2, $"gauge grant ammo={bombHarness.Bomb.Ammo} expected=2"); Check(fails, bombHarness.Client.Ammo == 2, "client ammo not synced after gauge"); long num = 1000000 + (long)gameConfig.BomberInitialCooldownSec * 1000L; BombDenyReason bombDenyReason = bombHarness.Bomb.TryPlant(1, 3, num); Check(fails, bombDenyReason == BombDenyReason.None, $"plant rejected: {bombDenyReason}"); Check(fails, bombHarness.Bomb.TargetActor == 3, "target not 3 after plant"); Check(fails, bombHarness.Bomb.Ammo == 1, "ammo not decremented after plant"); bombHarness.FlushBomberState(); Check(fails, bombHarness.Client.TargetActor == 3, "client target not 3"); Check(fails, bombHarness.Client.HasBomb, "client HasBomb not true after plant"); long num2 = num + (long)gameConfig.BomberCooldownSec * 1000L; int detonatedTargetActor; BombDenyReason bombDenyReason2 = bombHarness.Bomb.TryDetonate(1, num2, meetingLocked: false, targetNearTruck: false, out detonatedTargetActor); Check(fails, bombDenyReason2 == BombDenyReason.None && detonatedTargetActor == 3, $"detonate rejected: reason={bombDenyReason2} target={detonatedTargetActor}"); long num3 = num2 + (long)(gameConfig.BomberWarningSec * 1000f); bombHarness.SendDetonation(detonatedTargetActor, num3); bombHarness.FlushBomberState(); Check(fails, !bombHarness.Bomb.HasBomb, "bomb not consumed after detonate"); Check(fails, bombHarness.CountReceived(180) == 1, $"180 count={bombHarness.CountReceived(180)} expected=1"); InboundMessage inboundMessage = bombHarness.LastReceived(180); Check(fails, inboundMessage != null && (int)inboundMessage.Payload[0] == 3 && (long)inboundMessage.Payload[1] == num3, "180 payload mismatch"); Check(fails, bombHarness.Client.HasPendingDetonation && bombHarness.Client.PendingTargetActor == 3, "client pending detonation not tracked"); Check(fails, !bombHarness.Client.HasBomb, "client HasBomb not false after detonate"); Check(fails, bombHarness.Client.PlantReadyUnixMs == num2 + (long)gameConfig.BomberCooldownSec * 1000L, "client plant cooldown not restarted after detonate"); Check(fails, bombHarness.Client.DetonateReadyUnixMs == 0, "client detonate cooldown not cleared after detonate"); long num4 = num2 + (long)gameConfig.BomberCooldownSec * 1000L; BombDenyReason bombDenyReason3 = bombHarness.Bomb.TryPlant(1, 4, num4); Check(fails, bombDenyReason3 == BombDenyReason.None, $"replant rejected: {bombDenyReason3}"); bombHarness.FlushBomberState(); bombHarness.Player(4).Alive = false; long num5 = num4 + (long)gameConfig.BomberCooldownSec * 1000L; int detonatedTargetActor2; BombDenyReason bombDenyReason4 = bombHarness.Bomb.TryDetonate(1, num5, meetingLocked: false, targetNearTruck: false, out detonatedTargetActor2); Check(fails, bombDenyReason4 == BombDenyReason.TargetDead, $"dud not TargetDead: {bombDenyReason4}"); Check(fails, !bombHarness.Bomb.HasBomb, "bomb not consumed on dud"); Check(fails, bombHarness.CountReceived(180) == 1, "180 unexpectedly sent on dud"); bombHarness.FlushBomberState(); Check(fails, bombHarness.Client.LastDeny == BombDenyReason.TargetDead || bombHarness.Client.Ammo == bombHarness.Bomb.Ammo, "client did not receive dud state sync"); Check(fails, bombHarness.Client.DetonateReadyUnixMs == 0, "client detonate cooldown not cleared after dud"); bombHarness.Player(1).Alive = false; bombHarness.Bomb.OnPlayerDied(1); Check(fails, bombHarness.Bomb.BomberActor == -1, "bomber actor not -1 after death"); BombDenyReason bombDenyReason5 = bombHarness.Bomb.TryPlant(1, 2, num5 + 60000); Check(fails, bombDenyReason5 == BombDenyReason.NotBomber, $"post-death plant accepted: {bombDenyReason5}"); } } internal static class StructuredLog { private static readonly SecretLogBuffer DeferredSecrets = new SecretLogBuffer(); internal static void Install() { WLog.Sink = Write; Texts.FormatErrorLogger = delegate(string id) { WLog.Line("text_format_error", false, ("id", id)); }; } private static void Write(string line, bool secret) { if (!secret || IsDebugMode()) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)line); } } else { DeferredSecrets.Add(line, DateTime.Now); } } private static bool IsDebugMode() { return Plugin.GameConfig?.DebugMode ?? false; } internal static void FlushDeferredSecrets(string reason) { ManualLogSource logger = Plugin.Logger; if (logger == null) { return; } int dropped; List list = DeferredSecrets.Flush(out dropped); if (list.Count == 0 && dropped == 0) { return; } logger.LogInfo((object)string.Format("{0} secretlog_flush reason={1} count={2} dropped={3}", "[WW]", reason, list.Count, dropped)); foreach (string item in list) { logger.LogInfo((object)item); } logger.LogInfo((object)"[WW] secretlog_flush_end"); } } } namespace Werewolf.Core { public enum AutoStartFireReason { None, AllLoaded, Timeout } public sealed class AutoStartWaitGate { public const int DefaultTimeoutSec = 60; private readonly int _timeoutSec; private long _armedAtUnixMs = -1L; public bool Armed => _armedAtUnixMs >= 0; public AutoStartWaitGate(int timeoutSec = 60) { _timeoutSec = ((timeoutSec > 0) ? timeoutSec : 60); } public long WaitedMs(long nowUnixMs) { if (!Armed) { return 0L; } return nowUnixMs - _armedAtUnixMs; } public void Arm(long nowUnixMs) { if (!Armed) { _armedAtUnixMs = nowUnixMs; } } public void Disarm() { _armedAtUnixMs = -1L; } public AutoStartFireReason ShouldFire(bool allPlayersLoaded, long nowUnixMs) { if (!Armed) { return AutoStartFireReason.None; } if (allPlayersLoaded) { Disarm(); return AutoStartFireReason.AllLoaded; } if (nowUnixMs - _armedAtUnixMs >= (long)_timeoutSec * 1000L) { Disarm(); return AutoStartFireReason.Timeout; } return AutoStartFireReason.None; } } public sealed class BeaconPulseSequence { public const int PulseCount = 10; public const int IntervalMs = 2500; private readonly long _startUnixMs; public BeaconPulseSequence(long startUnixMs) { _startUnixMs = startUnixMs; } public int DuePulses(long nowUnixMs) { if (nowUnixMs < _startUnixMs) { return 0; } long num = (nowUnixMs - _startUnixMs) / 2500 + 1; if (num <= 10) { return (int)num; } return 10; } } public sealed class BeaconState { private readonly GameConfig _config; private long _suppressedUntilUnixMs; private long _cooldownUntilUnixMs; public int Charges { get; private set; } public long ReadyUnixMs { get { if (_suppressedUntilUnixMs <= _cooldownUntilUnixMs) { return _cooldownUntilUnixMs; } return _suppressedUntilUnixMs; } } public BeaconState(GameConfig config) { _config = config ?? throw new ArgumentNullException("config"); } public void AddCharges(int count) { if (count > 0) { Charges += count; } } public void Suppress(long untilUnixMs) { if (untilUnixMs > _suppressedUntilUnixMs) { _suppressedUntilUnixMs = untilUnixMs; } } public void DebugClearRestrictions() { _suppressedUntilUnixMs = 0L; _cooldownUntilUnixMs = 0L; } public BeaconStatus TryUse(long nowUnixMs) { if (nowUnixMs < _suppressedUntilUnixMs) { return BeaconStatus.Suppressed; } if (nowUnixMs < _cooldownUntilUnixMs) { return BeaconStatus.Cooldown; } if (Charges <= 0) { return BeaconStatus.NoCharge; } Charges--; _cooldownUntilUnixMs = nowUnixMs + (long)_config.BeaconCooldownSec * 1000L; return BeaconStatus.Ok; } } public static class BeaconSummonPlan { public const float StepSeconds = 5f; public const float MaxSeconds = 20f; public static float ClampSeconds(int despawnedIndex) { if (despawnedIndex < 0) { despawnedIndex = 0; } float num = 5f * (float)(despawnedIndex + 1); if (!(num > 20f)) { return num; } return 20f; } } public sealed class BeaconSummonGate { private long _nextAllowedUnixMs; public bool TryOpen(long nowUnixMs, int cooldownSec) { if (nowUnixMs < _nextAllowedUnixMs) { return false; } _nextAllowedUnixMs = nowUnixMs + (long)cooldownSec * 1000L; return true; } public void Reset() { _nextAllowedUnixMs = 0L; } } public sealed class BellSchedule { public const int AlertThresholdSec = 300; public const float BaseVolumeScale = 0.5f; public const float FinalVolumeScale = 1f; private const long RearmToleranceMs = 1000L; private static readonly (int ThresholdSec, int IntervalSec)[] Tiers = new(int, int)[5] { (10, 1), (30, 3), (60, 5), (180, 10), (300, 30) }; private static readonly int[] MarksSec = BuildMarks(); private bool _armed; private int _nextIdx; private long _lastRemainingMs; public static bool AlertActive(long remainingMs) { return remainingMs <= 300000; } public static int IntervalSecFor(long remainingSec) { if (remainingSec <= 0) { return 0; } for (int i = 0; i < Tiers.Length; i++) { if (remainingSec <= Tiers[i].ThresholdSec) { return Tiers[i].IntervalSec; } } return 0; } public static float VolumeScaleFor(int markSec) { int item = Tiers[0].ThresholdSec; if (markSec > item) { return 0.5f; } if (markSec < 1) { markSec = 1; } float num = (float)(item - markSec) / (float)(item - 1); return 0.5f + 0.5f * num; } public int Tick(long remainingMs) { if (!_armed || remainingMs > _lastRemainingMs + 1000) { _armed = true; _lastRemainingMs = remainingMs; _nextIdx = 0; while (_nextIdx < MarksSec.Length && (long)MarksSec[_nextIdx] * 1000L >= remainingMs) { _nextIdx++; } return 0; } _lastRemainingMs = remainingMs; int result = 0; while (_nextIdx < MarksSec.Length && remainingMs <= (long)MarksSec[_nextIdx] * 1000L) { result = MarksSec[_nextIdx]; _nextIdx++; } return result; } public void Reset() { _armed = false; _nextIdx = 0; _lastRemainingMs = 0L; } private static int[] BuildMarks() { List list = new List(); for (int num = 300; num >= 1; num--) { int num2 = IntervalSecFor(num); if (num2 > 0 && num % num2 == 0) { list.Add(num); } } return list.ToArray(); } } public sealed class BombClientState { public int TargetActor { get; private set; } = -1; public byte Ammo { get; private set; } public long PlantReadyUnixMs { get; private set; } public long DetonateReadyUnixMs { get; private set; } public BombDenyReason LastDeny { get; private set; } public bool HasPendingDetonation { get; private set; } public int PendingTargetActor { get; private set; } public long PendingDetonateAtUnixMs { get; private set; } public bool HasBomb => TargetActor != -1; public void ApplyState(BomberStateSnapshot snapshot) { TargetActor = snapshot.TargetActor; Ammo = snapshot.Ammo; PlantReadyUnixMs = snapshot.PlantReadyUnixMs; DetonateReadyUnixMs = snapshot.DetonateReadyUnixMs; LastDeny = snapshot.LastDeny; } public void ApplyPendingDetonation(int targetActor, long detonateAtUnixMs) { HasPendingDetonation = true; PendingTargetActor = targetActor; PendingDetonateAtUnixMs = detonateAtUnixMs; } public void ClearPendingDetonation() { HasPendingDetonation = false; PendingTargetActor = -1; PendingDetonateAtUnixMs = 0L; } public void ConsumeLastDeny() { LastDeny = BombDenyReason.None; } public void Reset() { TargetActor = -1; Ammo = 0; PlantReadyUnixMs = 0L; DetonateReadyUnixMs = 0L; LastDeny = BombDenyReason.None; ClearPendingDetonation(); } } public static class BombDamageRules { public static int TargetDamage(int playerDamage, int currentHealth) { if (playerDamage <= 0 || currentHealth <= 1) { return 0; } return Math.Min(playerDamage / 2 + playerDamage % 2, currentHealth - 1); } } public static class BomberHudModel { public static float PlantFraction(bool phaseAllows, int cooldownSec, bool hasPlantResource, IReadOnlyDictionary ratios, int excludedTarget) { if (!phaseAllows || cooldownSec > 0 || !hasPlantResource || ratios == null) { return 0f; } float num = 0f; foreach (KeyValuePair ratio in ratios) { if (ratio.Key != excludedTarget) { float num2 = ratio.Value; if (num2 < 0f) { num2 = 0f; } if (num2 > 1f) { num2 = 1f; } if (num2 > num) { num = num2; } } } return num; } } public sealed class BombSession { private readonly GameConfig _config; private readonly IReadOnlyList _players; private int _bomberActor; private int _targetActor; private byte _ammo; private long _plantReadyUnixMs; private long _detonateReadyUnixMs; private BombDenyReason _lastDeny; private float _lastGaugePct; public int BomberActor => _bomberActor; public bool HasBomb => _targetActor != -1; public int TargetActor => _targetActor; public byte Ammo => _ammo; public BombDenyReason LastDeny => _lastDeny; public bool Dirty { get; private set; } public BombSession(GameConfig config, GameSession session, long nowUnixMs) : this(config, session?.Players, nowUnixMs) { } public BombSession(GameConfig config, IReadOnlyList players, long nowUnixMs) { _config = config ?? throw new ArgumentNullException("config"); _players = players ?? throw new ArgumentNullException("players"); _bomberActor = ResolveBomberActor(_players); _targetActor = -1; long num = (long)_config.BomberInitialCooldownSec * 1000L; _plantReadyUnixMs = ((_bomberActor >= 0) ? (nowUnixMs + num) : 0); _detonateReadyUnixMs = 0L; _ammo = ((_bomberActor >= 0) ? ((byte)1) : ((byte)0)); _lastDeny = BombDenyReason.None; Dirty = _bomberActor >= 0; } public BombDenyReason TryPlant(int senderActor, int targetActor, long nowUnixMs) { if (_bomberActor < 0) { return Deny(BombDenyReason.NotBomber); } if (senderActor != _bomberActor) { return Deny(BombDenyReason.NotBomber); } if (nowUnixMs < _plantReadyUnixMs) { return Deny(BombDenyReason.PlantCooldown); } if (targetActor == _bomberActor) { return Deny(BombDenyReason.TargetInvalid); } WPlayer wPlayer = FindPlayer(targetActor); if (wPlayer == null || !wPlayer.Alive) { return Deny(BombDenyReason.TargetInvalid); } if (_targetActor == targetActor) { return BombDenyReason.None; } bool flag = _targetActor != -1; if (!flag && _ammo == 0) { return Deny(BombDenyReason.NoAmmo); } if (!flag) { _ammo--; } _targetActor = targetActor; long num = (long)_config.BomberCooldownSec * 1000L; _plantReadyUnixMs = nowUnixMs + num; _detonateReadyUnixMs = nowUnixMs + num; _lastDeny = BombDenyReason.None; Dirty = true; return BombDenyReason.None; } public BombDenyReason TryDetonate(int senderActor, long nowUnixMs, bool meetingLocked, bool targetNearTruck, out int detonatedTargetActor) { detonatedTargetActor = -1; if (_bomberActor < 0 || senderActor != _bomberActor) { return Deny(BombDenyReason.NotBomber); } if (_targetActor == -1) { return Deny(BombDenyReason.NoBomb); } if (nowUnixMs < _detonateReadyUnixMs) { return Deny(BombDenyReason.DetonateCooldown); } if (meetingLocked) { return Deny(BombDenyReason.MeetingLocked); } if (targetNearTruck) { return Deny(BombDenyReason.TruckZone); } WPlayer wPlayer = FindPlayer(_targetActor); bool num = wPlayer == null || !wPlayer.Alive; long num2 = (long)_config.BomberCooldownSec * 1000L; _plantReadyUnixMs = nowUnixMs + num2; _detonateReadyUnixMs = 0L; if (num) { _targetActor = -1; _lastDeny = BombDenyReason.TargetDead; Dirty = true; return BombDenyReason.TargetDead; } detonatedTargetActor = _targetActor; _targetActor = -1; _lastDeny = BombDenyReason.None; Dirty = true; return BombDenyReason.None; } public void OnGaugeChanged(float cumulativeGaugePct) { if (_bomberActor < 0) { return; } int bomberAmmoRefillPct = _config.BomberAmmoRefillPct; if (bomberAmmoRefillPct <= 0) { return; } int num = (int)(_lastGaugePct / (float)bomberAmmoRefillPct); int num2 = (int)(cumulativeGaugePct / (float)bomberAmmoRefillPct); _lastGaugePct = cumulativeGaugePct; int num3 = num2 - num; if (num3 > 0) { int num4 = _ammo + num3; if (num4 > 255) { num4 = 255; } if (num4 != _ammo) { _ammo = (byte)num4; Dirty = true; } } } public void OnMeetingEnded(long nowUnixMs) { if (_bomberActor >= 0) { long num = (_plantReadyUnixMs = nowUnixMs + (long)_config.BomberCooldownSec * 1000L); _detonateReadyUnixMs = ((_targetActor != -1) ? num : 0); _lastDeny = BombDenyReason.None; Dirty = true; } } public void OnPlayerDied(int actor) { if (_bomberActor >= 0 && actor == _bomberActor) { _bomberActor = -1; _targetActor = -1; _plantReadyUnixMs = 0L; _detonateReadyUnixMs = 0L; Dirty = true; } } public void OnPlayerDisconnected(int actor) { if (_bomberActor >= 0) { if (actor == _bomberActor) { _bomberActor = -1; _targetActor = -1; _plantReadyUnixMs = 0L; _detonateReadyUnixMs = 0L; Dirty = true; } else if (actor == _targetActor) { _targetActor = -1; _detonateReadyUnixMs = 0L; Dirty = true; } } } public void DebugGrantAmmo(int n) { if (_bomberActor >= 0 && n > 0) { int num = _ammo + n; if (num > 255) { num = 255; } if (num != _ammo) { _ammo = (byte)num; Dirty = true; } } } public BomberStateSnapshot BuildSnapshot() { Dirty = false; return new BomberStateSnapshot(_targetActor, _ammo, _plantReadyUnixMs, _detonateReadyUnixMs, _lastDeny); } private BombDenyReason Deny(BombDenyReason reason) { _lastDeny = reason; Dirty = true; return reason; } private WPlayer FindPlayer(int actor) { foreach (WPlayer player in _players) { if (player.ActorNumber == actor) { return player; } } return null; } private static int ResolveBomberActor(IReadOnlyList players) { foreach (WPlayer player in players) { if (player.Role == Role.Bomber && player.Alive) { return player.ActorNumber; } } return -1; } } public enum BombDenyReason : byte { None, NotBomber, NoAmmo, NoFullTarget, PlantCooldown, DetonateCooldown, NoBomb, MeetingLocked, TruckZone, TargetDead, TargetInvalid } public readonly struct BomberStateSnapshot { public int TargetActor { get; } public byte Ammo { get; } public long PlantReadyUnixMs { get; } public long DetonateReadyUnixMs { get; } public BombDenyReason LastDeny { get; } public BomberStateSnapshot(int targetActor, byte ammo, long plantReadyUnixMs, long detonateReadyUnixMs, BombDenyReason lastDeny) { TargetActor = targetActor; Ammo = ammo; PlantReadyUnixMs = plantReadyUnixMs; DetonateReadyUnixMs = detonateReadyUnixMs; LastDeny = lastDeny; } } public static class BombRoleActionSubtype { public const byte Plant = 3; public const byte Detonate = 4; } public sealed class CatAwakenToastGate { private bool _shown; public void Reset() { _shown = false; } public bool ShouldFire(GamePhase phase, bool catPossible, long gameStartUnixMs, int revealDelaySec, long nowUnixMs) { if (_shown) { return false; } if (!catPossible) { return false; } if (gameStartUnixMs == 0L) { return false; } if (phase != GamePhase.Play) { return false; } if (nowUnixMs < gameStartUnixMs + (long)revealDelaySec * 1000L) { return false; } _shown = true; return true; } } public enum ChatBlockKind : byte { Speaker, Bubble, Vote } public readonly struct ChatBlockSize { public float Width { get; } public float Height { get; } public ChatBlockSize(float width, float height) { Width = width; Height = height; } } public readonly struct ChatLayoutMetrics { public float SpeakerWidth { get; } public float SpeakerHeight { get; } public float VoteWidth { get; } public float VoteHeight { get; } public float BlockGap { get; } public float GroupGap { get; } public ChatLayoutMetrics(float speakerWidth, float speakerHeight, float voteWidth, float voteHeight, float blockGap, float groupGap) { SpeakerWidth = speakerWidth; SpeakerHeight = speakerHeight; VoteWidth = voteWidth; VoteHeight = voteHeight; BlockGap = blockGap; GroupGap = groupGap; } } public readonly struct ChatLayoutBlock { public long EntrySeq { get; } public ChatBlockKind Kind { get; } public float Top { get; } public float Width { get; } public float Height { get; } public float Bottom => Top + Height; public ChatLayoutBlock(long entrySeq, ChatBlockKind kind, float top, float width, float height) { EntrySeq = entrySeq; Kind = kind; Top = top; Width = width; Height = height; } } public sealed class ChatLayout { private ChatLayoutBlock[] _blocks = new ChatLayoutBlock[16]; private int _blockHead; private int _blockCount; private readonly ChatLayoutMetrics _metrics; private long _syncedAppended; private long _syncedDropped; private int _lastActor = int.MinValue; private float _cursor; private float _origin; public int Count => _blockCount; public ChatLayoutBlock this[int index] { get { if ((uint)index >= (uint)_blockCount) { throw new ArgumentOutOfRangeException("index"); } return _blocks[(_blockHead + index) % _blocks.Length]; } } public float TotalHeight => _cursor - _origin; public float Origin => _origin; public int Epoch { get; private set; } public int Version { get; private set; } public ChatLayout(ChatLayoutMetrics metrics) { _metrics = metrics; } public void Sync(MeetingChatLog log, Func measureBubble) { if (log != null && measureBubble != null) { if (log.DroppedTotal != _syncedDropped) { DropLeading(log.DroppedTotal); } if (log.AppendedTotal != _syncedAppended) { AppendTail(log, measureBubble); } } } public void Reset() { _blockHead = 0; _blockCount = 0; _syncedAppended = 0L; _syncedDropped = 0L; _lastActor = int.MinValue; _cursor = 0f; _origin = 0f; Epoch++; Version++; } public float ContentTop(int index) { return this[index].Top - _origin; } public void GetVisibleRange(float from, float to, out int first, out int end) { first = 0; end = 0; if (_blockCount == 0 || to <= from) { return; } float num = from + _origin; float num2 = to + _origin; int num3 = 0; int num4 = _blockCount; while (num3 < num4) { int num5 = num3 + num4 >> 1; if (this[num5].Bottom <= num) { num3 = num5 + 1; } else { num4 = num5; } } first = num3; num4 = _blockCount; while (num3 < num4) { int num6 = num3 + num4 >> 1; if (this[num6].Top < num2) { num3 = num6 + 1; } else { num4 = num6; } } end = num3; } private void DropLeading(long dropped) { _syncedDropped = dropped; int i; for (i = 0; i < _blockCount && this[i].EntrySeq < dropped; i++) { } if (i == 0) { return; } RemoveFirst(i); if (_blockCount == 0) { _cursor = 0f; _origin = 0f; _lastActor = int.MinValue; } else { ChatLayoutBlock chatLayoutBlock = this[0]; if (chatLayoutBlock.Kind == ChatBlockKind.Bubble) { float top = chatLayoutBlock.Top - (_metrics.SpeakerHeight + _metrics.BlockGap); AddFirst(new ChatLayoutBlock(chatLayoutBlock.EntrySeq, ChatBlockKind.Speaker, top, _metrics.SpeakerWidth, _metrics.SpeakerHeight)); } _origin = this[0].Top; } Epoch++; Version++; } private void AppendTail(MeetingChatLog log, Func measureBubble) { for (int i = (int)(((_syncedAppended > log.DroppedTotal) ? _syncedAppended : log.DroppedTotal) - log.DroppedTotal); i < log.Count; i++) { ChatLogEntry arg = log.Entries[i]; long seq = log.DroppedTotal + i; if (arg.Kind == ChatEntryKind.Vote) { Add(seq, ChatBlockKind.Vote, _metrics.VoteWidth, _metrics.VoteHeight, group: true); _lastActor = int.MinValue; continue; } if (arg.Actor != _lastActor) { Add(seq, ChatBlockKind.Speaker, _metrics.SpeakerWidth, _metrics.SpeakerHeight, group: true); } ChatBlockSize chatBlockSize = measureBubble(arg); Add(seq, ChatBlockKind.Bubble, chatBlockSize.Width, chatBlockSize.Height, group: false); _lastActor = arg.Actor; } _syncedAppended = log.AppendedTotal; Version++; } private void Add(long seq, ChatBlockKind kind, float width, float height, bool group) { if (group && _blockCount > 0) { _cursor += _metrics.GroupGap; } AddLast(new ChatLayoutBlock(seq, kind, _cursor, width, height)); _cursor += height + _metrics.BlockGap; } private void AddFirst(ChatLayoutBlock block) { EnsureCapacity(); _blockHead = (_blockHead - 1 + _blocks.Length) % _blocks.Length; _blocks[_blockHead] = block; _blockCount++; } private void AddLast(ChatLayoutBlock block) { EnsureCapacity(); int num = (_blockHead + _blockCount) % _blocks.Length; _blocks[num] = block; _blockCount++; } private void RemoveFirst(int count) { if (count > 0) { if (count > _blockCount) { throw new ArgumentOutOfRangeException("count"); } for (int i = 0; i < count; i++) { _blocks[(_blockHead + i) % _blocks.Length] = default(ChatLayoutBlock); } _blockHead = (_blockHead + count) % _blocks.Length; _blockCount -= count; if (_blockCount == 0) { _blockHead = 0; } } } private void EnsureCapacity() { if (_blockCount >= _blocks.Length) { ChatLayoutBlock[] array = new ChatLayoutBlock[_blocks.Length * 2]; for (int i = 0; i < _blockCount; i++) { array[i] = this[i]; } _blocks = array; _blockHead = 0; } } } public static class WWCheckmateCodes { public const byte CheckmateReveal = 187; } public static class CheckmateJudge { public static int RemainingQuotaDollars(int haulGoalDollars, int extractionPoints, int pointsCompleted) { if (haulGoalDollars <= 0 || extractionPoints <= 0) { return -1; } if (pointsCompleted < 0) { pointsCompleted = 0; } if (pointsCompleted >= extractionPoints) { return 0; } return haulGoalDollars / extractionPoints * (extractionPoints - pointsCompleted); } public static bool IsCheckmate(float obtainableDollars, int remainingQuotaDollars) { if (remainingQuotaDollars > 0) { return obtainableDollars < (float)remainingQuotaDollars; } return false; } } public sealed class HaulFreeze { public const long TimeoutMs = 60000L; private bool _open; private long _lastSuckUnixMs; public void NoteSuck(long nowUnixMs) { _open = true; _lastSuckUnixMs = nowUnixMs; } public void Close() { _open = false; } public bool IsHolding(long nowUnixMs) { if (_open) { return nowUnixMs - _lastSuckUnixMs < 60000; } return false; } } public enum CheckmateAction : byte { None, StartCeremony, ConfirmWin } public sealed class CheckmateSequence { public const int CeremonyMs = 7000; private long _ceremonyStartUnixMs; public bool Detected { get; private set; } public bool CeremonyStarted { get; private set; } public bool Confirmed { get; private set; } public void NotifyDetected() { if (!Confirmed) { Detected = true; } } public CheckmateAction Tick(long nowUnixMs, GamePhase phase, bool curseActive) { if (Confirmed) { return CheckmateAction.None; } if (!CeremonyStarted) { if (!Detected) { return CheckmateAction.None; } if (phase != GamePhase.Play || curseActive) { return CheckmateAction.None; } CeremonyStarted = true; _ceremonyStartUnixMs = nowUnixMs; return CheckmateAction.StartCeremony; } if (nowUnixMs - _ceremonyStartUnixMs >= 7000) { Confirmed = true; return CheckmateAction.ConfirmWin; } return CheckmateAction.None; } } public static class CheckmateCeremony { public const float BackdropFadeSec = 0.3f; public const float PostRevealPauseSec = 0.4f; public const float StampEntranceSec = 0.45f; public const float StampHoldSec = 3f; } public static class ClientResetPolicy { public static void ApplyRoomLeft(MeetingClientState meetingClient) { meetingClient?.Reset(); } } public static class CombatRules { public static bool IsMatchLive(GamePhase phase) { if (phase != GamePhase.Play) { return phase == GamePhase.Meeting; } return true; } public static bool OverrideSavingGrace(GamePhase phase, bool savingGrace) { if (!IsMatchLive(phase)) { return savingGrace; } return false; } public static int OverrideMeleePlayerDamage(GamePhase phase, int playerDamage, int enemyDamage) { if (!IsMatchLive(phase)) { return playerDamage; } return enemyDamage; } public static bool OverrideMeleePvpDurabilityHit(GamePhase phase, bool playerHit, bool arenaOrShop) { if (!IsMatchLive(phase)) { return playerHit; } if (!playerHit || arenaOrShop) { return playerHit; } return false; } public static bool ShouldDisarmGrabbedOnMeleeHit(GamePhase phase, bool grabbing, bool grabbedIsMelee) { if (IsMatchLive(phase) && grabbing) { return !grabbedIsMelee; } return false; } public static bool ShouldSpillInventoryOnMeleeHit(GamePhase phase) { return IsMatchLive(phase); } public static bool ShouldBlockEquipContestedItem(GamePhase phase, bool grabbedByOtherPlayer) { return IsMatchLive(phase) && grabbedByOtherPlayer; } public static int PickSpillSlotIndex(int occupiedCount, float roll) { if (occupiedCount <= 0) { return -1; } if (roll < 0f) { roll = 0f; } int num = (int)(roll * (float)occupiedCount); if (num < occupiedCount) { return num; } return occupiedCount - 1; } } public enum ConfigIssue : byte { WerewolfCountExceedsPlayers = 1, RoundSecondsNotPositive } public static class ConfigValidator { public static bool WerewolfCountExceedsPlayers(int werewolfCount, int playerCount) { return werewolfCount >= playerCount; } public static IReadOnlyList Validate(GameConfig config, int playerCount) { List list = new List(); if (config == null) { return list; } if (WerewolfCountExceedsPlayers(config.WerewolfCount, playerCount)) { list.Add(ConfigIssue.WerewolfCountExceedsPlayers); } if (config.RoundSeconds <= 0) { list.Add(ConfigIssue.RoundSecondsNotPositive); } return list; } } public static class ConveneDeniedWire { public static byte ToWire(ConveneRejectReason reason) { return reason switch { ConveneRejectReason.NoRight => 1, ConveneRejectReason.Suppressed => 2, ConveneRejectReason.WrongPhase => 3, ConveneRejectReason.CorpseReportLastRun => 4, ConveneRejectReason.NoCorpse => 5, _ => 0, }; } public static ConveneRejectReason FromWire(byte wire) { return wire switch { 1 => ConveneRejectReason.NoRight, 2 => ConveneRejectReason.Suppressed, 3 => ConveneRejectReason.WrongPhase, 4 => ConveneRejectReason.CorpseReportLastRun, 5 => ConveneRejectReason.NoCorpse, _ => ConveneRejectReason.CallerDead, }; } } public sealed class ConveneHold { public const float HoldSeconds = 3f; private float _held; private bool _fired; public float Ratio { get { if (_fired) { return 1f; } float num = _held / 3f; if (num < 0f) { return 0f; } if (!(num > 1f)) { return num; } return 1f; } } public bool IsCharging { get { if (!_fired) { return _held > 0f; } return false; } } public bool Tick(bool engaged, float deltaSeconds) { if (!engaged) { _held = 0f; _fired = false; return false; } if (_fired) { return false; } if (deltaSeconds > 0f) { _held += deltaSeconds; } if (_held >= 3f) { _held = 3f; _fired = true; return true; } return false; } public void Reset() { _held = 0f; _fired = false; } } public static class CosmeticGrantWire { public static object[] ToWire(CosmeticGrant grant) { if (grant == null) { throw new ArgumentNullException("grant"); } return new object[2] { grant.Actors, grant.Rarities }; } public static bool TryFromWire(object[] payload, out CosmeticGrant grant) { grant = null; if (payload == null || payload.Length != 2) { return false; } int[] array = payload[0] as int[]; byte[] array2 = payload[1] as byte[]; if (array == null || array2 == null) { return false; } if (array2.Length != array.Length) { return false; } for (int i = 0; i < array2.Length; i++) { if (array2[i] >= 4) { return false; } } grant = new CosmeticGrant(array, array2); return true; } } public static class CosmeticHandoff { public enum Route : byte { Inject, DirectAdd } public const long MinMatchDurationMs = 180000L; public static bool ShouldGrant(long startUnixMs, long nowUnixMs) { if (startUnixMs <= 0) { return true; } return nowUnixMs - startUnixMs >= 180000; } public static Route Decide(bool departedToLobbyMenu, bool roundDirectorAlive, bool cooldownKnown, float cooldownSeconds, out string fallbackReason) { if (!departedToLobbyMenu) { fallbackReason = "not_lobby_menu_departure"; return Route.DirectAdd; } if (!roundDirectorAlive) { fallbackReason = "no_round_director"; return Route.DirectAdd; } if (!cooldownKnown) { fallbackReason = "cooldown_unknown"; return Route.DirectAdd; } if (cooldownSeconds > 0f) { fallbackReason = "cooldown_active"; return Route.DirectAdd; } fallbackReason = null; return Route.Inject; } public static int[] SubtractLeading(int[] countsByRarity, int injectedCount) { if (countsByRarity == null) { throw new ArgumentNullException("countsByRarity"); } int[] array = (int[])countsByRarity.Clone(); for (int i = 0; i < array.Length; i++) { if (injectedCount <= 0) { break; } int num = Math.Min(array[i], injectedCount); array[i] -= num; injectedCount -= num; } return array; } } public static class CoinRarity { public const byte Common = 0; public const byte Uncommon = 1; public const byte Rare = 2; public const byte UltraRare = 3; public const int Count = 4; } public static class CosmeticLottery { public const int CoinsPerPlayer = 1; public const int WeightCommon = 280; public const int WeightUncommon = 131; public const int WeightRare = 105; public const int WeightUltraRare = 31; public const int TotalWeight = 547; public static bool ValidateWeights() { return true; } public static byte DrawRarity(int roll) { if (roll < 0 || roll >= 547) { throw new ArgumentOutOfRangeException("roll", roll, "roll は 0〜" + 546 + " の範囲でなければなりません。"); } if (roll < 280) { return 0; } int num = 411; if (roll < num) { return 1; } int num2 = num + 105; if (roll < num2) { return 2; } return 3; } public static byte Draw(Random rng) { if (rng == null) { throw new ArgumentNullException("rng"); } return DrawRarity(rng.Next(547)); } public static CosmeticGrant BuildGrant(IReadOnlyList rosterActors, Random rng) { if (rosterActors == null) { throw new ArgumentNullException("rosterActors"); } if (rng == null) { throw new ArgumentNullException("rng"); } List list = new List(rosterActors.Count); for (int i = 0; i < rosterActors.Count; i++) { int num = rosterActors[i]; if (num > 0) { list.Add(num); } } byte[] array = new byte[list.Count]; for (int j = 0; j < list.Count; j++) { for (int k = 0; k < 1; k++) { array[j + k] = Draw(rng); } } return new CosmeticGrant(list.ToArray(), array); } } public sealed class CosmeticGrant { public int[] Actors { get; } public byte[] Rarities { get; } public CosmeticGrant(int[] actors, byte[] rarities) { if (actors == null) { throw new ArgumentNullException("actors"); } if (rarities == null) { throw new ArgumentNullException("rarities"); } if (rarities.Length != actors.Length) { throw new ArgumentException("rarities の長さは actors.Length * CoinsPerPlayer と一致していなければなりません。", "rarities"); } Actors = actors; Rarities = rarities; } public bool TryGetCounts(int actor, out int[] countsByRarity) { for (int i = 0; i < Actors.Length; i++) { if (Actors[i] == actor) { int[] array = new int[4]; int num = i; for (int j = 0; j < 1; j++) { byte b = Rarities[num + j]; array[b]++; } countsByRarity = array; return true; } } countsByRarity = null; return false; } } public static class CrownRoster { private static readonly HashSet _winners = new HashSet(); public static int Version { get; private set; } public static bool HasWinners => _winners.Count > 0; public static int Count => _winners.Count; public static void SetWinners(IEnumerable actors) { _winners.Clear(); Version++; if (actors == null) { return; } foreach (int actor in actors) { _winners.Add(actor); } } public static void Clear() { _winners.Clear(); Version++; } public static bool IsWinner(int actorNumber) { return _winners.Contains(actorNumber); } } public sealed class CurseResolution { public const int NoVictim = int.MinValue; public int VictimActor { get; } public bool HasVictim => VictimActor != int.MinValue; public bool WasDesignated { get; } public CurseResolution(int victimActor, bool wasDesignated) { VictimActor = victimActor; WasDesignated = wasDesignated; } } public sealed class CurseSession { private int _designatedActor = int.MinValue; private bool _resolved; private readonly HashSet _voters; public int CatActor { get; } public long ResolveAtUnixMs { get; } public bool Resolved => _resolved; public CurseSession(int catActor, long resolveAtUnixMs, IEnumerable voterActors = null) { CatActor = catActor; ResolveAtUnixMs = resolveAtUnixMs; _voters = ((voterActors != null) ? new HashSet(voterActors) : null); } public bool Designate(int senderActor, int targetActor, long nowUnixMs) { if (_resolved || nowUnixMs >= ResolveAtUnixMs) { WLog.Line("curse_designate_rejected", true, ("sender", senderActor), ("reason", "expired")); return false; } if (senderActor != CatActor) { WLog.Line("curse_designate_rejected", true, ("sender", senderActor), ("reason", "not_cat")); return false; } if (_voters != null && !_voters.Contains(targetActor)) { WLog.Line("curse_designate_rejected", true, ("sender", senderActor), ("reason", "not_voter")); return false; } _designatedActor = targetActor; WLog.Line("curse_designate", true, ("target", targetActor), ("deadline", ResolveAtUnixMs)); return true; } public CurseResolution TryResolve(long nowUnixMs, IReadOnlyList players, bool informantEstablished, Random rng) { if (_resolved || nowUnixMs < ResolveAtUnixMs) { return null; } if (players == null) { throw new ArgumentNullException("players"); } if (rng == null) { throw new ArgumentNullException("rng"); } _resolved = true; if (_designatedActor != int.MinValue) { foreach (WPlayer player in players) { if (player.ActorNumber == _designatedActor && player.Alive) { return new CurseResolution(_designatedActor, wasDesignated: true); } } } List list = new List(); List list2 = ((_voters != null) ? new List() : null); foreach (WPlayer player2 in players) { if (!player2.Alive || player2.ActorNumber == CatActor) { continue; } if (_voters != null) { if (_voters.Contains(player2.ActorNumber)) { list.Add(player2.ActorNumber); if (RoleDistribution.TeamOf(player2.Role) == Team.Villagers) { list2.Add(player2.ActorNumber); } } } else if (informantEstablished) { if (RoleDistribution.TeamOf(player2.Role) == Team.Villagers) { list.Add(player2.ActorNumber); } } else { list.Add(player2.ActorNumber); } } if (_voters != null && informantEstablished && list2.Count > 0) { list = list2; } if (list.Count == 0) { WLog.Line("curse_resolve_no_candidate", true, ("informant", informantEstablished), ("restricted", _voters != null)); return new CurseResolution(int.MinValue, wasDesignated: false); } return new CurseResolution(list[rng.Next(list.Count)], wasDesignated: false); } } public static class DeathReveal { public const float EntranceSec = 0.2f; public const float HoldSec = 4f; public const float ExitSec = 0.8f; public const long TotalDurationMs = 5000L; } public enum DisclosureKind { BlackCatSelfAwareness, BlackCatSeesWerewolves } public enum DisclosureType : byte { RoleNotice, SelfRoleReveal, TeammatesReveal } public sealed class Disclosure { public DisclosureType Type { get; } public int[] TargetActors { get; } public Role ShownRole { get; } public int[] WerewolfActors { get; } public byte[] WerewolfActorRoles { get; } public Disclosure(DisclosureType type, int[] targetActors, Role shownRole, int[] werewolfActors, byte[] werewolfActorRoles = null) { Type = type; TargetActors = targetActors; ShownRole = shownRole; WerewolfActors = werewolfActors; WerewolfActorRoles = werewolfActorRoles; } } public sealed class DisclosureManager { private static readonly IReadOnlyList Empty = Array.Empty(); private readonly IReadOnlyList _players; private readonly long _selfAwarenessDueUnixMs; private readonly bool _selfAwarenessImmediate; private bool _initialIssued; private bool _selfAwarenessIssued; private bool _werewolvesShownToBlackCat; public bool SelfAwarenessIssued => _selfAwarenessIssued; public DisclosureManager(IReadOnlyList players, long gameStartUnixMs, int blackCatRevealDelaySec) { _players = players ?? throw new ArgumentNullException("players"); _selfAwarenessDueUnixMs = gameStartUnixMs + (long)blackCatRevealDelaySec * 1000L; _selfAwarenessImmediate = blackCatRevealDelaySec <= 0; } public IReadOnlyList IssueInitialDisclosures() { if (_initialIssued) { return Empty; } _initialIssued = true; List list = new List(_players.Count + 2); foreach (WPlayer player in _players) { bool flag = player.Role == Role.BlackCat && _selfAwarenessImmediate; Role shownRole = ((player.Role != Role.BlackCat || flag) ? player.Role : Role.Villager); list.Add(new Disclosure(DisclosureType.RoleNotice, new int[1] { player.ActorNumber }, shownRole, null)); if (flag) { _selfAwarenessIssued = true; } } CollectTeamRoster(out var actors, out var roles); if (actors.Length >= 2) { int[] array = actors; foreach (int num in array) { list.Add(new Disclosure(DisclosureType.TeammatesReveal, new int[1] { num }, Role.Werewolf, actors, roles)); } } return list; } public IReadOnlyList Tick(long nowUnixMs) { if (nowUnixMs < _selfAwarenessDueUnixMs) { return Empty; } return IssueSelfAwareness(); } public IReadOnlyList NotifyCondition(DisclosureKind kind) { switch (kind) { case DisclosureKind.BlackCatSelfAwareness: return IssueSelfAwareness(); case DisclosureKind.BlackCatSeesWerewolves: return IssueWerewolvesToBlackCat(); default: WLog.Line("drop", false, ("reason", "unknown_disclosure_kind"), ("kind", (int)kind)); return Empty; } } private IReadOnlyList IssueSelfAwareness() { if (_selfAwarenessIssued) { return Empty; } List list = new List(1); foreach (WPlayer player in _players) { if (player.Role == Role.BlackCat) { list.Add(new Disclosure(DisclosureType.SelfRoleReveal, new int[1] { player.ActorNumber }, Role.BlackCat, null)); } } if (list.Count == 0) { return Empty; } _selfAwarenessIssued = true; return list; } private IReadOnlyList IssueWerewolvesToBlackCat() { if (_werewolvesShownToBlackCat) { return Empty; } CollectTeamRoster(out var actors, out var roles); List list = new List(1); foreach (WPlayer player in _players) { if (player.Role == Role.BlackCat) { list.Add(new Disclosure(DisclosureType.TeammatesReveal, new int[1] { player.ActorNumber }, Role.Werewolf, actors, roles)); } } if (list.Count == 0) { return Empty; } _werewolvesShownToBlackCat = true; return list; } private void CollectTeamRoster(out int[] actors, out byte[] roles) { List list = new List(); List list2 = new List(); foreach (WPlayer player in _players) { if (player.Role == Role.Werewolf || player.Role == Role.Bomber) { list.Add(player.ActorNumber); list2.Add((byte)player.Role); } } actors = list.ToArray(); roles = list2.ToArray(); } } public sealed class GameConfig { public int WerewolfCount = 1; public int BlackCatChancePercent; public int BomberChancePercent; public int ShamanChancePercent = 100; public int RoundSeconds = 1200; public int BlackCatRevealDelaySec = 60; public bool BlackCatCurseEnabled = true; public bool DebugMode; public int MeetingRightsPerPlayer = 1; public int ConveneSuppressStartSec = 15; public int ConveneSuppressAfterSec = 15; public int MeetingCountdownSec = 10; public int MeetingDurationSec = 180; public bool VoteTimeCutEnabled = true; public int ResultDisplaySec = 9; public float ButtonOffsetX = -6.2f; public float ButtonOffsetY; public float ButtonOffsetZ; public float ButtonYaw = -90f; public float ButtonPitch = -90f; public int StaminaUnlockPct = 10; public int JumpUnlockPct = 30; public int EnemyIgnoreUnlockPct = 40; public int HealUnlockPct = 60; public int HealIntervalSec = 3; public int BeaconChargePct = 20; public int InformantThresholdPct = 50; public int ExtraJumpCount = 10; public int BeaconCooldownSec = 60; public int BeaconSuppressStartSec = 60; public int BeaconSuppressAfterMeetingSec = 30; public int CurseWaitSec = 10; public int CatGaugeSyncIntervalSec = 120; public bool OrbGaugeEnabled = true; public bool WerewolfModeEnabled; public bool MinimapHideEnabled = true; public ValuableMapMode ValuableMapMode = ValuableMapMode.MeetingSync; public int GameOverAutoReturnSec = 60; public int ToastDurationSec = 9; public int StartLevelNumber = 11; public string StartMapName = ""; public string StartItemsSpec = ""; public int StartEnergyPct = 20; public string StartUpgradesSpec = ""; public int OrbDropMax = 6; public NecroVoiceMode NecroVoiceMode = NecroVoiceMode.NonWerewolfDead; public float NecroVoiceVolume = 0.1f; public float NecroVoiceLowPassCutoffHz = 2500f; public float NecroVoiceEchoDelayMs = 500f; public float NecroVoiceEchoDecay = 0.2f; public float NecroVoiceReverbRoom = -600f; public float NecroVoiceReverbRoomHF = -800f; public float NecroVoiceReverbDecayTime = 7f; public float NecroVoiceReverbDecayHFRatio = 0.5f; public float NecroVoiceReverbReflections = -400f; public float NecroVoiceReverbReflectionsDelay = 0.03f; public float NecroVoiceReverbLevel = 300f; public float NecroVoiceReverbDelay = 0.07f; public float NecroVoiceReverbDiffusion = 100f; public float NecroVoiceReverbDensity = 100f; public float NecroVoiceReverbHFReference = 4500f; public int BomberProximityMeters = 9; public int BomberGaugeFullSec = 20; public int BomberInitialCooldownSec = 60; public int BomberCooldownSec = 30; public float BomberWarningSec = 1f; public float BomberBlastRadiusMeters = 9f; public int BomberBlastPlayerDamage = 80; public int BomberBlastEnemyDamage = 60; public int BomberAmmoRefillPct = 20; public float BomberTruckSafeRadiusMeters = 20f; public int ShamanGazeFullSec = 3; public int ShamanGhostCooldownSec = 10; public int ShamanStormWeakMeters = 30; public int ShamanStormMediumMeters = 20; public int ShamanStormStrongMeters = 10; public bool BlackCatPossible(int playerCount) { if (BlackCatChancePercent > 0) { return playerCount - RoleAssigner.CorrectedWerewolfSlots(this, playerCount) >= 2; } return false; } public bool BomberPossible(int playerCount) { if (BomberChancePercent > 0) { return RoleAssigner.CorrectedWerewolfSlots(this, playerCount) >= 2; } return false; } } public enum GamePhase : byte { Lobby, Play, Meeting, GameOver } public enum StartRejectReason : byte { None, TooFewPlayers, NotInLobby, InvalidConfig } public sealed class StartResult { private static readonly IReadOnlyList NoIssues = new ConfigIssue[0]; public bool Success { get; } public StartRejectReason Reason { get; } public IReadOnlyList Issues { get; } private StartResult(bool success, StartRejectReason reason, IReadOnlyList issues) { Success = success; Reason = reason; Issues = issues ?? NoIssues; } public static StartResult Ok() { return new StartResult(success: true, StartRejectReason.None, NoIssues); } public static StartResult Rejected(StartRejectReason reason) { return new StartResult(success: false, reason, NoIssues); } public static StartResult Rejected(StartRejectReason reason, IReadOnlyList issues) { return new StartResult(success: false, reason, issues); } } public enum PhaseChangeRejectReason : byte { None, InvalidTransition } public sealed class PhaseChangeResult { public bool Success { get; } public PhaseChangeRejectReason Reason { get; } private PhaseChangeResult(bool success, PhaseChangeRejectReason reason) { Success = success; Reason = reason; } public static PhaseChangeResult Ok() { return new PhaseChangeResult(success: true, PhaseChangeRejectReason.None); } public static PhaseChangeResult Rejected(PhaseChangeRejectReason reason) { return new PhaseChangeResult(success: false, reason); } } public sealed class GameSession { private readonly Dictionary _forcedRoles = new Dictionary(); private readonly HashSet _voteMarked = new HashSet(); private List _players = new List(); private bool _checkmateLocked; private RoundTimer _timer = new RoundTimer(); private DisclosureManager _disclosures; private GameConfig _config; public GamePhase Phase { get; private set; } public IReadOnlyList Players => _players; public WinResult Winner { get; private set; } public long StartUnixMs { get; private set; } public bool BlackCatSelfAwarenessIssued { get { if (_disclosures != null) { return _disclosures.SelfAwarenessIssued; } return false; } } public event Action OnSend; public event Action OnSessionEvent; public void ReserveForcedRole(int actorNumber, Role role) { _forcedRoles[actorNumber] = role; } public StartResult Start(GameConfig config, IReadOnlyList players, long nowUnixMs, Random rng) { if (config == null) { throw new ArgumentNullException("config"); } if (players == null) { throw new ArgumentNullException("players"); } if (rng == null) { throw new ArgumentNullException("rng"); } if (Phase != GamePhase.Lobby) { WLog.Line("start_rejected", false, ("reason", "not_in_lobby"), ("phase", Phase)); return StartResult.Rejected(StartRejectReason.NotInLobby); } if (players.Count < 3) { WLog.Line("start_rejected", false, ("reason", "too_few_players"), ("players", players.Count)); return StartResult.Rejected(StartRejectReason.TooFewPlayers); } IReadOnlyList readOnlyList = ConfigValidator.Validate(config, players.Count); if (readOnlyList.Count > 0) { WLog.Line("start_rejected", false, ("reason", "invalid_config"), ("issues", string.Join(",", readOnlyList))); return StartResult.Rejected(StartRejectReason.InvalidConfig, readOnlyList); } _config = config; _players = new List(players); Winner = null; _checkmateLocked = false; _voteMarked.Clear(); RoleAssignResult roleAssignResult = RoleAssigner.Assign(_players, config, rng, _forcedRoles); _forcedRoles.Clear(); _timer = new RoundTimer(); _timer.Start(nowUnixMs, config.RoundSeconds); StartUnixMs = nowUnixMs; _disclosures = new DisclosureManager(_players, nowUnixMs, config.BlackCatRevealDelaySec); foreach (Disclosure item in _disclosures.IssueInitialDisclosures()) { Send(ToMessage(item)); } bool flag = config.BlackCatPossible(_players.Count); Send(new OutboundMessage(170, new object[6] { _timer.EndUnixMs, config.RoundSeconds, (byte)roleAssignResult.Werewolves, flag ? ((byte)1) : ((byte)0), config.BlackCatRevealDelaySec, config.DebugMode ? ((byte)1) : ((byte)0) }, MessageTarget.All, null)); SetPhase(GamePhase.Play, "start", nowUnixMs); return StartResult.Ok(); } public void Tick(long nowUnixMs) { if (Winner != null || (Phase != GamePhase.Play && Phase != GamePhase.Meeting)) { return; } if (Phase == GamePhase.Play) { foreach (Disclosure item in _disclosures.Tick(nowUnixMs)) { Send(ToMessage(item)); } } if (_timer.CheckExpiry(nowUnixMs)) { ConfirmWinner(WinJudge.Judge(_players, extractionCompleted: false, timerExpired: true), "timer_expired", nowUnixMs); } } public void RecordDeath(int actorNumber, long nowUnixMs) { if (Winner != null || (Phase != GamePhase.Play && Phase != GamePhase.Meeting)) { return; } WPlayer wPlayer = null; foreach (WPlayer player in _players) { if (player.ActorNumber == actorNumber) { wPlayer = player; break; } } if (wPlayer == null) { WLog.Line("drop", false, ("reason", "death_unknown_actor"), ("actor", actorNumber)); } else if (wPlayer.Alive) { DeathCause deathCause = ((!_voteMarked.Remove(actorNumber)) ? DeathCause.Other : DeathCause.Vote); wPlayer.Alive = false; wPlayer.DeathCause = deathCause; Send(new OutboundMessage(168, new object[2] { actorNumber, (byte)deathCause }, MessageTarget.All, null)); this.OnSessionEvent?.Invoke(SessionEvent.ForPlayerDied(actorNumber, deathCause)); WinResult winResult = WinJudge.Judge(_players); if (winResult != null) { ConfirmWinner(winResult, "death", nowUnixMs); } } } public void NotifyPlayerLeft(int actorNumber, long nowUnixMs) { if (Winner != null || (Phase != GamePhase.Play && Phase != GamePhase.Meeting)) { return; } WPlayer wPlayer = null; foreach (WPlayer player in _players) { if (player.ActorNumber == actorNumber) { wPlayer = player; break; } } if (wPlayer != null && wPlayer.Alive) { wPlayer.Alive = false; WLog.Line("player_left_roster", false, ("actor", actorNumber)); WinResult winResult = WinJudge.Judge(_players); if (winResult != null) { ConfirmWinner(winResult, "player_left", nowUnixMs); } } } public void MarkNextDeathAsVote(int actorNumber) { _voteMarked.Add(actorNumber); } public void ForceExpireTimer(long nowUnixMs) { if (Winner == null && (Phase == GamePhase.Play || Phase == GamePhase.Meeting)) { _timer.ForceExpire(nowUnixMs); WLog.Line("timer_force_expire", false, ("endUnixMs", _timer.EndUnixMs)); } } public long RemainingMs(long nowUnixMs) { return _timer.RemainingMs(nowUnixMs); } public void NotifyExtractionOutcome(bool completed, bool failed, long nowUnixMs) { if (Winner == null && (Phase == GamePhase.Play || Phase == GamePhase.Meeting)) { if (completed) { ConfirmWinner(WinJudge.Judge(_players, extractionCompleted: true), "extraction_completed", nowUnixMs); } else if (failed) { WinResult result = WinJudge.Judge(_players) ?? new WinResult(Team.Werewolves, WinReason.ExtractionFailed); ConfirmWinner(result, "extraction_failed", nowUnixMs); } } } public void NotifyMailDeparture(long nowUnixMs) { if (Winner == null && (Phase == GamePhase.Play || Phase == GamePhase.Meeting)) { WinResult result = WinJudge.Judge(_players, extractionCompleted: true); ConfirmWinner(result, "mail_departure", nowUnixMs); } } public void LockValueCheckmate() { if (Winner == null && (Phase == GamePhase.Play || Phase == GamePhase.Meeting) && !_checkmateLocked) { _checkmateLocked = true; WLog.Line("checkmate_locked", false); } } public void NotifyValueCheckmate(long nowUnixMs) { if (Winner == null && (Phase == GamePhase.Play || Phase == GamePhase.Meeting)) { ConfirmWinner(new WinResult(Team.Werewolves, WinReason.ValueCheckmate), "value_checkmate", nowUnixMs); } } public void NotifyDisclosureCondition(DisclosureKind kind) { if (Winner != null || (Phase != GamePhase.Play && Phase != GamePhase.Meeting)) { return; } foreach (Disclosure item in _disclosures.NotifyCondition(kind)) { Send(ToMessage(item)); } } public PhaseChangeResult RequestPhaseChange(GamePhase target, long nowUnixMs) { if (Winner != null || ((Phase != GamePhase.Play || target != GamePhase.Meeting) && (Phase != GamePhase.Meeting || target != GamePhase.Play) && (Phase != GamePhase.Play || target != GamePhase.GameOver) && (Phase != GamePhase.Meeting || target != GamePhase.GameOver))) { WLog.Line("phase_rejected", false, ("from", Phase), ("to", target)); return PhaseChangeResult.Rejected(PhaseChangeRejectReason.InvalidTransition); } switch (target) { case GamePhase.Meeting: _timer.PauseForMeeting(nowUnixMs); break; case GamePhase.Play: _timer.ResumeFromMeeting(nowUnixMs); break; } SetPhase(target, "request", nowUnixMs); return PhaseChangeResult.Ok(); } private void ConfirmWinner(WinResult result, string reason, long nowUnixMs) { if (_checkmateLocked && result.Reason != WinReason.ValueCheckmate) { WLog.Line("win_suppressed_by_checkmate", false, ("team", result.WinningTeam), ("reason", result.Reason), ("trigger", reason)); return; } Winner = result; WLog.Line("win", false, ("team", result.WinningTeam), ("reason", result.Reason), ("trigger", reason)); int[] array = new int[_players.Count]; byte[] array2 = new byte[_players.Count]; for (int i = 0; i < _players.Count; i++) { array[i] = _players[i].ActorNumber; array2[i] = (byte)_players[i].Role; } Send(new OutboundMessage(169, new object[3] { (byte)result.WinningTeam, array, array2 }, MessageTarget.All, null)); this.OnSessionEvent?.Invoke(SessionEvent.ForWinnerConfirmed(result)); SetPhase(GamePhase.GameOver, "win_" + reason, nowUnixMs); } private void SetPhase(GamePhase to, string reason, long nowUnixMs) { GamePhase phase = Phase; Phase = to; WLog.Phase(phase, to, reason); Send(new OutboundMessage(172, new object[3] { (byte)to, nowUnixMs, _timer.EndUnixMs }, MessageTarget.All, null)); this.OnSessionEvent?.Invoke(SessionEvent.ForPhaseChanged(to, _timer.EndUnixMs)); } private static OutboundMessage ToMessage(Disclosure disclosure) { switch (disclosure.Type) { case DisclosureType.RoleNotice: return new OutboundMessage(160, new object[1] { (byte)disclosure.ShownRole }, MessageTarget.Actors, disclosure.TargetActors); case DisclosureType.SelfRoleReveal: return new OutboundMessage(161, new object[1] { (byte)disclosure.ShownRole }, MessageTarget.Actors, disclosure.TargetActors); case DisclosureType.TeammatesReveal: { int[] array = disclosure.WerewolfActors ?? Array.Empty(); byte[] array2 = disclosure.WerewolfActorRoles; if (array2 == null || array2.Length != array.Length) { array2 = new byte[array.Length]; for (int i = 0; i < array2.Length; i++) { array2[i] = 1; } } return new OutboundMessage(162, new object[2] { array, array2 }, MessageTarget.Actors, disclosure.TargetActors); } default: throw new ArgumentOutOfRangeException("disclosure", disclosure.Type, "未知の開示種別です。"); } } private void Send(OutboundMessage message) { this.OnSend?.Invoke(message); } } public enum GaugeMarkerKind : byte { Perk, Scale } public readonly struct GaugeMarker { public int Pct { get; } public GaugeMarkerKind Kind { get; } public string IconKey { get; } public string Label { get; } public int Tier { get; } public bool Unlocked { get; } public GaugeMarker(int pct, GaugeMarkerKind kind, string iconKey, string label, int tier, bool unlocked) { Pct = pct; Kind = kind; IconKey = iconKey; Label = label; Tier = tier; Unlocked = unlocked; } } public static class GaugeMarkerLayout { public static List Build(MeetingGaugeSnapshot s, int permille, int minGapPct) { List list = new List(); if (s == null) { return list; } List> list2 = new List>(); InsertSorted(list2, s.StaminaPct, "perk_stamina", Texts.Get(TextId.GaugePerkStaminaLabel)); InsertSorted(list2, s.JumpPct, "perk_jump", Texts.Get(TextId.GaugePerkJumpLabel)); InsertSorted(list2, s.EnemyIgnorePct, "perk_enemy_ignore", Texts.Get(TextId.GaugePerkEnemyIgnoreLabel)); InsertSorted(list2, s.HealPct, "perk_heal", Texts.Get(TextId.GaugePerkHealLabel)); InsertSorted(list2, s.InformantPct, "perk_informant", Texts.Get(TextId.GaugePerkInformantLabel)); int num = -1000; int num2 = -1000; foreach (KeyValuePair item in list2) { int tier; if (item.Key - num >= minGapPct) { tier = 0; num = item.Key; } else if (item.Key - num2 >= minGapPct) { tier = 1; num2 = item.Key; } else { tier = 0; num = item.Key; } list.Add(new GaugeMarker(item.Key, GaugeMarkerKind.Perk, item.Value[0], item.Value[1], tier, permille >= item.Key * 10)); } for (int i = 10; i <= 90; i += 10) { list.Add(new GaugeMarker(i, GaugeMarkerKind.Scale, null, null, 0, unlocked: false)); } return list; } public static int UnlockedCount(MeetingGaugeSnapshot s, int permille) { if (s == null) { return 0; } return UnlockedIf(s.StaminaPct, permille) + UnlockedIf(s.JumpPct, permille) + UnlockedIf(s.EnemyIgnorePct, permille) + UnlockedIf(s.HealPct, permille) + UnlockedIf(s.InformantPct, permille); } private static int UnlockedIf(int pct, int permille) { if (pct < 1 || pct > 100 || permille < pct * 10) { return 0; } return 1; } private static void InsertSorted(List> entries, int pct, string iconKey, string label) { if (pct >= 1 && pct <= 100) { int num = entries.Count; while (num > 0 && entries[num - 1].Key > pct) { num--; } entries.Insert(num, new KeyValuePair(pct, new string[2] { iconKey, label })); } } } public sealed class GaugeReveal { public const int HoldMs = 1000; public const int DurationMs = 1600; private long _startUnixMs; private int _fromPermille; private int _toPermille; private int _fromLoss; private int _toLoss; public bool Active { get; private set; } public static bool ShouldAnimate(int fromPermille, int toPermille) { return toPermille > fromPermille; } public void Start(int fromPermille, int toPermille, int fromLoss, int toLoss, long nowUnixMs) { _fromPermille = fromPermille; _toPermille = toPermille; _fromLoss = ((fromLoss <= toLoss) ? fromLoss : toLoss); _toLoss = toLoss; _startUnixMs = nowUnixMs + 1000; Active = true; } public void Stop() { Active = false; } public bool GrowthStarted(long nowUnixMs) { if (Active) { return nowUnixMs >= _startUnixMs; } return false; } public double Progress(long nowUnixMs) { if (!Active) { return 1.0; } long num = nowUnixMs - _startUnixMs; if (num <= 0) { return 0.0; } if (num >= 1600) { return 1.0; } double num2 = 1.0 - (double)num / 1600.0; return 1.0 - num2 * num2 * num2; } public int CurrentPermille(long nowUnixMs) { return Interpolate(_fromPermille, _toPermille, Progress(nowUnixMs)); } public int CurrentLoss(long nowUnixMs) { return Interpolate(_fromLoss, _toLoss, Progress(nowUnixMs)); } public bool Done(long nowUnixMs) { if (Active) { return nowUnixMs - _startUnixMs >= 1600; } return true; } private static int Interpolate(int from, int to, double progress) { return from + (int)Math.Round((double)(to - from) * progress); } } public sealed class GazeConeLock { public const float ConeDegrees = 15f; private readonly float _cosThreshold; private bool _hasRef; private float _refX; private float _refY; private float _refZ; public GazeConeLock(float coneDegrees = 15f) { _cosThreshold = (float)Math.Cos((double)((coneDegrees > 0f) ? coneDegrees : 15f) * Math.PI / 180.0); } public bool Update(float dirX, float dirY, float dirZ) { float num = (float)Math.Sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ); if (num < 1E-06f) { _hasRef = false; return false; } float num2 = dirX / num; float num3 = dirY / num; float num4 = dirZ / num; if (_hasRef && num2 * _refX + num3 * _refY + num4 * _refZ >= _cosThreshold) { return true; } _refX = num2; _refY = num3; _refZ = num4; _hasRef = true; return false; } public void Reset() { _hasRef = false; } } public sealed class HudModel { private bool _meetingFrozen; private long _frozenRemainingMs; private readonly BellSchedule _bell = new BellSchedule(); public HudState Compute(HudInput input) { bool num = input.Phase == GamePhase.Play || input.Phase == GamePhase.Meeting; bool flag = input.Phase == GamePhase.Meeting || input.MeetingActive; if (flag) { if (!_meetingFrozen) { _frozenRemainingMs = RemainingMs(input.RoundEndUnixMs, input.NowUnixMs); _meetingFrozen = true; } } else { _meetingFrozen = false; } if (!num) { _bell.Reset(); return HudState.Hidden; } bool flag2 = input.RolesClient != null && input.RolesClient.CanShowHud(input.LocalRole); long num2 = (flag ? _frozenRemainingMs : RemainingMs(input.RoundEndUnixMs, input.NowUnixMs)); int num3 = ((!flag) ? _bell.Tick(num2) : 0); return new HudState(showBadge: true, input.LocalRole, showTimer: true, num2, flag, BellSchedule.AlertActive(num2), (num3 > 0) ? BellSchedule.VolumeScaleFor(num3) : 0f, flag2, flag2 ? input.RolesClient.RatioPermille : 0, input.NearMeetingButton, input.RightsRemaining, input.DebugSession); } private static long RemainingMs(long endUnixMs, long nowUnixMs) { long num = endUnixMs - nowUnixMs; if (num <= 0) { return 0L; } return num; } } public readonly struct HudInput { public GamePhase Phase { get; } public Role? LocalRole { get; } public long RoundEndUnixMs { get; } public RolesClientState RolesClient { get; } public bool NearMeetingButton { get; } public int RightsRemaining { get; } public long NowUnixMs { get; } public bool MeetingActive { get; } public bool DebugSession { get; } public HudInput(GamePhase phase, Role? localRole, long roundEndUnixMs, RolesClientState rolesClient, bool nearMeetingButton, int rightsRemaining, long nowUnixMs, bool meetingActive = false, bool debugSession = false) { Phase = phase; LocalRole = localRole; RoundEndUnixMs = roundEndUnixMs; RolesClient = rolesClient; NearMeetingButton = nearMeetingButton; RightsRemaining = rightsRemaining; NowUnixMs = nowUnixMs; MeetingActive = meetingActive; DebugSession = debugSession; } } public readonly struct HudState { public static readonly HudState Hidden = new HudState(showBadge: false, null, showTimer: false, 0L, timerFrozen: false, timerAlert: false, 0f, showGauge: false, 0, showRights: false, 0); public bool ShowBadge { get; } public Role? BadgeRole { get; } public bool ShowTimer { get; } public long TimerRemainingMs { get; } public bool TimerFrozen { get; } public bool TimerAlert { get; } public float BellVolumeScale { get; } public bool ShowGauge { get; } public int GaugeRatioPermille { get; } public bool ShowRights { get; } public int RightsRemaining { get; } public bool ShowTestPlay { get; } public HudState(bool showBadge, Role? badgeRole, bool showTimer, long timerRemainingMs, bool timerFrozen, bool timerAlert, float bellVolumeScale, bool showGauge, int gaugeRatioPermille, bool showRights, int rightsRemaining, bool showTestPlay = false) { ShowBadge = showBadge; BadgeRole = badgeRole; ShowTimer = showTimer; TimerRemainingMs = timerRemainingMs; TimerFrozen = timerFrozen; TimerAlert = timerAlert; BellVolumeScale = bellVolumeScale; ShowGauge = showGauge; GaugeRatioPermille = gaugeRatioPermille; ShowRights = showRights; RightsRemaining = rightsRemaining; ShowTestPlay = showTestPlay; } } public static class LangFile { public static Dictionary Parse(string content) { Dictionary dictionary = new Dictionary(); if (string.IsNullOrEmpty(content)) { return dictionary; } string[] array = content.Split('\n'); for (int i = 0; i < array.Length; i++) { string text = array[i]; if (text.Length > 0 && text[text.Length - 1] == '\r') { text = text.Substring(0, text.Length - 1); } if (text.Length == 0 || text[0] == '#') { continue; } int num = text.IndexOf('='); if (num >= 0) { string text2 = text.Substring(0, num).Trim(); if (text2.Length != 0 && Enum.TryParse(text2, out var result) && !(result.ToString() != text2)) { string value = text.Substring(num + 1); dictionary[result] = Unescape(value); } } } return dictionary; } private static string Unescape(string value) { if (value.IndexOf('\\') < 0) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); int num = 0; while (num < value.Length) { char c = value[num]; if (c == '\\' && num + 1 < value.Length) { switch (value[num + 1]) { case 'n': stringBuilder.Append('\n'); num += 2; continue; case '\\': stringBuilder.Append('\\'); num += 2; continue; } } stringBuilder.Append(c); num++; } return stringBuilder.ToString(); } } public sealed class LifecycleGate { private bool _startedThisRound; public bool ShouldAutoStart(bool modeEnabled, bool isRunLevel, GamePhase phase) { if (!modeEnabled) { return false; } if (!isRunLevel) { return false; } if (_startedThisRound) { return false; } if (phase != GamePhase.Lobby) { return false; } return true; } public void MarkStarted() { _startedThisRound = true; } public void ResetForNextRound() { _startedThisRound = false; } } public enum LobbyStartWarningKind { None, Caution, Severe } public readonly struct LobbyStartDecision { public LobbyStartWarningKind ModWarningKind { get; } public int DifferenceCount { get; } public int UnavailableCount { get; } public int PendingCount { get; } public bool BaselineReady { get; } public int PlayerCount { get; } public int RequiredPlayerCount { get; } public bool PlayerCountSatisfied { get; } public int WerewolfCount { get; } public bool TeamTotalSatisfied { get; } public bool HasPending => PendingCount > 0; public bool HasPlayerShortfall => !PlayerCountSatisfied; public bool HasTeamOverflow => !TeamTotalSatisfied; public bool AllowsContinue { get { if (!HasPlayerShortfall) { return !HasTeamOverflow; } return false; } } public LobbyStartWarningKind WarningKind { get { if ((!HasPlayerShortfall && !HasTeamOverflow) || ModWarningKind != LobbyStartWarningKind.None) { return ModWarningKind; } return LobbyStartWarningKind.Caution; } } public LobbyStartDecision(LobbyStartWarningKind modWarningKind, int differenceCount, int unavailableCount, int pendingCount, bool baselineReady, int playerCount, int requiredPlayerCount, bool playerCountSatisfied, int werewolfCount, bool teamTotalSatisfied) { ModWarningKind = modWarningKind; DifferenceCount = differenceCount; UnavailableCount = unavailableCount; PendingCount = pendingCount; BaselineReady = baselineReady; PlayerCount = playerCount; RequiredPlayerCount = requiredPlayerCount; PlayerCountSatisfied = playerCountSatisfied; WerewolfCount = werewolfCount; TeamTotalSatisfied = teamTotalSatisfied; } } public static class LobbyStartGate { public const int FinalCheckWaitMs = 1500; public const int AttemptTokenLifetimeMs = 5000; public static LobbyStartDecision Evaluate(ModIntegritySnapshot snapshot, bool baselineReady, int playerCount, int werewolfCount, bool debugMode) { int num = 0; int num2 = 0; int num3 = 0; if (snapshot != null) { for (int i = 0; i < snapshot.Records.Count; i++) { switch (snapshot.Records[i].Status) { case ModIntegrityStatus.Difference: num++; break; case ModIntegrityStatus.Unavailable: num2++; break; case ModIntegrityStatus.Pending: num3++; break; } } } LobbyStartWarningKind modWarningKind = ((!baselineReady || snapshot == null || num2 > 0 || num3 > 0) ? LobbyStartWarningKind.Severe : ((num > 0) ? LobbyStartWarningKind.Caution : LobbyStartWarningKind.None)); bool teamTotalSatisfied = debugMode || !ConfigValidator.WerewolfCountExceedsPlayers(werewolfCount, playerCount); return new LobbyStartDecision(modWarningKind, num, num2, num3, baselineReady, playerCount, 3, PlayerCountGate.IsSatisfied(playerCount, debugMode), werewolfCount, teamTotalSatisfied); } } public sealed class LobbyStartAttempt { private bool _bypassArmed; private int _lobbyInstanceId; private int _authorizingHostActor; private long _bypassExpiresUnixMs; public bool IsFinalCheckPending { get; private set; } public long FinalCheckDeadlineUnixMs { get; private set; } public bool IsBypassArmed => _bypassArmed; public bool TryBeginFinalCheck(bool hasPending, long nowUnixMs) { if (!hasPending || IsFinalCheckPending) { return false; } IsFinalCheckPending = true; FinalCheckDeadlineUnixMs = nowUnixMs + 1500; return true; } public bool ShouldCompleteFinalCheck(bool hasPending, long nowUnixMs) { if (!IsFinalCheckPending) { return false; } if (hasPending && nowUnixMs < FinalCheckDeadlineUnixMs) { return false; } IsFinalCheckPending = false; FinalCheckDeadlineUnixMs = 0L; return true; } public void CancelFinalCheck() { IsFinalCheckPending = false; FinalCheckDeadlineUnixMs = 0L; } public void ArmOneShotBypass(int lobbyInstanceId, int authorizingHostActor, long nowUnixMs) { if (lobbyInstanceId == 0) { throw new ArgumentOutOfRangeException("lobbyInstanceId"); } if (authorizingHostActor <= 0) { throw new ArgumentOutOfRangeException("authorizingHostActor"); } CancelFinalCheck(); _bypassArmed = true; _lobbyInstanceId = lobbyInstanceId; _authorizingHostActor = authorizingHostActor; _bypassExpiresUnixMs = nowUnixMs + 5000; } public bool TryConsumeBypass(int lobbyInstanceId, int currentMasterActor, long nowUnixMs) { if (!_bypassArmed) { return false; } bool result = lobbyInstanceId == _lobbyInstanceId && currentMasterActor == _authorizingHostActor && nowUnixMs <= _bypassExpiresUnixMs; ClearBypass(); return result; } public void Clear() { CancelFinalCheck(); ClearBypass(); } private void ClearBypass() { _bypassArmed = false; _lobbyInstanceId = 0; _authorizingHostActor = 0; _bypassExpiresUnixMs = 0L; } } public sealed class ManualBlock { public string IconKey; public TextId BodyId; public static ManualBlock Text(TextId bodyId) { return new ManualBlock { IconKey = null, BodyId = bodyId }; } public static ManualBlock Icon(string iconKey, TextId bodyId) { return new ManualBlock { IconKey = iconKey, BodyId = bodyId }; } } public sealed class ManualPage { public TextId TitleId; public ManualBlock[] Blocks; public string ImageKey; public float ImageHeight; public float ImageWidth; public ManualPage(TextId titleId, TextId bodyId, string imageKey = null, float imageHeight = 0f, float imageWidth = 0f) : this(titleId, new ManualBlock[1] { ManualBlock.Text(bodyId) }, imageKey, imageHeight, imageWidth) { } public ManualPage(TextId titleId, ManualBlock[] blocks, string imageKey = null, float imageHeight = 0f, float imageWidth = 0f) { TitleId = titleId; Blocks = blocks; ImageKey = imageKey; ImageHeight = imageHeight; ImageWidth = imageWidth; } } public sealed class ManualSection { public TextId TitleId; public int StartIndex; public ManualSection(TextId titleId, int startIndex) { TitleId = titleId; StartIndex = startIndex; } } public static class ManualCatalog { private const float RoleIconHeight = 320f; private const float GaugeImageWidth = 1000f; private const float GaugeImageHeight = 128f; private const float MeetingButtonImageWidth = 426f; private const float MeetingButtonImageHeight = 229f; private const float VotePanelImageWidth = 772f; private const float VotePanelImageHeight = 264f; public static readonly ManualPage[] Pages = new ManualPage[27] { new ManualPage(TextId.ManualWelcomeTitle, TextId.ManualWelcomeBody), new ManualPage(TextId.ManualGameFlowTitle, TextId.ManualGameFlowBody), new ManualPage(TextId.ManualVillagerWinTitle, TextId.ManualVillagerWinBody), new ManualPage(TextId.ManualWerewolfWinTitle, TextId.ManualWerewolfWinBody), new ManualPage(TextId.ManualValuablesMapTitle, TextId.ManualValuablesMapBody, "manual_map_markers"), new ManualPage(TextId.ManualValuableRecordTitle, new ManualBlock[2] { ManualBlock.Icon("icon_valuable_record", TextId.ManualValuableRecordBody), ManualBlock.Text(TextId.ManualValuableRecordToggle) }), new ManualPage(TextId.ManualCombatTitle, TextId.ManualCombatBody), new ManualPage(TextId.ManualEndgamePrepTitle, TextId.ManualEndgamePrepBody), new ManualPage(TextId.ManualCorpseTitle, new ManualBlock[1] { ManualBlock.Icon("icon_host_megaphone", TextId.ManualCorpseBody) }), new ManualPage(TextId.ManualConveneTitle, TextId.ManualConveneBody, "manual_meeting_button", 229f, 426f), new ManualPage(TextId.ManualMeetingFlowTitle, TextId.ManualMeetingFlowBody, "manual_death_reveal"), new ManualPage(TextId.ManualVotingTitle, TextId.ManualVotingBody, "manual_vote_panel", 264f, 772f), new ManualPage(TextId.ManualGaugeBasicsTitle, new ManualBlock[4] { ManualBlock.Text(TextId.ManualGaugeIntro), ManualBlock.Text(TextId.ManualGaugeLoss), ManualBlock.Text(TextId.ManualGaugeDelivery), ManualBlock.Text(TextId.ManualGaugeLines) }, "manual_gauge_overview", 128f, 1000f), new ManualPage(TextId.ManualRoleVillagerTitle, TextId.ManualRoleVillagerBody, "role_villager", 320f), new ManualPage(TextId.ManualRoleShamanTitle, TextId.ManualRoleShamanIntro, "role_shaman", 320f), new ManualPage(TextId.ManualShamanSenseTitle, new ManualBlock[2] { ManualBlock.Text(TextId.ManualShamanGhost), ManualBlock.Text(TextId.ManualShamanStorm) }), new ManualPage(TextId.ManualRoleWerewolfTitle, new ManualBlock[2] { ManualBlock.Text(TextId.ManualRoleWerewolfIntro), ManualBlock.Text(TextId.ManualRoleWerewolfEnemyMap) }, "role_werewolf", 320f), new ManualPage(TextId.ManualRoleWerewolfPerksTitle, new ManualBlock[2] { ManualBlock.Icon("perk_stamina", TextId.ManualRoleWerewolfPerkStamina), ManualBlock.Icon("perk_jump", TextId.ManualRoleWerewolfPerkJump) }), new ManualPage(TextId.ManualRoleWerewolfPerksTitle2, new ManualBlock[3] { ManualBlock.Icon("perk_enemy_ignore", TextId.ManualRoleWerewolfPerkEnemyIgnore), ManualBlock.Icon("perk_heal", TextId.ManualRoleWerewolfPerkHeal), ManualBlock.Text(TextId.ManualRoleWerewolfPerkToggle) }), new ManualPage(TextId.ManualRoleWerewolfBeaconTitle, TextId.ManualRoleWerewolfBeaconBody, "perk_beacon"), new ManualPage(TextId.ManualRoleBlackCatTitle, TextId.ManualRoleBlackCatIntro, "role_blackcat", 320f), new ManualPage(TextId.ManualBlackCatInformantTitle, new ManualBlock[2] { ManualBlock.Text(TextId.ManualRoleBlackCatInformant), ManualBlock.Text(TextId.ManualRoleBlackCatGaugeNote) }, "perk_informant"), new ManualPage(TextId.ManualBlackCatCounterTitle, TextId.ManualBlackCatCounterBody), new ManualPage(TextId.ManualRoleBomberTitle, TextId.ManualRoleBomberIntro, "role_bomber", 320f), new ManualPage(TextId.ManualBomberPlantTitle, TextId.ManualRoleBomberPlant, "perk_bomb_plant"), new ManualPage(TextId.ManualRoleBomberDetonateTitle, TextId.ManualRoleBomberDetonateBody, "perk_bomb_detonate"), new ManualPage(TextId.ManualAfterDeathTitle, TextId.ManualAfterDeathBody) }; public static readonly ManualSection[] Sections = new ManualSection[10] { new ManualSection(TextId.ManualSectionBasics, 0), new ManualSection(TextId.ManualSectionExploration, 4), new ManualSection(TextId.ManualSectionMeeting, 8), new ManualSection(TextId.ManualSectionGauge, 12), new ManualSection(TextId.ManualSectionVillager, 13), new ManualSection(TextId.ManualSectionShaman, 14), new ManualSection(TextId.ManualSectionWerewolf, 16), new ManualSection(TextId.ManualSectionBlackCat, 20), new ManualSection(TextId.ManualSectionBomber, 23), new ManualSection(TextId.ManualSectionAfterDeath, 26) }; public static int PageCount => Pages.Length; public static int SectionIndexForPage(int pageIndex) { int num = ClampIndex(pageIndex); for (int num2 = Sections.Length - 1; num2 >= 0; num2--) { if (num >= Sections[num2].StartIndex) { return num2; } } return 0; } public static int PreviousSectionStart(int pageIndex) { int num = SectionIndexForPage(pageIndex); return Sections[(num > 0) ? (num - 1) : 0].StartIndex; } public static int NextSectionStart(int pageIndex) { int num = SectionIndexForPage(pageIndex); return Sections[(num + 1 < Sections.Length) ? (num + 1) : num].StartIndex; } public static int ClampIndex(int index) { if (index < 0) { return 0; } if (index >= Pages.Length) { return Pages.Length - 1; } return index; } } public readonly struct MapGridCellRange { public readonly int ColMin; public readonly int ColMax; public readonly int RowMin; public readonly int RowMax; public int Cols => ColMax - ColMin + 1; public int Rows => RowMax - RowMin + 1; public MapGridCellRange(int colMin, int colMax, int rowMin, int rowMax) { ColMin = colMin; ColMax = colMax; RowMin = rowMin; RowMax = rowMax; } } public sealed class MapGridLayout { public float RectLeft; public float RectRight; public float RectBottom; public float RectTop; public float[] VerticalLineX; public float[] HorizontalLineY; public string[] ColumnLabels; public float[] ColumnLabelX; public float ColumnLabelY; public string[] RowLabels; public float[] RowLabelY; public float RowLabelX; } public static class MapGridMath { public const float ModuleSizeWorld = 15f; public const float BoundsShrinkWorld = 1f; public const float LabelOffsetPanel = 22f; public const float LabelEdgeInsetPanel = 16f; public static string ColumnLetter(int index) { if (index < 0) { return "?"; } string text = ""; int num = index; do { text = (char)(65 + num % 26) + text; num = num / 26 - 1; } while (num >= 0); return text; } public static int RowNumber(int rowMax, int rowIndex) { return rowMax - rowIndex + 1; } public static MapGridCellRange? RangeFromModuleCenters(IReadOnlyList<(float X, float Z)> centers, int levelWidth, int levelHeight) { if (centers == null || levelWidth <= 0 || levelHeight <= 0) { return null; } int num = levelWidth / 2; bool flag = false; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; for (int i = 0; i < centers.Count; i++) { int num6 = (int)Math.Round(centers[i].X / 15f, MidpointRounding.AwayFromZero) + num; int num7 = (int)Math.Round((centers[i].Z - 7.5f) / 15f, MidpointRounding.AwayFromZero); if (num6 < 0 || num6 >= levelWidth || num7 < 0 || num7 >= levelHeight) { continue; } if (!flag) { num2 = (num3 = num6); num4 = (num5 = num7); flag = true; continue; } if (num6 < num2) { num2 = num6; } if (num6 > num3) { num3 = num6; } if (num7 < num4) { num4 = num7; } if (num7 > num5) { num5 = num7; } } if (!flag) { return null; } return new MapGridCellRange(num2, num3, num4, num5); } public static MapGridCellRange? RangeFromBounds(int levelWidth, int levelHeight, float mapScale, float originMiniX, float originMiniZ, float boundsMinMiniX, float boundsMaxMiniX, float boundsMinMiniZ, float boundsMaxMiniZ) { if (levelWidth <= 0 || levelHeight <= 0 || mapScale <= 1E-06f) { return null; } if (boundsMaxMiniX <= boundsMinMiniX || boundsMaxMiniZ <= boundsMinMiniZ) { return null; } int offsetCols = levelWidth / 2; float min = (boundsMinMiniX - originMiniX) / mapScale; float max = (boundsMaxMiniX - originMiniX) / mapScale; float min2 = (boundsMinMiniZ - originMiniZ) / mapScale; float max2 = (boundsMaxMiniZ - originMiniZ) / mapScale; ShrinkRange(ref min, ref max, 1f); ShrinkRange(ref min2, ref max2, 1f); int num = ClampInt(ColumnIndexOfWorldX(min, offsetCols), 0, levelWidth - 1); int num2 = ClampInt(ColumnIndexOfWorldX(max, offsetCols), 0, levelWidth - 1); int num3 = ClampInt(RowIndexOfWorldZ(min2), 0, levelHeight - 1); int num4 = ClampInt(RowIndexOfWorldZ(max2), 0, levelHeight - 1); if (num2 < num || num4 < num3) { return null; } return new MapGridCellRange(num, num2, num3, num4); } public static MapGridLayout Compute(int levelWidth, MapGridCellRange range, float mapScale, float originMiniX, float originMiniZ, float camMiniX, float camMiniZ, float orthoSize, float aspect, float panelWidth, float panelHeight) { if (levelWidth <= 0) { return null; } if (mapScale <= 1E-06f || orthoSize <= 0.001f || aspect <= 0.001f) { return null; } if (panelWidth <= 0f || panelHeight <= 0f) { return null; } if (range.ColMax < range.ColMin || range.RowMax < range.RowMin) { return null; } int offsetCols = levelWidth / 2; int colMin = range.ColMin; int colMax = range.ColMax; int rowMin = range.RowMin; int rowMax = range.RowMax; float pxPerMiniX = panelWidth * 0.5f / (orthoSize * aspect); float pxPerMiniZ = panelHeight * 0.5f / orthoSize; MapGridLayout mapGridLayout = new MapGridLayout { RectLeft = PanelX(BoundaryWorldX(colMin)), RectRight = PanelX(BoundaryWorldX(colMax + 1)), RectBottom = PanelY(BoundaryWorldZ(rowMin)), RectTop = PanelY(BoundaryWorldZ(rowMax + 1)) }; int num = colMax - colMin + 1; int num2 = rowMax - rowMin + 1; mapGridLayout.VerticalLineX = new float[num + 1]; for (int i = 0; i <= num; i++) { mapGridLayout.VerticalLineX[i] = PanelX(BoundaryWorldX(colMin + i)); } mapGridLayout.HorizontalLineY = new float[num2 + 1]; for (int j = 0; j <= num2; j++) { mapGridLayout.HorizontalLineY[j] = PanelY(BoundaryWorldZ(rowMin + j)); } mapGridLayout.ColumnLabels = new string[num]; mapGridLayout.ColumnLabelX = new float[num]; for (int k = 0; k < num; k++) { mapGridLayout.ColumnLabels[k] = ColumnLetter(k); mapGridLayout.ColumnLabelX[k] = PanelX((float)(colMin + k - offsetCols) * 15f); } mapGridLayout.RowLabels = new string[num2]; mapGridLayout.RowLabelY = new float[num2]; for (int l = 0; l < num2; l++) { mapGridLayout.RowLabels[l] = RowNumber(rowMax, rowMin + l).ToString(); mapGridLayout.RowLabelY[l] = PanelY((float)(rowMin + l) * 15f + 7.5f); } mapGridLayout.ColumnLabelY = Math.Min(mapGridLayout.RectTop + 22f, panelHeight * 0.5f - 16f); mapGridLayout.RowLabelX = Math.Max(mapGridLayout.RectLeft - 22f, (0f - panelWidth) * 0.5f + 16f); return mapGridLayout; float BoundaryWorldX(int num3) { return (float)(num3 - offsetCols) * 15f - 7.5f; } static float BoundaryWorldZ(int num3) { return (float)num3 * 15f; } float PanelX(float worldX) { return (worldX * mapScale + originMiniX - camMiniX) * pxPerMiniX; } float PanelY(float worldZ) { return (worldZ * mapScale + originMiniZ - camMiniZ) * pxPerMiniZ; } } private static int ColumnIndexOfWorldX(float worldX, int offsetCols) { return (int)Math.Floor((worldX + (float)offsetCols * 15f + 7.5f) / 15f); } private static int RowIndexOfWorldZ(float worldZ) { return (int)Math.Floor(worldZ / 15f); } private static void ShrinkRange(ref float min, ref float max, float amount) { if (max - min > amount * 2f) { min += amount; max -= amount; } else { max = (min = (min + max) * 0.5f); } } private static int ClampInt(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } public static class MapHideGate { public static bool ShouldSuppress(bool roundActive, bool minimapHideEnabled) { return roundActive && minimapHideEnabled; } } public enum ChatSpeaker : byte { Alive, Dead } public enum ChatEntryKind : byte { Message, Vote } public readonly struct ChatLogEntry { public int Actor { get; } public string Name { get; } public string Text { get; } public ChatSpeaker Speaker { get; } public ChatEntryKind Kind { get; } public ChatLogEntry(int actor, string name, string text, ChatSpeaker speaker, ChatEntryKind kind) { Actor = actor; Name = name; Text = text; Speaker = speaker; Kind = kind; } } public sealed class MeetingChatLog : IReadOnlyList, IEnumerable, IEnumerable, IReadOnlyCollection { public const int MaxEntries = 10000; public const int MaxTextLength = 140; public const int MaxNameLength = 20; public const string UnknownName = "???"; private readonly ChatLogEntry[] _entries = new ChatLogEntry[10000]; private int _head; private int _count; public IReadOnlyList Entries => this; public int Count => _count; public ChatLogEntry this[int index] { get { if ((uint)index >= (uint)_count) { throw new ArgumentOutOfRangeException("index"); } return _entries[(_head + index) % 10000]; } } public int Revision { get; private set; } public long AppendedTotal { get; private set; } public long DroppedTotal { get; private set; } public bool Append(int actor, string name, string text, ChatSpeaker speaker) { return AppendCore(actor, name, text, speaker, ChatEntryKind.Message); } public bool AppendVote(int actor, string name, string text) { return AppendCore(actor, name, text, ChatSpeaker.Alive, ChatEntryKind.Vote); } private bool AppendCore(int actor, string name, string text, ChatSpeaker speaker, ChatEntryKind kind) { string text2 = Sanitize(text, 140); if (text2.Length == 0) { return false; } string text3 = Sanitize(name, 20); if (text3.Length == 0) { text3 = "???"; } ChatLogEntry chatLogEntry = new ChatLogEntry(actor, text3, text2, speaker, kind); if (_count < 10000) { int num = (_head + _count) % 10000; _entries[num] = chatLogEntry; _count++; } else { _entries[_head] = chatLogEntry; _head = (_head + 1) % 10000; DroppedTotal++; } AppendedTotal++; Revision++; return true; } public void Clear() { if (_count != 0) { DroppedTotal += _count; for (int i = 0; i < _count; i++) { _entries[(_head + i) % 10000] = default(ChatLogEntry); } _head = 0; _count = 0; Revision++; } } public IEnumerator GetEnumerator() { for (int i = 0; i < _count; i++) { yield return this[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public static string Sanitize(string raw, int maxLength) { if (string.IsNullOrEmpty(raw)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(raw.Length); foreach (char c in raw) { switch (c) { case '<': stringBuilder.Append('<'); continue; case '\t': case '\n': case '\r': stringBuilder.Append(' '); continue; } if (!char.IsControl(c)) { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (maxLength > 0 && text.Length > maxLength) { text = text.Substring(0, maxLength) + "…"; } return text; } } public enum RowStatus : byte { Alive, Dead, Executed, Disconnected } public sealed class MeetingClientState { private const long WarpAlreadyDone = long.MinValue; private bool _active; private bool _votingClosed; private long _warpUnixMs; private long _endUnixMs; private readonly Dictionary _rows = new Dictionary(); private readonly HashSet _voted = new HashSet(); private long _votingUiOffsetMs; private readonly HashSet _announcedDead = new HashSet(); public const long EarlyCloseSlackMs = 1000L; public bool MeetingActive => _active; public int CallerActor { get; private set; } = -1; public ConveneKind Kind { get; private set; } public IReadOnlyDictionary Rows => _rows; public IReadOnlyCollection VotedActors => _voted; public MeetingOutcome Result { get; private set; } public RowStatus GetRowStatus(int actorNumber) { if (!_rows.TryGetValue(actorNumber, out var value)) { return RowStatus.Alive; } return value; } public int CompareRowOrder(int leftActor, int rightActor) { bool flag = GetRowStatus(leftActor) == RowStatus.Alive; bool flag2 = GetRowStatus(rightActor) == RowStatus.Alive; if (flag != flag2) { if (!flag) { return 1; } return -1; } return leftActor.CompareTo(rightActor); } public bool WarpDone(long nowUnixMs) { if (_active) { return nowUnixMs >= _warpUnixMs; } return false; } public bool VotingUiReady(long nowUnixMs) { if (_active) { return nowUnixMs - _votingUiOffsetMs >= _warpUnixMs; } return false; } public bool GaugeIntroReady(long nowUnixMs) { if (_active) { return nowUnixMs - ((_votingUiOffsetMs == 0L) ? 0 : 5000) >= _warpUnixMs; } return false; } public double GaugeMoveProgress(long nowUnixMs) { if (!_active || _votingUiOffsetMs == 0L) { return 1.0; } return MeetingIntro.MoveProgress(nowUnixMs - (_warpUnixMs + _votingUiOffsetMs)); } public bool IsDeadUnannounced(int actorNumber) { if (!_rows.TryGetValue(actorNumber, out var value)) { return false; } if (value != RowStatus.Dead && value != RowStatus.Executed) { return false; } return !_announcedDead.Contains(actorNumber); } public void MarkAllDeadAnnounced() { foreach (KeyValuePair row in _rows) { if (row.Value == RowStatus.Dead || row.Value == RowStatus.Executed) { _announcedDead.Add(row.Key); } } } public long RemainingMs(long nowUnixMs) { if (!_active || _votingClosed) { return 0L; } long num = _endUnixMs - nowUnixMs; if (num <= 0) { return 0L; } return num; } public bool ClosedEarly(long nowUnixMs) { if (_active && _votingClosed) { return nowUnixMs + 1000 < _endUnixMs; } return false; } public void ApplyStartMeeting(int caller, long warpUnixMs, long endUnixMs, ConveneKind kind = ConveneKind.Button) { _active = true; _votingClosed = false; CallerActor = caller; Kind = kind; _warpUnixMs = warpUnixMs; _endUnixMs = endUnixMs; _votingUiOffsetMs = 7600L; _voted.Clear(); Result = null; } public void ApplyVoteProgress(int[] votedActors, long endUnixMs) { _voted.Clear(); if (votedActors != null) { foreach (int item in votedActors) { _voted.Add(item); } } _endUnixMs = endUnixMs; } public void ApplyResult(MeetingOutcome outcome) { Result = outcome; _votingClosed = true; } public void ApplyPlayerDied(int actor, DeathCause cause) { _rows[actor] = ((cause != DeathCause.Vote) ? RowStatus.Dead : RowStatus.Executed); } public void ApplyPlayerLeft(int actor) { _rows[actor] = RowStatus.Disconnected; } public void ApplyCancelled() { _active = false; _votingClosed = false; CallerActor = -1; Kind = ConveneKind.Button; _voted.Clear(); Result = null; } public void ApplyPhase(GamePhase phase) { if (phase == GamePhase.Play || phase == GamePhase.GameOver) { if (_active) { MarkAllDeadAnnounced(); } _active = false; } } public void RestoreFromRoomState(int caller, long endUnixMs) { _active = true; _votingClosed = false; CallerActor = caller; Kind = ConveneKind.Button; _warpUnixMs = long.MinValue; _endUnixMs = endUnixMs; _votingUiOffsetMs = 0L; _voted.Clear(); Result = null; } public void Reset() { _active = false; _votingClosed = false; CallerActor = -1; Kind = ConveneKind.Button; _warpUnixMs = 0L; _endUnixMs = 0L; _votingUiOffsetMs = 0L; _rows.Clear(); _voted.Clear(); _announcedDead.Clear(); Result = null; } } public static class MeetingIntro { public const long GaugeRevealOffsetMs = 5000L; public const long GaugeRevealMs = 2600L; public const long GaugeMoveMs = 500L; public const long VotingUiDelayMs = 7600L; public static double MoveProgress(long sinceVotingUiReadyMs) { if (sinceVotingUiReadyMs <= 0) { return 0.0; } if (sinceVotingUiReadyMs >= 500) { return 1.0; } double num = (double)sinceVotingUiReadyMs / 500.0; return num * num * num; } } public readonly struct MeetingRecapData { public IReadOnlyList DeathNames { get; } public int LostDollars { get; } public int ExtractedDollars { get; } public int HaulGoalDollars { get; } public int BeaconUses { get; } public long Signature => ((((17L * 31L + (DeathNames?.Count ?? 0)) * 31 + LostDollars) * 31 + ExtractedDollars) * 31 + HaulGoalDollars) * 31 + BeaconUses; public MeetingRecapData(IReadOnlyList deathNames, int lostDollars, int extractedDollars, int haulGoalDollars, int beaconUses) { DeathNames = deathNames; LostDollars = lostDollars; ExtractedDollars = extractedDollars; HaulGoalDollars = haulGoalDollars; BeaconUses = beaconUses; } } public static class MeetingRecap { public const int Unknown = -1; public static List BuildLines(MeetingRecapData data) { List list = new List(4); list.Add((data.DeathNames != null && data.DeathNames.Count > 0) ? Texts.Format(TextId.RecapDeathsFormat, JoinNames(data.DeathNames)) : Texts.Get(TextId.RecapDeathsNone)); if (data.LostDollars >= 0) { list.Add(Texts.Format(TextId.RecapLostFormat, data.LostDollars)); } if (data.ExtractedDollars >= 0 && data.HaulGoalDollars >= 0) { list.Add(Texts.Format(TextId.RecapHaulFormat, data.ExtractedDollars, data.HaulGoalDollars)); } if (data.BeaconUses >= 0) { list.Add((data.BeaconUses > 0) ? Texts.Format(TextId.RecapBeaconFormat, data.BeaconUses) : Texts.Get(TextId.RecapBeaconNone)); } return list; } private static string JoinNames(IReadOnlyList names) { string value = Texts.Get(TextId.RecapNameSeparator); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < names.Count; i++) { if (!string.IsNullOrEmpty(names[i])) { if (stringBuilder.Length > 0) { stringBuilder.Append(value); } stringBuilder.Append(MeetingChatLog.Sanitize(names[i], 20)); } } return stringBuilder.ToString(); } } public enum ConveneRejectReason : byte { None, NoRight, Suppressed, WrongPhase, CallerDead, AlreadyMeeting, UnknownCaller, CorpseReportLastRun, NoCorpse } public enum ConveneKind : byte { Button, CorpseReport } public enum MeetingStage : byte { Idle, Countdown, Voting, Closing } public sealed class MeetingSession { private readonly GameConfig _config; private readonly GameSession _gameSession; private readonly IReadOnlyList _players; private readonly long _gameStartUnixMs; private readonly Dictionary _rights = new Dictionary(); private readonly HashSet _disconnected = new HashSet(); private readonly MeetingTimer _timer = new MeetingTimer(); private VoteBox _voteBox; private long _warpUnixMs; private long _closingUntilUnixMs; public const int PostResultKillDelaySec = 5; public MeetingStage Stage { get; private set; } public MeetingOutcome Outcome { get; private set; } public long LastMeetingEndUnixMs { get; private set; } = long.MinValue; public int CallerActor { get; private set; } = -1; public ConveneKind CurrentKind { get; private set; } public int VotedCount => _voteBox?.VotedActors.Count ?? 0; public long EndUnixMs { get { if (Stage != MeetingStage.Idle) { return _timer.EndUnixMs; } return 0L; } } public event Action OnSend; public event Action OnExecutePlayer; public event Action OnPhaseChangeRequest; public event Action OnMeetingStateChanged; public event Action OnVotingStarted; public event Action OnRightsChanged; public MeetingSession(GameConfig config, GameSession gameSession, IReadOnlyList players, long gameStartUnixMs) { _config = config ?? throw new ArgumentNullException("config"); _gameSession = gameSession ?? throw new ArgumentNullException("gameSession"); _players = players ?? throw new ArgumentNullException("players"); _gameStartUnixMs = gameStartUnixMs; foreach (WPlayer player in _players) { _rights[player.ActorNumber] = config.MeetingRightsPerPlayer; } } public int RightsRemaining(int actorNumber) { if (!_rights.TryGetValue(actorNumber, out var value)) { return 0; } return value; } public int[] VotersFor(int targetActor) { return _voteBox?.VotersFor(targetActor); } public ConveneRejectReason TryConvene(int callerActor, long nowUnixMs) { return TryConvene(callerActor, nowUnixMs, ConveneKind.Button, lastRunActive: false, corpseAvailable: true); } public ConveneRejectReason TryConvene(int callerActor, long nowUnixMs, ConveneKind kind, bool lastRunActive, bool corpseAvailable) { ConveneRejectReason conveneRejectReason = EvaluateConvene(callerActor, nowUnixMs, kind, lastRunActive, corpseAvailable); if (conveneRejectReason != ConveneRejectReason.None) { WLog.Line("convene_rejected", false, ("caller", callerActor), ("kind", kind), ("reason", conveneRejectReason)); if (conveneRejectReason != ConveneRejectReason.UnknownCaller) { Send(new OutboundMessage(176, new object[1] { ConveneDeniedWire.ToWire(conveneRejectReason) }, MessageTarget.Actors, new int[1] { callerActor })); } return conveneRejectReason; } if (kind != ConveneKind.CorpseReport) { _rights[callerActor] -= 1; this.OnRightsChanged?.Invoke(callerActor, _rights[callerActor]); } CallerActor = callerActor; CurrentKind = kind; Outcome = null; _voteBox = null; _warpUnixMs = nowUnixMs + (long)_config.MeetingCountdownSec * 1000L; _timer.Start(_warpUnixMs + 7600, _config.MeetingDurationSec); Stage = MeetingStage.Countdown; WLog.Line("convene_accepted", false, ("caller", callerActor), ("kind", kind), ("warpUnixMs", _warpUnixMs), ("endUnixMs", _timer.EndUnixMs)); Send(new OutboundMessage(163, new object[4] { callerActor, _warpUnixMs, _timer.EndUnixMs, (byte)kind }, MessageTarget.All, null)); this.OnPhaseChangeRequest?.Invoke(GamePhase.Meeting); this.OnMeetingStateChanged?.Invoke(callerActor, _timer.EndUnixMs); return ConveneRejectReason.None; } private ConveneRejectReason EvaluateConvene(int callerActor, long nowUnixMs, ConveneKind kind, bool lastRunActive, bool corpseAvailable) { WPlayer wPlayer = FindPlayer(callerActor); if (wPlayer == null) { return ConveneRejectReason.UnknownCaller; } if (Stage != MeetingStage.Idle) { return ConveneRejectReason.AlreadyMeeting; } if (_gameSession.Phase != GamePhase.Play) { return ConveneRejectReason.WrongPhase; } if (!wPlayer.Alive) { return ConveneRejectReason.CallerDead; } if (kind == ConveneKind.CorpseReport) { if (lastRunActive) { return ConveneRejectReason.CorpseReportLastRun; } if (!corpseAvailable) { return ConveneRejectReason.NoCorpse; } } else { if (nowUnixMs < _gameStartUnixMs + (long)_config.ConveneSuppressStartSec * 1000L) { return ConveneRejectReason.Suppressed; } if (LastMeetingEndUnixMs != long.MinValue && nowUnixMs < LastMeetingEndUnixMs + (long)_config.ConveneSuppressAfterSec * 1000L) { return ConveneRejectReason.Suppressed; } } if (kind != ConveneKind.CorpseReport && RightsRemaining(callerActor) <= 0) { return ConveneRejectReason.NoRight; } return ConveneRejectReason.None; } public bool TryCancelCorpseReportCountdown(long nowUnixMs) { if (Stage != MeetingStage.Countdown || CurrentKind != ConveneKind.CorpseReport) { return false; } int callerActor = CallerActor; Stage = MeetingStage.Idle; CallerActor = -1; CurrentKind = ConveneKind.Button; WLog.Line("meeting_cancelled", false, ("caller", callerActor), ("reason", "final_extraction_started"), ("nowUnixMs", nowUnixMs)); Send(new OutboundMessage(178, new object[1] { (byte)0 }, MessageTarget.All, null)); this.OnMeetingStateChanged?.Invoke(-1, 0L); this.OnPhaseChangeRequest?.Invoke(GamePhase.Play); return true; } public bool AbortForGameOver() { if (Stage == MeetingStage.Idle) { return false; } WLog.Line("meeting_aborted", false, ("caller", CallerActor), ("stage", Stage), ("reason", "game_over")); Stage = MeetingStage.Idle; CallerActor = -1; CurrentKind = ConveneKind.Button; Outcome = null; _voteBox = null; this.OnMeetingStateChanged?.Invoke(-1, 0L); return true; } public void Tick(long nowUnixMs) { switch (Stage) { case MeetingStage.Countdown: if (nowUnixMs >= _warpUnixMs) { BeginVoting(); } break; case MeetingStage.Voting: if (_voteBox.AllVotesIn || _timer.IsExpired(nowUnixMs)) { CloseVoting(nowUnixMs); } break; case MeetingStage.Closing: if (nowUnixMs >= _closingUntilUnixMs) { FinishMeeting(nowUnixMs); } break; } } public VoteRejectReason CastVote(int voterActor, int targetActor, long nowUnixMs) { if (Stage != MeetingStage.Voting) { WLog.Line("vote_rejected", false, ("voter", voterActor), ("reason", VoteRejectReason.NotVotingStage), ("stage", Stage)); return VoteRejectReason.NotVotingStage; } VoteRejectReason voteRejectReason = _voteBox.TryCast(voterActor, targetActor); if (voteRejectReason != VoteRejectReason.None) { WLog.Line("vote_rejected", false, ("voter", voterActor), ("reason", voteRejectReason)); return voteRejectReason; } WLog.Line("vote_accepted", false, ("voter", voterActor)); if (_config.VoteTimeCutEnabled) { long endUnixMs = _timer.EndUnixMs; int num = _voteBox.RemainingVoterCount + 1; long num2 = _timer.ReduceByVote(num, nowUnixMs); WLog.Line("vote_shorten", false, ("preVoteRemaining", num), ("endBefore", endUnixMs), ("endAfter", num2), ("remainMsBefore", endUnixMs - nowUnixMs), ("remainMsAfter", num2 - nowUnixMs)); } EmitVoteProgress(); return VoteRejectReason.None; } public void ExtendClosingHold(long extraMs) { if (Stage == MeetingStage.Closing) { _closingUntilUnixMs += extraMs; } } private void BeginVoting() { List list = new List(); foreach (WPlayer player in _players) { if (player.Alive && !_disconnected.Contains(player.ActorNumber)) { list.Add(player.ActorNumber); } } _voteBox = new VoteBox(list); Stage = MeetingStage.Voting; WLog.Line("meeting_voting", false, ("expectedVoters", list.Count)); this.OnVotingStarted?.Invoke(); } private void CloseVoting(long nowUnixMs) { Outcome = _voteBox.Tally(IsEligibleForExecution); Stage = MeetingStage.Closing; _closingUntilUnixMs = nowUnixMs + (long)Math.Max(_config.ResultDisplaySec, 6) * 1000L; Send(new OutboundMessage(166, new object[3] { Outcome.ExecutedActor, Outcome.TargetActors, Outcome.VoteCounts }, MessageTarget.All, null)); WLog.Line("meeting_result", false, ("executed", Outcome.ExecutedActor), ("targets", Outcome.TargetActors), ("counts", Outcome.VoteCounts)); if (Outcome.ExecutedActor != -1) { this.OnExecutePlayer?.Invoke(Outcome.ExecutedActor); } } private void FinishMeeting(long nowUnixMs) { LastMeetingEndUnixMs = nowUnixMs; Stage = MeetingStage.Idle; CallerActor = -1; CurrentKind = ConveneKind.Button; this.OnMeetingStateChanged?.Invoke(-1, 0L); if (_gameSession.Phase != GamePhase.GameOver) { this.OnPhaseChangeRequest?.Invoke(GamePhase.Play); } WLog.Line("meeting_end", false, ("lastEndUnixMs", LastMeetingEndUnixMs)); } public void NotifyPlayerLeft(int actorNumber, long nowUnixMs) { _disconnected.Add(actorNumber); if (Stage == MeetingStage.Voting) { _voteBox.RemoveVoter(actorNumber); _voteBox.RemoveTarget(actorNumber, disconnected: true); EmitVoteProgress(); } WLog.Line("meeting_player_left", false, ("actor", actorNumber), ("stage", Stage), ("nowUnixMs", nowUnixMs)); } public void NotifyPlayerDied(int actorNumber) { if (Stage == MeetingStage.Voting) { _voteBox.RemoveVoter(actorNumber); _voteBox.RemoveTarget(actorNumber); EmitVoteProgress(); } WLog.Line("meeting_player_died", false, ("actor", actorNumber), ("stage", Stage)); } private bool IsEligibleForExecution(int actorNumber) { return FindPlayer(actorNumber)?.Alive ?? false; } private void EmitVoteProgress() { List list = new List(_voteBox.VotedActors); Send(new OutboundMessage(165, new object[2] { list.ToArray(), _timer.EndUnixMs }, MessageTarget.All, null)); } private WPlayer FindPlayer(int actorNumber) { foreach (WPlayer player in _players) { if (player.ActorNumber == actorNumber) { return player; } } return null; } private void Send(OutboundMessage message) { this.OnSend?.Invoke(message); } } internal static class WWMeetingCodes { public const byte StartMeeting = 163; public const byte VoteProgress = 165; public const byte MeetingResult = 166; public const byte ConveneDenied = 176; public const byte MeetingCancelled = 178; } public sealed class MeetingTimer { public const long MinRemainingMs = 10000L; private bool _started; public long EndUnixMs { get; private set; } public void Start(long warpUnixMs, int durationSeconds) { if (durationSeconds <= 0) { throw new ArgumentOutOfRangeException("durationSeconds", durationSeconds, "会議制限時間は正の秒数であること。"); } EndUnixMs = warpUnixMs + (long)durationSeconds * 1000L; _started = true; } public long ReduceByVote(int remainingVoters, long nowUnixMs) { if (!_started) { return EndUnixMs; } if (remainingVoters <= 1) { return EndUnixMs; } long num = EndUnixMs - nowUnixMs; if (num < 10000) { return EndUnixMs; } long num2 = num * (remainingVoters - 1) / remainingVoters; if (num2 < 10000) { num2 = 10000L; } long num3 = nowUnixMs + num2; if (num3 > EndUnixMs) { num3 = EndUnixMs; } EndUnixMs = num3; return EndUnixMs; } public bool IsExpired(long nowUnixMs) { if (_started) { return nowUnixMs >= EndUnixMs; } return false; } } public sealed class ModIntegrityClientState { private sealed class DetailAssembly { public int ChunkCount; public IReadOnlyList[] Chunks; public int Received; } public const int DetailRequestTimeoutMs = 3000; public const int MaxDetailChunks = 32; public const int MaxDetailEntries = 1024; public const int DetailEntriesPerChunk = 32; private readonly Dictionary _details = new Dictionary(); private int _detailRequestActor = -1; private long _detailRequestStartedUnixMs; private DetailAssembly _detailAssembly; public ModIntegritySnapshot Current { get; private set; } public int PendingDetailActor => _detailRequestActor; public bool TryApplySnapshot(ModIntegritySnapshot snapshot, int expectedMasterActor) { if (snapshot == null || expectedMasterActor <= 0 || snapshot.BaselineActor != expectedMasterActor) { return false; } if (Current != null) { if (snapshot.Epoch < Current.Epoch) { return false; } if (snapshot.Epoch == Current.Epoch && snapshot.Revision <= Current.Revision) { return false; } } Current = snapshot; ClearDetails(); return true; } public bool BeginDetailRequest(int actor, long nowUnixMs) { if (Current == null || actor <= 0) { return false; } if (!Current.TryGetRecord(actor, out var record) || record.Status != ModIntegrityStatus.Difference) { return false; } if (_details.ContainsKey(actor)) { return false; } if (_detailRequestActor == actor) { return false; } _detailRequestActor = actor; _detailRequestStartedUnixMs = nowUnixMs; _detailAssembly = null; return true; } public bool TryApplyDetailChunk(ModIntegrityDetailChunk chunk, out bool completed) { completed = false; if (Current == null || chunk == null) { return false; } if (chunk.Epoch != Current.Epoch || chunk.Revision != Current.Revision) { return false; } if (chunk.Actor != _detailRequestActor) { return false; } if (chunk.ChunkCount < 1 || chunk.ChunkCount > 32 || chunk.ChunkIndex < 0 || chunk.ChunkIndex >= chunk.ChunkCount || chunk.Differences.Count > 32) { AbandonDetailRequest(); return false; } if (_detailAssembly == null) { _detailAssembly = new DetailAssembly { ChunkCount = chunk.ChunkCount, Chunks = new IReadOnlyList[chunk.ChunkCount] }; } else if (_detailAssembly.ChunkCount != chunk.ChunkCount) { AbandonDetailRequest(); return false; } IReadOnlyList readOnlyList = _detailAssembly.Chunks[chunk.ChunkIndex]; if (readOnlyList != null) { if (SameDifferences(readOnlyList, chunk.Differences)) { return true; } AbandonDetailRequest(); return false; } _detailAssembly.Chunks[chunk.ChunkIndex] = ModIntegrityCollections.Freeze(chunk.Differences); _detailAssembly.Received++; if (_detailAssembly.Received != _detailAssembly.ChunkCount) { return true; } List list = new List(); for (int i = 0; i < _detailAssembly.Chunks.Length; i++) { IReadOnlyList readOnlyList2 = _detailAssembly.Chunks[i]; if (readOnlyList2 == null) { AbandonDetailRequest(); return false; } for (int j = 0; j < readOnlyList2.Count; j++) { list.Add(readOnlyList2[j]); if (list.Count > 1024) { AbandonDetailRequest(); return false; } } } _details[chunk.Actor] = new ModIntegrityDetail(chunk.Epoch, chunk.Revision, chunk.Actor, list); _detailRequestActor = -1; _detailRequestStartedUnixMs = 0L; _detailAssembly = null; completed = true; return true; } public bool TickDetailTimeout(long nowUnixMs, out int timedOutActor) { timedOutActor = -1; if (_detailRequestActor < 0 || nowUnixMs - _detailRequestStartedUnixMs < 3000) { return false; } timedOutActor = _detailRequestActor; AbandonDetailRequest(); return true; } public bool TryGetDetail(int actor, out ModIntegrityDetail detail) { return _details.TryGetValue(actor, out detail); } public void Clear() { Current = null; ClearDetails(); } private void ClearDetails() { _details.Clear(); _detailRequestActor = -1; _detailRequestStartedUnixMs = 0L; _detailAssembly = null; } private void AbandonDetailRequest() { _detailRequestActor = -1; _detailRequestStartedUnixMs = 0L; _detailAssembly = null; } private static bool SameDifferences(IReadOnlyList left, IReadOnlyList right) { if (left == null || right == null || left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { ModDifference modDifference = left[i]; ModDifference modDifference2 = right[i]; if (modDifference == null || modDifference2 == null || modDifference.Kind != modDifference2.Kind || !string.Equals(modDifference.Guid, modDifference2.Guid, StringComparison.Ordinal) || !string.Equals(modDifference.Name, modDifference2.Name, StringComparison.Ordinal) || !string.Equals(modDifference.BaselineValue, modDifference2.BaselineValue, StringComparison.Ordinal) || !string.Equals(modDifference.ParticipantValue, modDifference2.ParticipantValue, StringComparison.Ordinal)) { return false; } } return true; } } public static class ModIntegrityGate { public static bool IsActive(bool inRoom, bool inLobbyMenu, bool isMaster, bool localModeEnabled, bool hostSignalReceived) { if (!IsInScope(inRoom, inLobbyMenu)) { return false; } if (!isMaster) { return hostSignalReceived; } return localModeEnabled; } public static bool IsInScope(bool inRoom, bool inLobbyMenu) { return inRoom && inLobbyMenu; } public static bool IsHostSignal(bool isMaster, int masterActor, int senderActor) { if (!isMaster && masterActor > 0) { return senderActor == masterActor; } return false; } } public sealed class ModIntegrityHostState { private sealed class ActorState { public int Actor; public ModIntegrityStatus Status; public ModUnavailableReason Reason; public ModDifferenceSummary Summary; public IReadOnlyList Differences = ModIntegrityCollections.Freeze(null); public string AcceptedFingerprint; public bool LockedInvalid; public long FirstRequestUnixMs; public long LastRequestUnixMs; public int RequestAttempts; } public const int ResponseTimeoutMs = 5000; public const int RetryIntervalMs = 1500; public const int MaxRequestAttempts = 3; public const int SnapshotCoalesceMs = 250; private readonly Dictionary _actors = new Dictionary(); private long _lastSnapshotPublishedUnixMs; public int Epoch { get; private set; } public int Revision { get; private set; } public int BaselineActor { get; private set; } public ModManifest Baseline { get; private set; } public bool Dirty { get; private set; } public bool IsInitialized => Baseline != null; public void BeginEpoch(int epoch, int baselineActor, IReadOnlyList rosterActors, ModManifest baseline, long nowUnixMs) { if (epoch <= 0) { throw new ArgumentOutOfRangeException("epoch"); } if (baselineActor <= 0) { throw new ArgumentOutOfRangeException("baselineActor"); } if (baseline == null) { throw new ArgumentNullException("baseline"); } ValidateRoster(rosterActors, baselineActor); _actors.Clear(); Epoch = epoch; Revision = 1; BaselineActor = baselineActor; Baseline = baseline; Dirty = true; _lastSnapshotPublishedUnixMs = nowUnixMs; for (int i = 0; i < rosterActors.Count; i++) { int num = rosterActors[i]; _actors[num] = ((num == baselineActor) ? CreateBaseline(num) : CreatePending(num, nowUnixMs)); } } public void SyncRoster(IReadOnlyList rosterActors, long nowUnixMs) { if (!IsInitialized) { return; } ValidateRoster(rosterActors, BaselineActor); HashSet hashSet = new HashSet(); for (int i = 0; i < rosterActors.Count; i++) { hashSet.Add(rosterActors[i]); } bool flag = false; List list = new List(); foreach (int key in _actors.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } for (int j = 0; j < list.Count; j++) { _actors.Remove(list[j]); flag = true; } for (int k = 0; k < rosterActors.Count; k++) { int num = rosterActors[k]; if (!_actors.ContainsKey(num)) { _actors[num] = ((num == BaselineActor) ? CreateBaseline(num) : CreatePending(num, nowUnixMs)); flag = true; } } if (flag) { Touch(); } } public FingerprintReportOutcome ApplyFingerprintReport(int actor, string reportedFingerprint, long nowUnixMs) { if (!TryGetParticipant(actor, out var state)) { return FingerprintReportOutcome.RejectedInvalid; } if (!ModManifestComparer.IsCanonicalFingerprint(reportedFingerprint)) { SetUnavailable(state, ModUnavailableReason.InvalidPayload); return FingerprintReportOutcome.RejectedInvalid; } if (state.LockedInvalid) { return FingerprintReportOutcome.RejectedInvalid; } if (!string.IsNullOrEmpty(state.AcceptedFingerprint)) { if (string.Equals(state.AcceptedFingerprint, reportedFingerprint, StringComparison.Ordinal)) { if (state.Status != ModIntegrityStatus.Match) { return FingerprintReportOutcome.RejectedInvalid; } return FingerprintReportOutcome.Matched; } LockConflictingReport(state); return FingerprintReportOutcome.RejectedInvalid; } if (!string.Equals(reportedFingerprint, Baseline.Fingerprint, StringComparison.Ordinal)) { state.LastRequestUnixMs = nowUnixMs; return FingerprintReportOutcome.RetryCurrentRequest; } state.AcceptedFingerprint = reportedFingerprint; SetResolved(state, ModIntegrityStatus.Match, default(ModDifferenceSummary), ModIntegrityCollections.Freeze(null)); return FingerprintReportOutcome.Matched; } public void ApplyManifest(int actor, ModManifest manifest, long nowUnixMs) { if (manifest == null || !TryGetParticipant(actor, out var state) || state.LockedInvalid) { return; } if (!string.IsNullOrEmpty(state.AcceptedFingerprint)) { if (!string.Equals(state.AcceptedFingerprint, manifest.Fingerprint, StringComparison.Ordinal)) { LockConflictingReport(state); } } else { state.AcceptedFingerprint = manifest.Fingerprint; ModComparisonResult modComparisonResult = ModManifestComparer.Compare(Baseline, manifest); SetResolved(state, modComparisonResult.IsMatch ? ModIntegrityStatus.Match : ModIntegrityStatus.Difference, modComparisonResult.Summary, modComparisonResult.Differences); } } public void MarkUnavailable(int actor, ModUnavailableReason reason) { if (reason != ModUnavailableReason.None && TryGetParticipant(actor, out var state)) { SetUnavailable(state, reason); } } public IReadOnlyList GetRetryTargets(long nowUnixMs, bool force) { List list = new List(); foreach (ActorState value in _actors.Values) { if (value.Status != ModIntegrityStatus.Pending) { continue; } if (!force) { if (value.RequestAttempts >= 3 || nowUnixMs - value.LastRequestUnixMs < 1500) { continue; } value.RequestAttempts++; } value.LastRequestUnixMs = nowUnixMs; list.Add(value.Actor); } list.Sort(); return list.AsReadOnly(); } public bool TickTimeouts(long nowUnixMs) { bool flag = false; foreach (ActorState value in _actors.Values) { if (value.Status == ModIntegrityStatus.Pending && nowUnixMs - value.FirstRequestUnixMs >= 5000) { value.Status = ModIntegrityStatus.Unavailable; value.Reason = ModUnavailableReason.NoResponse; value.Summary = default(ModDifferenceSummary); value.Differences = ModIntegrityCollections.Freeze(null); flag = true; } } if (flag) { Touch(); } return flag; } public bool ShouldPublishSnapshot(long nowUnixMs, bool force) { if (!Dirty || !IsInitialized) { return false; } if (!force) { return nowUnixMs - _lastSnapshotPublishedUnixMs >= 250; } return true; } public ModIntegritySnapshot BuildSnapshot() { if (!IsInitialized) { return null; } List list = new List(_actors.Count); if (_actors.TryGetValue(BaselineActor, out var value)) { list.Add(ToRecord(value)); } List list2 = new List(_actors.Keys); list2.Sort(); for (int i = 0; i < list2.Count; i++) { int num = list2[i]; if (num != BaselineActor) { list.Add(ToRecord(_actors[num])); } } if (!ModIntegritySnapshot.TryCreate(Epoch, Revision, BaselineActor, list, out var snapshot, out var error)) { throw new InvalidOperationException("snapshot:" + error); } return snapshot; } public IReadOnlyList GetDifferences(int actor) { if (!_actors.TryGetValue(actor, out var value)) { return ModIntegrityCollections.Freeze(null); } return value.Differences; } public void MarkSnapshotPublished(long nowUnixMs) { if (IsInitialized) { Dirty = false; _lastSnapshotPublishedUnixMs = nowUnixMs; } } public void Clear() { _actors.Clear(); Epoch = 0; Revision = 0; BaselineActor = 0; Baseline = null; Dirty = false; _lastSnapshotPublishedUnixMs = 0L; } private static ActorState CreateBaseline(int actor) { return new ActorState { Actor = actor, Status = ModIntegrityStatus.Baseline, Reason = ModUnavailableReason.None }; } private static ActorState CreatePending(int actor, long nowUnixMs) { return new ActorState { Actor = actor, Status = ModIntegrityStatus.Pending, Reason = ModUnavailableReason.None, FirstRequestUnixMs = nowUnixMs, LastRequestUnixMs = nowUnixMs, RequestAttempts = 1 }; } private bool TryGetParticipant(int actor, out ActorState state) { if (actor == BaselineActor || !_actors.TryGetValue(actor, out state)) { state = null; return false; } return true; } private void SetResolved(ActorState state, ModIntegrityStatus status, ModDifferenceSummary summary, IReadOnlyList differences) { bool num = state.Status != status || state.Reason != ModUnavailableReason.None || state.Summary != summary; state.Status = status; state.Reason = ModUnavailableReason.None; state.Summary = summary; state.Differences = ModIntegrityCollections.Freeze(differences); if (num) { Touch(); } } private void SetUnavailable(ActorState state, ModUnavailableReason reason) { if (state.Status != ModIntegrityStatus.Unavailable || state.Reason != reason) { state.Status = ModIntegrityStatus.Unavailable; state.Reason = reason; state.Summary = default(ModDifferenceSummary); state.Differences = ModIntegrityCollections.Freeze(null); Touch(); } } private void LockConflictingReport(ActorState state) { state.LockedInvalid = true; SetUnavailable(state, ModUnavailableReason.InvalidPayload); } private void Touch() { Revision++; Dirty = true; } private static ModParticipantRecord ToRecord(ActorState state) { return new ModParticipantRecord(state.Actor, state.Status, state.Reason, state.Summary); } private static void ValidateRoster(IReadOnlyList rosterActors, int baselineActor) { if (rosterActors == null) { throw new ArgumentNullException("rosterActors"); } if (rosterActors.Count == 0 || rosterActors.Count > 100) { throw new ArgumentOutOfRangeException("rosterActors"); } HashSet hashSet = new HashSet(); bool flag = false; for (int i = 0; i < rosterActors.Count; i++) { int num = rosterActors[i]; if (num <= 0 || !hashSet.Add(num)) { throw new ArgumentException("actor", "rosterActors"); } if (num == baselineActor) { flag = true; } } if (!flag) { throw new ArgumentException("baseline", "rosterActors"); } } } public enum ModIntegrityStatus : byte { Baseline, Pending, Match, Difference, Unavailable } public enum ModUnavailableReason : byte { None, NoResponse, UnsupportedProtocol, InvalidPayload, TooLarge, ManifestCollectionFailed } public enum ModDifferenceKind : byte { Missing, Extra, Version, Content } public enum FingerprintReportOutcome : byte { Matched, RetryCurrentRequest, RejectedInvalid } internal static class ModIntegrityCollections { public static IReadOnlyList Freeze(IEnumerable items) { if (items != null) { return new List(items).AsReadOnly(); } return new List().AsReadOnly(); } } public sealed class ModManifestEntry { public string Guid { get; } public string Name { get; } public string Version { get; } public string ContentId { get; } public ModManifestEntry(string guid, string name, string version, string contentId) { Guid = guid; Name = name; Version = version; ContentId = contentId; } } public sealed class ModManifest { public IReadOnlyList Entries { get; } public string Fingerprint { get; } internal ModManifest(IEnumerable entries, string fingerprint) { Entries = ModIntegrityCollections.Freeze(entries); Fingerprint = fingerprint; } } public sealed class ModDifference { public ModDifferenceKind Kind { get; } public string Guid { get; } public string Name { get; } public string BaselineValue { get; } public string ParticipantValue { get; } public ModDifference(ModDifferenceKind kind, string guid, string name, string baselineValue, string participantValue) { Kind = kind; Guid = guid ?? string.Empty; Name = name ?? string.Empty; BaselineValue = baselineValue ?? string.Empty; ParticipantValue = participantValue ?? string.Empty; } } public readonly struct ModDifferenceSummary : IEquatable { public int Missing { get; } public int Extra { get; } public int Version { get; } public int Content { get; } public int Total => Missing + Extra + Version + Content; public ModDifferenceSummary(int missing, int extra, int version, int content) { if (missing < 0 || extra < 0 || version < 0 || content < 0) { throw new ArgumentOutOfRangeException("missing"); } Missing = missing; Extra = extra; Version = version; Content = content; } public bool Equals(ModDifferenceSummary other) { if (Missing == other.Missing && Extra == other.Extra && Version == other.Version) { return Content == other.Content; } return false; } public override bool Equals(object obj) { if (obj is ModDifferenceSummary other) { return Equals(other); } return false; } public override int GetHashCode() { return HashCode.Combine(Missing, Extra, Version, Content); } public static bool operator ==(ModDifferenceSummary left, ModDifferenceSummary right) { return left.Equals(right); } public static bool operator !=(ModDifferenceSummary left, ModDifferenceSummary right) { return !left.Equals(right); } } public sealed class ModComparisonResult { public IReadOnlyList Differences { get; } public ModDifferenceSummary Summary { get; } public bool IsMatch => Summary.Total == 0; internal ModComparisonResult(IEnumerable differences) { Differences = ModIntegrityCollections.Freeze(differences); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; for (int i = 0; i < Differences.Count; i++) { switch (Differences[i].Kind) { case ModDifferenceKind.Missing: num++; break; case ModDifferenceKind.Extra: num2++; break; case ModDifferenceKind.Version: num3++; break; case ModDifferenceKind.Content: num4++; break; } } Summary = new ModDifferenceSummary(num, num2, num3, num4); } } public sealed class ModParticipantRecord { public int Actor { get; } public ModIntegrityStatus Status { get; } public ModUnavailableReason UnavailableReason { get; } public ModDifferenceSummary Summary { get; } internal ModParticipantRecord(int actor, ModIntegrityStatus status, ModUnavailableReason unavailableReason, ModDifferenceSummary summary) { Actor = actor; Status = status; UnavailableReason = unavailableReason; Summary = summary; } } public sealed class ModIntegritySnapshot { public int Epoch { get; } public int Revision { get; } public int BaselineActor { get; } public IReadOnlyList Records { get; } private ModIntegritySnapshot(int epoch, int revision, int baselineActor, IEnumerable records) { Epoch = epoch; Revision = revision; BaselineActor = baselineActor; Records = ModIntegrityCollections.Freeze(records); } public static bool TryCreate(int epoch, int revision, int baselineActor, IEnumerable records, out ModIntegritySnapshot snapshot, out string error) { snapshot = null; error = null; if (epoch <= 0) { return Fail("epoch", out error); } if (revision < 0) { return Fail("revision", out error); } if (baselineActor <= 0) { return Fail("baseline_actor", out error); } if (records == null) { return Fail("records", out error); } List list = new List(); HashSet hashSet = new HashSet(); int num = 0; foreach (ModParticipantRecord record in records) { if (record == null) { return Fail("null_record", out error); } if (record.Actor <= 0 || !hashSet.Add(record.Actor)) { return Fail("actor", out error); } if (!Enum.IsDefined(typeof(ModIntegrityStatus), record.Status) || !Enum.IsDefined(typeof(ModUnavailableReason), record.UnavailableReason)) { return Fail("enum", out error); } if (record.Actor == baselineActor) { if (record.Status != ModIntegrityStatus.Baseline) { return Fail("baseline_status", out error); } num++; } else if (record.Status == ModIntegrityStatus.Baseline) { return Fail("foreign_baseline", out error); } if (record.Status == ModIntegrityStatus.Unavailable) { if (record.UnavailableReason == ModUnavailableReason.None) { return Fail("missing_reason", out error); } } else if (record.UnavailableReason != ModUnavailableReason.None) { return Fail("unexpected_reason", out error); } if (record.Status == ModIntegrityStatus.Difference) { if (record.Summary.Total <= 0 || record.Summary.Total > 1024) { return Fail("difference_summary", out error); } } else if (record.Summary.Total != 0) { return Fail("unexpected_summary", out error); } list.Add(record); if (list.Count > 100) { return Fail("too_many_records", out error); } } if (num != 1) { return Fail("baseline_count", out error); } snapshot = new ModIntegritySnapshot(epoch, revision, baselineActor, list); return true; } public bool TryGetRecord(int actor, out ModParticipantRecord record) { for (int i = 0; i < Records.Count; i++) { if (Records[i].Actor == actor) { record = Records[i]; return true; } } record = null; return false; } private static bool Fail(string value, out string error) { error = value; return false; } } public sealed class ModIntegrityDetailChunk { public int Epoch { get; } public int Revision { get; } public int Actor { get; } public int ChunkIndex { get; } public int ChunkCount { get; } public IReadOnlyList Differences { get; } public ModIntegrityDetailChunk(int epoch, int revision, int actor, int chunkIndex, int chunkCount, IEnumerable differences) { Epoch = epoch; Revision = revision; Actor = actor; ChunkIndex = chunkIndex; ChunkCount = chunkCount; Differences = ModIntegrityCollections.Freeze(differences); } } public sealed class ModIntegrityDetail { public int Epoch { get; } public int Revision { get; } public int Actor { get; } public IReadOnlyList Differences { get; } internal ModIntegrityDetail(int epoch, int revision, int actor, IEnumerable differences) { Epoch = epoch; Revision = revision; Actor = actor; Differences = ModIntegrityCollections.Freeze(differences); } } public sealed class ModParticipantView { public ModParticipantRecord Record { get; } public string DisplayName { get; } public bool IsLocal { get; } public bool IsBaseline { get; } public ModParticipantView(ModParticipantRecord record, string displayName, bool isLocal, bool isBaseline) { Record = record ?? throw new ArgumentNullException("record"); DisplayName = (string.IsNullOrEmpty(displayName) ? $"Actor {record.Actor}" : displayName); IsLocal = isLocal; IsBaseline = isBaseline; } } public static class ModManifestComparer { public static bool TryCreateManifest(IEnumerable entries, out ModManifest manifest, out string error) { manifest = null; error = null; if (entries == null) { error = "entries"; return false; } List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ModManifestEntry entry in entries) { if (entry == null) { error = "null_entry"; return false; } string text = (entry.Guid ?? string.Empty).Trim().ToLowerInvariant(); if (text.Length == 0) { error = "empty_guid"; return false; } if (!hashSet.Add(text)) { error = "duplicate_guid"; return false; } list.Add(new ModManifestEntry(text, entry.Name ?? string.Empty, entry.Version ?? string.Empty, entry.ContentId ?? string.Empty)); } list.Sort((ModManifestEntry a, ModManifestEntry b) => StringComparer.Ordinal.Compare(a.Guid, b.Guid)); string fingerprint = ComputeFingerprint(list); manifest = new ModManifest(list, fingerprint); return true; } public static bool IsCanonicalFingerprint(string value) { if (value == null || value.Length != 64) { return false; } foreach (char c in value) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { return false; } } return true; } public static ModComparisonResult Compare(ModManifest baseline, ModManifest participant) { if (baseline == null) { throw new ArgumentNullException("baseline"); } if (participant == null) { throw new ArgumentNullException("participant"); } Dictionary dictionary = ToDictionary(baseline); Dictionary dictionary2 = ToDictionary(participant); List list = new List(); for (int i = 0; i < baseline.Entries.Count; i++) { ModManifestEntry modManifestEntry = baseline.Entries[i]; if (!dictionary2.TryGetValue(modManifestEntry.Guid, out var value)) { list.Add(new ModDifference(ModDifferenceKind.Missing, modManifestEntry.Guid, modManifestEntry.Name, modManifestEntry.Version, string.Empty)); continue; } if (!string.Equals(modManifestEntry.Version, value.Version, StringComparison.Ordinal)) { list.Add(new ModDifference(ModDifferenceKind.Version, modManifestEntry.Guid, DisplayName(modManifestEntry, value), modManifestEntry.Version, value.Version)); } if (!string.Equals(modManifestEntry.ContentId, value.ContentId, StringComparison.Ordinal)) { list.Add(new ModDifference(ModDifferenceKind.Content, modManifestEntry.Guid, DisplayName(modManifestEntry, value), modManifestEntry.ContentId, value.ContentId)); } } for (int j = 0; j < participant.Entries.Count; j++) { ModManifestEntry modManifestEntry2 = participant.Entries[j]; if (!dictionary.ContainsKey(modManifestEntry2.Guid)) { list.Add(new ModDifference(ModDifferenceKind.Extra, modManifestEntry2.Guid, modManifestEntry2.Name, string.Empty, modManifestEntry2.Version)); } } return new ModComparisonResult(list); } private static Dictionary ToDictionary(ModManifest manifest) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < manifest.Entries.Count; i++) { ModManifestEntry modManifestEntry = manifest.Entries[i]; dictionary[modManifestEntry.Guid] = modManifestEntry; } return dictionary; } private static string DisplayName(ModManifestEntry baseline, ModManifestEntry participant) { if (!string.IsNullOrEmpty(baseline.Name)) { return baseline.Name; } return participant.Name ?? string.Empty; } private static string ComputeFingerprint(IReadOnlyList entries) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < entries.Count; i++) { ModManifestEntry modManifestEntry = entries[i]; stringBuilder.Append(modManifestEntry.Guid).Append('\n').Append(modManifestEntry.Version) .Append('\n') .Append(modManifestEntry.ContentId) .Append('\n'); } byte[] bytes = Encoding.UTF8.GetBytes(stringBuilder.ToString()); byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(bytes); } StringBuilder stringBuilder2 = new StringBuilder(array.Length * 2); for (int j = 0; j < array.Length; j++) { stringBuilder2.Append(array[j].ToString("x2")); } return stringBuilder2.ToString(); } } public sealed class NaturalHealClock { private long _nextDueUnixMs; public bool ShouldHeal(bool active, long nowUnixMs, int intervalSec) { if (!active) { _nextDueUnixMs = 0L; return false; } long num = (long)((intervalSec < 1) ? 1 : intervalSec) * 1000L; if (_nextDueUnixMs == 0L) { _nextDueUnixMs = nowUnixMs + num; return false; } if (nowUnixMs < _nextDueUnixMs) { return false; } _nextDueUnixMs += num; if (_nextDueUnixMs <= nowUnixMs) { _nextDueUnixMs = nowUnixMs + num; } return true; } public void Reset() { _nextDueUnixMs = 0L; } } public enum NecroVoiceMode : byte { Off, NonWerewolfDead, AllDead } public static class NecroVoiceModes { public static NecroVoiceMode FromByte(byte value) { return value switch { 0 => NecroVoiceMode.Off, 1 => NecroVoiceMode.NonWerewolfDead, 2 => NecroVoiceMode.AllDead, _ => NecroVoiceMode.Off, }; } } public static class NoticeCatalog { public static string Format(SessionNotice notice) { if (notice == null) { return null; } switch (notice.Kind) { case NoticeKind.ConveneStarted: return Texts.Format(TextId.NoticeConveneStartedFormat, notice.ActorName); case NoticeKind.BeaconAudit: if (notice.BeaconUseCount <= 0) { return Texts.Get(TextId.NoticeBeaconAuditNone); } return Texts.Format(TextId.NoticeBeaconAuditFormat, notice.BeaconUseCount); case NoticeKind.NoExecution: return Texts.Get(TextId.NoticeNoExecution); case NoticeKind.Executed: return Texts.Format(TextId.NoticeExecutedFormat, notice.ActorName); case NoticeKind.BlackCatRevealed: return Texts.Format(TextId.NoticeBlackCatRevealedFormat, notice.ActorName); case NoticeKind.CurseVictim: return Texts.Format(TextId.NoticeCurseVictimFormat, notice.ActorName); case NoticeKind.CatAwakened: return Texts.Get(TextId.NoticeCatAwakened); case NoticeKind.CorpseReportStarted: return Texts.Format(TextId.NoticeCorpseReportStartedFormat, notice.ActorName); case NoticeKind.MeetingCancelled: return Texts.Get(TextId.NoticeMeetingCancelledExtraction); case NoticeKind.PlayerDisconnected: return Texts.Format(TextId.NoticePlayerDisconnectedFormat, notice.ActorName); case NoticeKind.ConveneHoldHint: return Texts.Get(TextId.NoticeConveneHoldHint); case NoticeKind.ConveneDenied: return FormatConveneDenied(notice.DenyReason); default: return null; } } private static string FormatConveneDenied(ConveneRejectReason reason) { return reason switch { ConveneRejectReason.None => null, ConveneRejectReason.NoRight => Texts.Get(TextId.NoticeConveneDeniedNoRight), ConveneRejectReason.Suppressed => Texts.Get(TextId.NoticeConveneDeniedSuppressed), ConveneRejectReason.WrongPhase => Texts.Get(TextId.NoticeConveneDeniedWrongPhase), ConveneRejectReason.CorpseReportLastRun => Texts.Get(TextId.NoticeConveneDeniedLastRun), ConveneRejectReason.NoCorpse => Texts.Get(TextId.NoticeConveneDeniedNoCorpse), _ => Texts.Get(TextId.NoticeConveneDeniedOther), }; } } public static class NoticeSfx { public const string DefaultClipKey = "sfx_toast"; public const string ConveneStartedClipKey = "sfx_notice_convene"; public const string CorpseReportClipKey = "sfx_corpse_report"; public const string BomberProximityWarningClipKey = "sfx_bomb_proximity_warning"; public const string PlayerDisconnectedClipKey = "sfx_player_disconnected"; public static string Resolve(SessionNotice notice) { if (notice == null) { return "sfx_toast"; } return notice.Kind switch { NoticeKind.ConveneStarted => "sfx_notice_convene", NoticeKind.CorpseReportStarted => "sfx_corpse_report", NoticeKind.PlayerDisconnected => "sfx_player_disconnected", _ => "sfx_toast", }; } } public enum MessageTarget { All, Actors, Master } public sealed record OutboundMessage(byte Code, object[] Payload, MessageTarget Target, int[] TargetActors); public static class WWEventCodes { public const byte AssignRole = 160; public const byte RevealSelfRole = 161; public const byte RevealTeammates = 162; public const byte PlayerDied = 168; public const byte GameOver = 169; public const byte GameStart = 170; public const byte PhaseChanged = 172; } public sealed class PerkGauge { private static readonly IReadOnlyList Empty = Array.Empty(); private readonly GameConfig _config; private float _lostDollars; private PerkFlags _unlocked; private bool _informantFired; private int _beaconChargesGranted; public bool BaseFrozen { get; private set; } public float BaseDollars { get; private set; } public float LostDollars => _lostDollars; public PerkFlags UnlockedFlags => _unlocked; public bool InformantFired => _informantFired; public int DisplayPermille { get { if (!BaseFrozen || BaseDollars <= 0f) { return 0; } int num = (int)(_lostDollars / BaseDollars * 1000f); if (num <= 1000) { return num; } return 1000; } } public PerkGauge(GameConfig config) { _config = config ?? throw new ArgumentNullException("config"); } public void FreezeBase(float totalDollars) { if (BaseFrozen) { WLog.Line("gauge_freeze_ignored", true, ("reason", "already_frozen"), ("total", totalDollars)); } else if (totalDollars <= 0f) { WLog.Line("gauge_freeze_ignored", true, ("reason", "non_positive"), ("total", totalDollars)); } else { BaseFrozen = true; BaseDollars = totalDollars; WLog.Line("gauge_freeze", true, ("baseDollars", totalDollars)); } } public IReadOnlyList AddLoss(float lostDollars) { if (!BaseFrozen) { WLog.Line("gauge_add_ignored", true, ("reason", "base_not_frozen"), ("lost", lostDollars)); return Empty; } if (lostDollars <= 0f) { return Empty; } _lostDollars += lostDollars; WLog.Line("gauge_add", true, ("lost", lostDollars), ("totalLost", _lostDollars), ("permille", DisplayPermille)); float num = _lostDollars / BaseDollars * 100f; List events = null; TryUnlock(ref events, num, _config.StaminaUnlockPct, PerkId.InfiniteStamina); TryUnlock(ref events, num, _config.JumpUnlockPct, PerkId.InfiniteJump); TryUnlock(ref events, num, _config.EnemyIgnoreUnlockPct, PerkId.EnemyIgnore); TryUnlock(ref events, num, _config.HealUnlockPct, PerkId.NaturalHeal); if (_config.BeaconChargePct > 0) { int num2 = (int)(num / (float)_config.BeaconChargePct); if (num2 > _beaconChargesGranted) { int count = num2 - _beaconChargesGranted; _beaconChargesGranted = num2; (events ?? (events = new List())).Add(GaugeEvent.Charged(count)); } } if (!_informantFired && num >= (float)_config.InformantThresholdPct) { _informantFired = true; (events ?? (events = new List())).Add(GaugeEvent.Informant()); } IReadOnlyList readOnlyList = events; return readOnlyList ?? Empty; } public static float ComputeRealLoss(float dollarBeforeLoss, float valueLost, float dollarOriginal) { if (!(dollarBeforeLoss - valueLost < dollarOriginal * 0.15f)) { return valueLost; } return dollarBeforeLoss; } public bool DebugForceUnlock(PerkId perk) { if (PerkFlagsUtil.Has(_unlocked, perk)) { return false; } _unlocked |= PerkFlagsUtil.ToFlag(perk); WLog.Line("perk_unlocked", true, ("perk", perk), ("via", "debug")); return true; } public bool DebugForceInformant() { if (_informantFired) { return false; } _informantFired = true; return true; } private void TryUnlock(ref List events, float pct, int thresholdPct, PerkId perk) { if (!PerkFlagsUtil.Has(_unlocked, perk) && !(pct < (float)thresholdPct)) { _unlocked |= PerkFlagsUtil.ToFlag(perk); (events ?? (events = new List())).Add(GaugeEvent.Unlocked(perk)); WLog.Line("perk_unlocked", true, ("perk", perk), ("pct", pct)); } } } public enum PerkId : byte { InfiniteStamina, InfiniteJump, EnemyIgnore, NaturalHeal } [Flags] public enum PerkFlags : byte { None = 0, InfiniteStamina = 1, InfiniteJump = 2, EnemyIgnore = 4, NaturalHeal = 8 } public static class PerkFlagsUtil { public static PerkFlags ToFlag(PerkId perk) { return perk switch { PerkId.InfiniteStamina => PerkFlags.InfiniteStamina, PerkId.InfiniteJump => PerkFlags.InfiniteJump, PerkId.EnemyIgnore => PerkFlags.EnemyIgnore, PerkId.NaturalHeal => PerkFlags.NaturalHeal, _ => PerkFlags.None, }; } public static bool Has(PerkFlags flags, PerkId perk) { return (flags & ToFlag(perk)) != 0; } } public enum GaugeEventKind : byte { PerkUnlocked, BeaconCharged, InformantReady } public sealed class GaugeEvent { public GaugeEventKind Kind { get; } public PerkId Perk { get; } public int BeaconChargeCount { get; } private GaugeEvent(GaugeEventKind kind, PerkId perk, int beaconChargeCount) { Kind = kind; Perk = perk; BeaconChargeCount = beaconChargeCount; } public static GaugeEvent Unlocked(PerkId perk) { return new GaugeEvent(GaugeEventKind.PerkUnlocked, perk, 0); } public static GaugeEvent Charged(int count) { return new GaugeEvent(GaugeEventKind.BeaconCharged, PerkId.InfiniteStamina, count); } public static GaugeEvent Informant() { return new GaugeEvent(GaugeEventKind.InformantReady, PerkId.InfiniteStamina, 0); } } public enum BeaconStatus : byte { Ok, Suppressed, Cooldown, NoCharge, MeetingActive } public static class WWRolesCodes { public const byte BeaconAudit = 167; public const byte SyncPerkGauge = 171; public const byte RoleAction = 174; public const byte RoleState = 175; public const byte CurseCandidates = 179; } public static class RoleActionSubtype { public const byte CurseDesignate = 0; public const byte BeaconUse = 1; public const byte WolfModeSync = 2; } public static class RoleStateSubtype { public const byte CurseStarted = 0; public const byte CurseResolved = 1; public const byte MeetingGauge = 2; } public static class PlayerCountGate { public const int MinimumPlayers = 3; public static bool IsSatisfied(int playerCount, bool debugMode) { if (!debugMode) { return playerCount >= 3; } return true; } } public sealed class ProximityGauge { private readonly Dictionary _accumulated = new Dictionary(); private readonly float _fullSeconds; private readonly float _decayMultiplier; private bool _armed = true; private bool _pendingEdge; public ProximityGauge(float fullSeconds, float decayMultiplier = 2f) { _fullSeconds = ((fullSeconds > 0f) ? fullSeconds : 1f); _decayMultiplier = ((decayMultiplier > 0f) ? decayMultiplier : 1f); } public void Tick(int actor, bool within, float deltaSeconds, bool suspend) { if (!suspend && !(deltaSeconds <= 0f)) { _accumulated.TryGetValue(actor, out var value); bool num = value >= _fullSeconds; float num2 = (within ? (value + deltaSeconds) : (value - deltaSeconds * _decayMultiplier)); if (num2 < 0f) { num2 = 0f; } if (num2 > _fullSeconds) { num2 = _fullSeconds; } _accumulated[actor] = num2; if (!num && num2 >= _fullSeconds && _armed) { _pendingEdge = true; _armed = false; } } } public void DebugSetFull(int actor) { _accumulated.TryGetValue(actor, out var value); bool num = value >= _fullSeconds; _accumulated[actor] = _fullSeconds; if (!num && _armed) { _pendingEdge = true; _armed = false; } } public void ResetAll() { _accumulated.Clear(); _armed = true; _pendingEdge = false; } public void Remove(int actor) { _accumulated.Remove(actor); } public float Ratio(int actor) { _accumulated.TryGetValue(actor, out var value); return value / _fullSeconds; } public bool IsFull(int actor) { _accumulated.TryGetValue(actor, out var value); return value >= _fullSeconds; } public bool TryGetNotifyEdge(out bool armed) { if (!_armed && AllEmpty()) { _armed = true; } bool pendingEdge = _pendingEdge; _pendingEdge = false; armed = _armed; return pendingEdge; } private bool AllEmpty() { foreach (float value in _accumulated.Values) { if (value > 0f) { return false; } } return true; } } public sealed class ResultCountdown { private int _durationSeconds; private long _startedAtUnixMs; private bool _active; public bool Active => _active; public void Begin(long nowUnixMs, int durationSeconds) { _durationSeconds = Math.Max(0, durationSeconds); _startedAtUnixMs = ((_durationSeconds > 0) ? nowUnixMs : 0); _active = _durationSeconds > 0; } public int? RemainingSeconds(long nowUnixMs) { if (!Active) { return null; } long num = Math.Max(0L, nowUnixMs - _startedAtUnixMs); long val = (Math.Max(0L, (long)_durationSeconds * 1000L - num) + 999) / 1000; return (int)Math.Min(_durationSeconds, val); } public void Clear() { _durationSeconds = 0; _startedAtUnixMs = 0L; _active = false; } } public enum DigestKind : byte { MatchStart, MeetingConvened, Executed, NoExecution, CurseStarted, CurseFollow, Death, BombDetonated, Checkmate, MatchEnd, ExtractionDone, PerkUnlocked, InformantEstablished, FinalBalance } public sealed class DigestEntry { public DigestKind Kind { get; } public int AtSec { get; } public int Actor { get; } public int ArgA { get; } public int ArgB { get; } public DigestEntry(DigestKind kind, int atSec, int actor, int argA, int argB) { Kind = kind; AtSec = atSec; Actor = actor; ArgA = argA; ArgB = argB; } } public sealed class ResultDigest { public const int MaxEntries = 400; public const int ReasonUnknown = 255; private const byte BombDetonationCode = 180; private readonly List _entries = new List(); private long _startUnixMs; private bool _started; public IReadOnlyList Entries => _entries; public bool Started => _started; public void Observe(byte code, object[] payload, long nowUnixMs, WinResult winner = null) { if (code == 170) { _entries.Clear(); _startUnixMs = nowUnixMs; _started = true; Add(DigestKind.MatchStart, nowUnixMs, 0, 0, 0); } else { if (!_started || payload == null) { return; } switch (code) { case 163: if (payload.Length >= 4 && payload[0] is int actor2 && payload[3] is byte argA2) { Add(DigestKind.MeetingConvened, nowUnixMs, actor2, argA2, 0); } break; case 166: if (payload.Length >= 1 && payload[0] is int num) { if (num != -1) { Add(DigestKind.Executed, nowUnixMs, num, 0, 0); } else { Add(DigestKind.NoExecution, nowUnixMs, 0, 0, 0); } } break; case 168: if (payload.Length >= 2 && payload[0] is int actor3 && payload[1] is byte b2 && b2 == 1) { Add(DigestKind.Death, nowUnixMs, actor3, 0, 0); } break; case 175: if (payload.Length >= 2 && payload[0] is byte b && payload[1] is int[] array && array != null && array.Length >= 1) { switch (b) { case 0: Add(DigestKind.CurseStarted, nowUnixMs, array[0], 0, 0); break; case 1: Add(DigestKind.CurseFollow, nowUnixMs, array[0], 0, 0); break; } } break; case 180: if (payload.Length >= 1 && payload[0] is int actor) { Add(DigestKind.BombDetonated, nowUnixMs, actor, 0, 0); } break; case 187: Add(DigestKind.Checkmate, nowUnixMs, 0, 0, 0); break; case 169: if (payload.Length >= 1 && payload[0] is byte argA) { int argB = (int)(winner?.Reason ?? ((WinReason)255)); Add(DigestKind.MatchEnd, nowUnixMs, 0, argA, argB, force: true); } break; } } } public void RecordExtractionDone(int completed, int total, long nowUnixMs) { if (_started) { Add(DigestKind.ExtractionDone, nowUnixMs, 0, completed, total); } } public void RecordPerkUnlocked(byte perkId, long nowUnixMs) { if (_started) { Add(DigestKind.PerkUnlocked, nowUnixMs, 0, perkId, 0); } } public void RecordInformant(long nowUnixMs) { if (_started) { Add(DigestKind.InformantEstablished, nowUnixMs, 0, 0, 0); } } public void RecordFinalBalance(int deliveredDollars, int remainingQuotaDollars, int obtainableDollars, long nowUnixMs) { if (_started) { Add(DigestKind.FinalBalance, nowUnixMs, obtainableDollars, deliveredDollars, remainingQuotaDollars, force: true); } } public object[] ToWire() { int count = _entries.Count; byte[] array = new byte[count]; int[] array2 = new int[count]; int[] array3 = new int[count]; int[] array4 = new int[count]; int[] array5 = new int[count]; for (int i = 0; i < count; i++) { DigestEntry digestEntry = _entries[i]; array[i] = (byte)digestEntry.Kind; array2[i] = digestEntry.AtSec; array3[i] = digestEntry.Actor; array4[i] = digestEntry.ArgA; array5[i] = digestEntry.ArgB; } return new object[5] { array, array2, array3, array4, array5 }; } public static IReadOnlyList FromWire(object[] payload) { if (payload == null || payload.Length < 5) { return null; } if (!(payload[0] is byte[] array) || !(payload[1] is int[] array2) || !(payload[2] is int[] array3) || !(payload[3] is int[] array4) || !(payload[4] is int[] array5)) { return null; } int num = array.Length; if (array2.Length != num || array3.Length != num || array4.Length != num || array5.Length != num) { return null; } List list = new List(num); for (int i = 0; i < num; i++) { list.Add(new DigestEntry((DigestKind)array[i], array2[i], array3[i], array4[i], array5[i])); } return list; } private void Add(DigestKind kind, long nowUnixMs, int actor, int argA, int argB, bool force = false) { if (force || _entries.Count < 400) { long num = (nowUnixMs - _startUnixMs) / 1000; if (num < 0) { num = 0L; } _entries.Add(new DigestEntry(kind, (int)num, actor, argA, argB)); } } } public static class ResultDigestText { public static List FormatLines(IReadOnlyList entries, Func resolveName) { List list = new List(); if (entries == null) { return list; } foreach (DigestEntry entry in entries) { if (entry != null) { string text = FormatBody(entry, resolveName); if (text != null) { list.Add(FormatTime(entry.AtSec) + " " + text); } } } return list; } public static string FormatTime(int atSec) { if (atSec < 0) { atSec = 0; } int num = atSec / 3600; int num2 = atSec % 3600 / 60; int num3 = atSec % 60; if (num > 0) { return num + ":" + num2.ToString("00") + ":" + num3.ToString("00"); } return num2.ToString("00") + ":" + num3.ToString("00"); } private static string FormatBody(DigestEntry e, Func resolveName) { return e.Kind switch { DigestKind.MatchStart => Texts.Get(TextId.DigestMatchStart), DigestKind.MeetingConvened => Texts.Format((e.ArgA == 1) ? TextId.DigestMeetingReportFormat : TextId.DigestMeetingButtonFormat, Name(e.Actor, resolveName)), DigestKind.Executed => Texts.Format(TextId.DigestExecutedFormat, Name(e.Actor, resolveName)), DigestKind.NoExecution => Texts.Get(TextId.DigestNoExecution), DigestKind.CurseStarted => Texts.Format(TextId.DigestCurseStartedFormat, Name(e.Actor, resolveName)), DigestKind.CurseFollow => Texts.Format(TextId.DigestCurseFollowFormat, Name(e.Actor, resolveName)), DigestKind.Death => Texts.Format(TextId.DigestDeathFormat, Name(e.Actor, resolveName)), DigestKind.BombDetonated => Texts.Format(TextId.DigestBombDetonatedFormat, Name(e.Actor, resolveName)), DigestKind.Checkmate => Texts.Get(TextId.DigestCheckmate), DigestKind.MatchEnd => FormatMatchEnd(e), DigestKind.ExtractionDone => Texts.Format(TextId.DigestExtractionDoneFormat, e.ArgA, e.ArgB), DigestKind.PerkUnlocked => Texts.Format(TextId.DigestPerkUnlockedFormat, PerkLabel(e.ArgA)), DigestKind.InformantEstablished => Texts.Get(TextId.DigestInformant), DigestKind.FinalBalance => Texts.Format(TextId.DigestFinalBalanceFormat, e.ArgA, e.ArgB, e.Actor), _ => null, }; } private static string PerkLabel(int perkId) { return (PerkId)(byte)perkId switch { PerkId.InfiniteStamina => Texts.Get(TextId.GaugePerkStaminaLabel), PerkId.InfiniteJump => Texts.Get(TextId.GaugePerkJumpLabel), PerkId.EnemyIgnore => Texts.Get(TextId.GaugePerkEnemyIgnoreLabel), PerkId.NaturalHeal => Texts.Get(TextId.GaugePerkHealLabel), _ => "?", }; } private static string FormatMatchEnd(DigestEntry e) { string text = (Team)(byte)e.ArgA switch { Team.Villagers => Texts.Get(TextId.ResultBannerVillagerWin), Team.Werewolves => Texts.Get(TextId.ResultBannerWerewolfWin), _ => Texts.Get(TextId.ResultBannerDefault), }; string text2 = ReasonText(e.ArgB); if (text2 != null) { return Texts.Format(TextId.DigestMatchEndFormat, text, text2); } return text; } private static string ReasonText(int reason) { return reason switch { 0 => Texts.Get(TextId.DigestReasonWerewolvesEradicated), 1 => Texts.Get(TextId.DigestReasonVillagersEradicated), 2 => Texts.Get(TextId.DigestReasonExtractionCompleted), 3 => Texts.Get(TextId.DigestReasonTimerExpired), 4 => Texts.Get(TextId.DigestReasonExtractionFailed), 5 => Texts.Get(TextId.DigestReasonValueCheckmate), _ => null, }; } private static string Name(int actor, Func resolveName) { string text = resolveName?.Invoke(actor); if (!string.IsNullOrEmpty(text)) { return text; } return "Actor" + actor; } } public enum ResultRowStatus : byte { Alive, Dead, Executed, Disconnected } public static class ResultModel { public static IReadOnlyList Build(byte winningTeam, int[] actors, byte[] roles, IReadOnlyDictionary deathMirror, Func resolveName, IReadOnlyCollection disconnectedActors = null) { if (actors == null) { throw new ArgumentNullException("actors"); } if (roles == null) { throw new ArgumentNullException("roles"); } if (resolveName == null) { throw new ArgumentNullException("resolveName"); } if (actors.Length != roles.Length) { throw new ArgumentException("actors と roles の長さが一致しません(169 payload破損)。", "roles"); } Team team = (Team)winningTeam; List list = new List(actors.Length); for (int i = 0; i < actors.Length; i++) { int num = actors[i]; Role role = (Role)roles[i]; ResultRowStatus status = ResultRowStatus.Alive; if (deathMirror != null && deathMirror.TryGetValue(num, out var value)) { status = ((value != DeathCause.Vote) ? ResultRowStatus.Dead : ResultRowStatus.Executed); } else if (disconnectedActors != null && disconnectedActors.Contains(num)) { status = ResultRowStatus.Disconnected; } bool isWinningSide = RoleDistribution.TeamOf(role) == team; string text = resolveName(num); if (string.IsNullOrEmpty(text)) { text = "Actor" + num; } list.Add(new ResultRow(num, text, role, status, isWinningSide)); } return list; } } public sealed class ResultRow { public int ActorNumber { get; } public string Name { get; } public Role Role { get; } public ResultRowStatus Status { get; } public bool Alive => Status == ResultRowStatus.Alive; public bool Executed => Status == ResultRowStatus.Executed; public bool IsWinningSide { get; } public ResultRow(int actorNumber, string name, Role role, ResultRowStatus status, bool isWinningSide) { ActorNumber = actorNumber; Name = name; Role = role; Status = status; IsWinningSide = isWinningSide; } } public sealed class ResultSequence { private bool _active; private bool _fired; private bool _returnRequested; private bool _autoEnabled; private long _autoReturnAtUnixMs; public bool Active => _active; public void Begin(long nowUnixMs, int autoReturnSeconds) { _active = true; _fired = false; _returnRequested = false; _autoEnabled = autoReturnSeconds > 0; _autoReturnAtUnixMs = (_autoEnabled ? (nowUnixMs + (long)autoReturnSeconds * 1000L) : 0); } public void RequestReturn() { if (_active && !_fired) { _returnRequested = true; } } public bool TickShouldReturn(long nowUnixMs) { if (!_active || _fired) { return false; } if (!_returnRequested && (!_autoEnabled || nowUnixMs < _autoReturnAtUnixMs)) { return false; } _fired = true; _active = false; return true; } } public enum RoleIcon : byte { Villager, Werewolf, BlackCat, Bomber, Shaman } public sealed class RevealPage { public string[] BodyLines; public float HoldSec; } public sealed class RevealContent { public string Title; public RoleIcon Icon; public RevealPage[] Pages; public float FadeInSec; public float FadeOutSec; } public static class RevealScript { private const int MaxLineLength = 20; private const string TeammateSeparator = ","; private const string Bullet = "・"; private const float DefaultFadeInSec = 1f; private const float DefaultFadeOutSec = 1f; private const float TeamPageHoldSec = 5f; private const float AbilityPageHoldSec = 5f; private const float SinglePageHoldSec = 5f; private static string TeammatePrefix => Texts.Get(TextId.RevealTeammatePrefix); public static RevealContent Build(Role selfRole, IReadOnlyList teammateNames, bool blackCatPossible, ValuableMapMode valuableMapMode, bool blackCatCurseEnabled = true) { return selfRole switch { Role.Villager => BuildVillager(blackCatPossible, valuableMapMode), Role.Werewolf => BuildWolfTeam(Texts.Get(TextId.RevealWerewolfTitle), RoleIcon.Werewolf, teammateNames, TextId.RevealWolfAbility1, TextId.RevealWolfAbility2, TextId.RevealWolfAbility3), Role.BlackCat => BuildBlackCat(blackCatCurseEnabled), Role.Bomber => BuildWolfTeam(Texts.Get(TextId.RevealBomberTitle), RoleIcon.Bomber, teammateNames, TextId.RevealBomberAbility1, TextId.RevealBomberAbility2, TextId.RevealBomberAbility3), Role.Shaman => BuildShaman(), _ => throw new ArgumentOutOfRangeException("selfRole", selfRole, "未知の役職です。"), }; } private static List WolfWinConditionLines() { return new List { Texts.Get(TextId.RevealHeadingWinCondition), "・" + Texts.Get(TextId.RevealWolfTeamWinCondition1), "・" + Texts.Get(TextId.RevealWolfTeamWinCondition2), "・" + Texts.Get(TextId.RevealWolfTeamWinCondition3) }; } private static RevealContent BuildVillager(bool blackCatPossible, ValuableMapMode valuableMapMode) { TextId id = ((valuableMapMode == ValuableMapMode.MeetingSync) ? TextId.RevealVillagerTipValuableMap : TextId.RevealVillagerTipAliveCheck); RevealContent revealContent = new RevealContent(); revealContent.Title = (blackCatPossible ? Texts.Get(TextId.RevealVillagerTitleMaybeCat) : Texts.Get(TextId.RevealVillagerTitle)); revealContent.Icon = RoleIcon.Villager; revealContent.Pages = new RevealPage[2] { new RevealPage { BodyLines = new string[3] { Texts.Get(TextId.RevealHeadingWinCondition), "・" + Texts.Get(TextId.RevealVillagerWinCondition1), "・" + Texts.Get(TextId.RevealVillagerWinCondition2) }, HoldSec = 5f }, new RevealPage { BodyLines = new string[6] { Texts.Get(TextId.RevealHeadingTips), "・" + Texts.Get(TextId.RevealVillagerTipConvene), "・" + Texts.Get(TextId.RevealVillagerTipReport), "・" + Texts.Get(id), string.Empty, Texts.Get(TextId.RevealVillagerFlavor) }, HoldSec = 5f } }; revealContent.FadeInSec = 1f; revealContent.FadeOutSec = 1f; return revealContent; } private static RevealContent BuildWolfTeam(string title, RoleIcon icon, IReadOnlyList teammateNames, TextId ability1, TextId ability2, TextId ability3) { List list = new List(); list.AddRange(WrapTeammateLines(teammateNames)); list.AddRange(WolfWinConditionLines()); RevealContent revealContent = new RevealContent(); revealContent.Title = title; revealContent.Icon = icon; revealContent.Pages = new RevealPage[2] { new RevealPage { BodyLines = list.ToArray(), HoldSec = 5f }, new RevealPage { BodyLines = new string[4] { Texts.Get(TextId.RevealHeadingAbility), "・" + Texts.Get(ability1), "・" + Texts.Get(ability2), "・" + Texts.Get(ability3) }, HoldSec = 5f } }; revealContent.FadeInSec = 1f; revealContent.FadeOutSec = 1f; return revealContent; } private static RevealContent BuildShaman() { RevealContent revealContent = new RevealContent(); revealContent.Title = Texts.Get(TextId.RevealShamanTitle); revealContent.Icon = RoleIcon.Shaman; revealContent.Pages = new RevealPage[2] { new RevealPage { BodyLines = new string[3] { Texts.Get(TextId.RevealHeadingWinCondition), "・" + Texts.Get(TextId.RevealVillagerWinCondition1), "・" + Texts.Get(TextId.RevealVillagerWinCondition2) }, HoldSec = 5f }, new RevealPage { BodyLines = new string[3] { Texts.Get(TextId.RevealHeadingAbility), "・" + Texts.Get(TextId.RevealShamanAbility1), "・" + Texts.Get(TextId.RevealShamanAbility2) }, HoldSec = 5f } }; revealContent.FadeInSec = 1f; revealContent.FadeOutSec = 1f; return revealContent; } public static RevealContent BuildBlackCatAwakening(bool blackCatCurseEnabled = true) { RevealContent revealContent = new RevealContent(); revealContent.Title = Texts.Get(TextId.RevealBlackCatAwakeningTitle); revealContent.Icon = RoleIcon.BlackCat; revealContent.Pages = new RevealPage[1] { new RevealPage { BodyLines = new string[1] { Texts.Get(blackCatCurseEnabled ? TextId.RevealBlackCatAbility : TextId.RevealBlackCatNoCurse) }, HoldSec = 5f } }; revealContent.FadeInSec = 1f; revealContent.FadeOutSec = 1f; return revealContent; } private static RevealContent BuildBlackCat(bool blackCatCurseEnabled) { RevealContent revealContent = new RevealContent(); revealContent.Title = Texts.Get(TextId.RevealBlackCatTitle); revealContent.Icon = RoleIcon.BlackCat; revealContent.Pages = new RevealPage[2] { new RevealPage { BodyLines = WolfWinConditionLines().ToArray(), HoldSec = 5f }, new RevealPage { BodyLines = new string[2] { Texts.Get(TextId.RevealHeadingAbility), "・" + Texts.Get(blackCatCurseEnabled ? TextId.RevealBlackCatAbility : TextId.RevealBlackCatNoCurse) }, HoldSec = 5f } }; revealContent.FadeInSec = 1f; revealContent.FadeOutSec = 1f; return revealContent; } private static List WrapTeammateLines(IReadOnlyList teammateNames) { List list = new List(); StringBuilder stringBuilder = new StringBuilder(TeammatePrefix); bool flag = false; if (teammateNames != null) { foreach (string teammateName in teammateNames) { if (!string.IsNullOrEmpty(teammateName)) { string text = (flag ? ("," + teammateName) : teammateName); if (flag && stringBuilder.Length + text.Length > 20) { list.Add(stringBuilder.ToString()); stringBuilder = new StringBuilder(teammateName); flag = true; } else { stringBuilder.Append(text); flag = true; } } } } list.Add(stringBuilder.ToString()); return list; } } public sealed class RewarpGate { private readonly long _farSustainMs; private readonly long _cooldownMs; private readonly Dictionary _farSince = new Dictionary(); private readonly Dictionary _lastFire = new Dictionary(); public RewarpGate(long farSustainMs, long cooldownMs) { _farSustainMs = farSustainMs; _cooldownMs = cooldownMs; } public bool Tick(int key, bool isFar, long nowMs) { if (!isFar) { _farSince.Remove(key); return false; } if (!_farSince.TryGetValue(key, out var value)) { value = nowMs; _farSince[key] = value; } if (nowMs - value < _farSustainMs) { return false; } if (_lastFire.TryGetValue(key, out var value2) && nowMs - value2 < _cooldownMs) { return false; } _lastFire[key] = nowMs; _farSince.Remove(key); return true; } public void Reset() { _farSince.Clear(); _lastFire.Clear(); } } public enum Role : byte { Villager, Werewolf, BlackCat, Bomber, Shaman } public enum Team : byte { Villagers, Werewolves } public static class RoleDistribution { public static Team TeamOf(Role role) { switch (role) { case Role.Villager: case Role.Shaman: return Team.Villagers; case Role.Werewolf: case Role.BlackCat: case Role.Bomber: return Team.Werewolves; default: throw new ArgumentOutOfRangeException("role", role, "未知の役職です。"); } } } public readonly struct RoleAssignResult { public int Werewolves { get; } public int BlackCats { get; } public int Bombers { get; } public int Shamans { get; } public bool Corrected { get; } public RoleAssignResult(int werewolves, int blackCats, int bombers, int shamans, bool corrected) { Werewolves = werewolves; BlackCats = blackCats; Bombers = bombers; Shamans = shamans; Corrected = corrected; } } public static class RoleAssigner { public static int CorrectedWerewolfSlots(GameConfig config, int playerCount) { if (config == null) { throw new ArgumentNullException("config"); } return CorrectWerewolfSlots(config, playerCount).N; } private static (int N, bool Corrected) CorrectWerewolfSlots(GameConfig config, int playerCount) { bool item = false; int num = config.WerewolfCount; if (num >= playerCount) { num = playerCount - 1; item = true; } if (num < 1) { num = 1; item = true; } return (N: num, Corrected: item); } public static RoleAssignResult Assign(IReadOnlyList players, GameConfig config, Random rng, IReadOnlyDictionary forcedRoles = null) { if (players == null) { throw new ArgumentNullException("players"); } if (config == null) { throw new ArgumentNullException("config"); } if (rng == null) { throw new ArgumentNullException("rng"); } if (players.Count < 3) { throw new ArgumentOutOfRangeException("players", players.Count, "役職割当は3人以上でのみ定義される(Req 1.4)。"); } (int N, bool Corrected) tuple = CorrectWerewolfSlots(config, players.Count); int item = tuple.N; bool item2 = tuple.Corrected; bool flag = false; bool flag2 = false; bool flag3 = false; if (forcedRoles != null) { foreach (KeyValuePair forcedRole in forcedRoles) { if (forcedRole.Value == Role.Bomber) { flag = true; } else if (forcedRole.Value == Role.BlackCat) { flag2 = true; } else if (forcedRole.Value == Role.Shaman) { flag3 = true; } } } int num = 0; if (flag) { num = 1; } else if (item >= 2 && config.BomberChancePercent > 0) { int bomberChancePercent = config.BomberChancePercent; if (bomberChancePercent >= 100 || rng.Next(100) < bomberChancePercent) { num = 1; } } int num2 = item - num; if (num2 < 0) { num2 = 0; } List list = new List(players.Count); int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; foreach (WPlayer player in players) { if (forcedRoles != null && forcedRoles.TryGetValue(player.ActorNumber, out var value)) { player.Role = value; switch (value) { case Role.Werewolf: num3++; break; case Role.BlackCat: num4++; break; case Role.Bomber: num5++; break; case Role.Shaman: num6++; break; } } else { player.Role = Role.Villager; list.Add(player); } } int num7 = Math.Max(0, num2 - num3); int num8 = Math.Max(0, num - num5); Shuffle(list, rng); int num9 = 0; for (int i = 0; i < num7; i++) { if (num9 >= list.Count) { break; } list[num9++].Role = Role.Werewolf; } for (int j = 0; j < num8; j++) { if (num9 >= list.Count) { break; } list[num9++].Role = Role.Bomber; } int num10 = 0; if (!flag2) { int blackCatChancePercent = config.BlackCatChancePercent; if (blackCatChancePercent > 0 && players.Count - item >= 2 && (blackCatChancePercent >= 100 || rng.Next(100) < blackCatChancePercent)) { num10 = 1; } } for (int k = 0; k < num10; k++) { if (num9 >= list.Count) { break; } list[num9++].Role = Role.BlackCat; } int num11 = 0; if (!flag3) { int shamanChancePercent = config.ShamanChancePercent; if (shamanChancePercent > 0 && (shamanChancePercent >= 100 || rng.Next(100) < shamanChancePercent)) { num11 = 1; } } for (int l = 0; l < num11; l++) { if (num9 >= list.Count) { break; } list[num9++].Role = Role.Shaman; } int num12 = 0; int num13 = 0; int num14 = 0; int num15 = 0; using (IEnumerator enumerator2 = players.GetEnumerator()) { while (enumerator2.MoveNext()) { switch (enumerator2.Current.Role) { case Role.Werewolf: num12++; break; case Role.BlackCat: num13++; break; case Role.Bomber: num14++; break; case Role.Shaman: num15++; break; } } } if (item2) { WLog.Line("warn", false, ("reason", "abnormal_config"), ("players", players.Count), ("n", item)); } WLog.Line("assign", true, ("players", players.Count), ("werewolves", num12), ("blackcats", num13), ("bombers", num14), ("shamans", num15), ("corrected", item2)); return new RoleAssignResult(num12, num13, num14, num15, item2); } private static void Shuffle(List list, Random rng) { for (int num = list.Count - 1; num > 0; num--) { int num2 = rng.Next(num + 1); int index = num; int index2 = num2; WPlayer value = list[num2]; WPlayer value2 = list[num]; list[index] = value; list[index2] = value2; } } } public sealed class RolesClientState { public int RatioPermille { get; private set; } public PerkFlags UnlockedFlags { get; private set; } public int BeaconCharges { get; private set; } public BeaconStatus LastBeaconStatus { get; private set; } public long BeaconReadyUnixMs { get; private set; } public bool GaugeReceived { get; private set; } public long GaugeNextUpdateUnixMs { get; private set; } public int CurseCatActor { get; private set; } = int.MinValue; public long CurseDeadlineUnixMs { get; private set; } public int[] CurseCandidates { get; private set; } public MeetingGaugeSnapshot MeetingGauge { get; private set; } public MeetingGaugeSnapshot PlayGauge { get; private set; } public bool WolfMode { get; private set; } public bool StaminaActive { get { if (WolfMode) { return PerkFlagsUtil.Has(UnlockedFlags, PerkId.InfiniteStamina); } return false; } } public bool JumpActive { get { if (WolfMode) { return PerkFlagsUtil.Has(UnlockedFlags, PerkId.InfiniteJump); } return false; } } public bool EnemyIgnoreActive { get { if (WolfMode) { return PerkFlagsUtil.Has(UnlockedFlags, PerkId.EnemyIgnore); } return false; } } public bool HealActive { get { if (WolfMode) { return PerkFlagsUtil.Has(UnlockedFlags, PerkId.NaturalHeal); } return false; } } public bool ValuableRecordOn { get; private set; } public bool TryToggleWolfMode(Role? localRole) { if (localRole != Role.Werewolf) { return false; } if (UnlockedFlags == PerkFlags.None) { return false; } WolfMode = !WolfMode; WLog.Line("wolfmode_toggle", true, ("on", WolfMode)); return true; } public bool ForceWolfModeOff() { if (!WolfMode) { return false; } WolfMode = false; WLog.Line("wolfmode_toggle", true, ("on", false), ("via", "reset")); return true; } public bool ToggleValuableRecord(Role? localRole) { if (!ValuableRecordGate.IsWerewolfTeam(localRole)) { return false; } ValuableRecordOn = !ValuableRecordOn; WLog.Line("valuable_record_toggle", true, ("on", ValuableRecordOn)); return true; } public bool ForceValuableRecordOff() { if (!ValuableRecordOn) { return false; } ValuableRecordOn = false; WLog.Line("valuable_record_toggle", true, ("on", false), ("via", "reset")); return true; } public bool CanShowHud(Role? localRole) { if (GaugeReceived) { if (localRole != Role.Werewolf && localRole != Role.BlackCat) { return localRole == Role.Bomber; } return true; } return false; } public void ApplyGaugeSync(int ratioPermille, byte unlockedFlags, byte beaconCharges, byte beaconStatus, long beaconReadyUnixMs, int[] gaugeMeta = null, long nowUnixMs = 0L) { RatioPermille = ratioPermille; UnlockedFlags = (PerkFlags)unlockedFlags; BeaconCharges = beaconCharges; LastBeaconStatus = (BeaconStatus)beaconStatus; BeaconReadyUnixMs = beaconReadyUnixMs; GaugeReceived = true; GaugeNextUpdateUnixMs = ((gaugeMeta != null && gaugeMeta.Length >= 7 && gaugeMeta[6] > 0 && nowUnixMs > 0) ? (nowUnixMs + (long)gaugeMeta[6] * 1000L) : 0); if (gaugeMeta != null && gaugeMeta.Length >= 6) { PlayGauge = new MeetingGaugeSnapshot { RatioPermille = ratioPermille, BaseDollars = gaugeMeta[0], StaminaPct = gaugeMeta[1], JumpPct = gaugeMeta[2], EnemyIgnorePct = gaugeMeta[3], InformantPct = gaugeMeta[4], BeaconChargePct = gaugeMeta[5], LostDollars = ((gaugeMeta.Length >= 8) ? gaugeMeta[7] : (-1)), CheckmateLossDollars = ((gaugeMeta.Length >= 9) ? gaugeMeta[8] : (-1)), HealPct = ((gaugeMeta.Length >= 10) ? gaugeMeta[9] : 0) }; } } public void ApplyCurseStarted(int catActor, long deadlineUnixMs) { CurseCatActor = catActor; CurseDeadlineUnixMs = deadlineUnixMs; } public void ApplyCurseCandidates(int[] voterActors) { CurseCandidates = voterActors; } public void ApplyCurseResolved() { CurseCatActor = int.MinValue; CurseDeadlineUnixMs = 0L; CurseCandidates = null; } public bool CurseActive(long nowUnixMs) { if (CurseCatActor != int.MinValue) { return nowUnixMs < CurseDeadlineUnixMs; } return false; } public void ApplyMeetingGauge(int[] data) { MeetingGaugeSnapshot meetingGaugeSnapshot = MeetingGaugeSnapshot.FromData(data); if (meetingGaugeSnapshot != null) { MeetingGauge = meetingGaugeSnapshot; } } public void ClearMeetingGauge() { MeetingGauge = null; } public void Reset() { RatioPermille = 0; UnlockedFlags = PerkFlags.None; BeaconCharges = 0; LastBeaconStatus = BeaconStatus.Ok; BeaconReadyUnixMs = 0L; GaugeReceived = false; GaugeNextUpdateUnixMs = 0L; PlayGauge = null; CurseCatActor = int.MinValue; CurseDeadlineUnixMs = 0L; CurseCandidates = null; MeetingGauge = null; WolfMode = false; ValuableRecordOn = false; } } public sealed class MeetingGaugeSnapshot { public int RatioPermille; public int BaseDollars; public int StaminaPct; public int JumpPct; public int EnemyIgnorePct; public int InformantPct; public int BeaconChargePct; public int HealPct; public int LostDollars = -1; public int ExtractedDollars = -1; public int HaulGoalDollars = -1; public int BombRefillPct = -1; public int CheckmateLossDollars = -1; public int DeliveryPermille() { return PermilleOf(ExtractedDollars, BaseDollars); } public int QuotaPermille() { return PermilleOf(HaulGoalDollars, BaseDollars); } public int CheckmateLinePermille() { return PermilleOf(CheckmateLossDollars, BaseDollars); } private static int PermilleOf(int dollars, int baseDollars) { if (dollars < 0 || baseDollars <= 0) { return -1; } long num = (long)dollars * 1000L / baseDollars; if (num <= 1000) { return (int)num; } return 1000; } public static MeetingGaugeSnapshot FromData(int[] data) { if (data == null || data.Length < 7) { return null; } return new MeetingGaugeSnapshot { RatioPermille = data[0], BaseDollars = data[1], StaminaPct = data[2], JumpPct = data[3], EnemyIgnorePct = data[4], InformantPct = data[5], BeaconChargePct = data[6], LostDollars = ((data.Length >= 8) ? data[7] : (-1)), ExtractedDollars = ((data.Length >= 9) ? data[8] : (-1)), HaulGoalDollars = ((data.Length >= 10) ? data[9] : (-1)), BombRefillPct = ((data.Length >= 11) ? data[10] : (-1)), CheckmateLossDollars = ((data.Length >= 12) ? data[11] : (-1)), HealPct = ((data.Length >= 13) ? data[12] : 0) }; } } public sealed class RolesSession { private readonly GameConfig _config; private readonly GameSession _session; private readonly Random _rng; private readonly PerkGauge _gauge; private readonly Dictionary _beacons = new Dictionary(); private readonly Dictionary _wolfMode = new Dictionary(); private bool _meetingGaugeSent; private int _lastSyncedPermille = -1; private readonly bool _catPossible; private readonly bool _bomberPossible; private int _checkmateLineDollars = -1; private long _catNextSyncUnixMs; private int _beaconUsesSinceAudit; public const int CurseKillDelaySec = 2; public CurseSession ActiveCurse { get; private set; } public bool InformantEstablished { get; private set; } public PerkGauge Gauge => _gauge; public event Action OnSend; public event Action OnInformantEstablished; public event Action OnCurseKill; public event Action OnCurseSealed; public event Action OnEnemyIgnoreChanged; public event Action OnPerkUnlocked; public event Action OnBeaconTriggered; public event Action OnGaugePctChanged; public RolesSession(GameConfig config, GameSession session, long nowUnixMs, Random rng) { _config = config ?? throw new ArgumentNullException("config"); _session = session ?? throw new ArgumentNullException("session"); _rng = rng ?? throw new ArgumentNullException("rng"); _gauge = new PerkGauge(config); _catPossible = config.BlackCatPossible(session.Players.Count); _bomberPossible = config.BomberPossible(session.Players.Count); long untilUnixMs = nowUnixMs + (long)config.BeaconSuppressStartSec * 1000L; foreach (WPlayer player in _session.Players) { if (player.Role == Role.Werewolf) { BeaconState beaconState = new BeaconState(config); beaconState.Suppress(untilUnixMs); _beacons[player.ActorNumber] = beaconState; } } } public void FreezeBase(float totalDollars) { bool baseFrozen = _gauge.BaseFrozen; _gauge.FreezeBase(totalDollars); if (!baseFrozen && _gauge.BaseFrozen) { SyncGaugeToTargets(); } } public void AddValueLoss(float lostDollars, bool isOrb) { if (isOrb && !_config.OrbGaugeEnabled) { return; } IReadOnlyList readOnlyList = _gauge.AddLoss(lostDollars); foreach (GaugeEvent item in readOnlyList) { ApplyGaugeEvent(item); } if (readOnlyList.Count > 0 || _gauge.DisplayPermille != _lastSyncedPermille) { SyncGaugeToTargets(); } if (_gauge.BaseFrozen && _gauge.BaseDollars > 0f) { this.OnGaugePctChanged?.Invoke(_gauge.LostDollars / _gauge.BaseDollars * 100f); } } private void ApplyGaugeEvent(GaugeEvent e) { switch (e.Kind) { case GaugeEventKind.PerkUnlocked: if (e.Perk == PerkId.EnemyIgnore) { foreach (KeyValuePair item in _wolfMode) { if (item.Value) { this.OnEnemyIgnoreChanged?.Invoke(item.Key, arg2: true); } } } this.OnPerkUnlocked?.Invoke(e.Perk); break; case GaugeEventKind.BeaconCharged: { foreach (BeaconState value in _beacons.Values) { value.AddCharges(e.BeaconChargeCount); } break; } case GaugeEventKind.InformantReady: FireInformant(); break; } } private void FireInformant() { if (InformantEstablished) { return; } bool flag = false; foreach (WPlayer player in _session.Players) { if (player.Role == Role.BlackCat && player.Alive) { flag = true; break; } } if (!flag) { WLog.Line("informant_skipped", true, ("reason", "cat_dead")); return; } InformantEstablished = true; WLog.Line("informant_established", true, ("permille", _gauge.DisplayPermille)); this.OnInformantEstablished?.Invoke(); _catNextSyncUnixMs = 0L; } public void HandleRoleAction(int senderActor, byte subtype, int arg, byte flag, long nowUnixMs) { switch (subtype) { case 0: HandleCurseDesignate(senderActor, arg, nowUnixMs); return; case 1: HandleBeaconUse(senderActor, nowUnixMs); return; case 2: HandleWolfModeSync(senderActor, flag != 0); return; } WLog.Line("role_action_dropped", true, ("sender", senderActor), ("reason", "unknown_subtype"), ("subtype", subtype)); } private void HandleCurseDesignate(int senderActor, int targetActor, long nowUnixMs) { if (ActiveCurse == null) { WLog.Line("role_action_dropped", true, ("sender", senderActor), ("reason", "no_active_curse")); } else { ActiveCurse.Designate(senderActor, targetActor, nowUnixMs); } } private void HandleBeaconUse(int senderActor, long nowUnixMs) { WPlayer wPlayer = FindPlayer(senderActor); if (wPlayer == null || wPlayer.Role != Role.Werewolf || !wPlayer.Alive || !_beacons.TryGetValue(senderActor, out var value)) { WLog.Line("role_action_dropped", true, ("sender", senderActor), ("reason", "not_alive_werewolf"), ("action", "beacon")); return; } if (_session.Phase == GamePhase.Meeting) { WLog.Line("beacon_use", true, ("actor", senderActor), ("status", BeaconStatus.MeetingActive), ("charges", value.Charges)); SyncGaugeTo(senderActor, BeaconStatus.MeetingActive); return; } BeaconStatus beaconStatus = value.TryUse(nowUnixMs); WLog.Line("beacon_use", true, ("actor", senderActor), ("status", beaconStatus), ("charges", value.Charges)); if (beaconStatus == BeaconStatus.Ok) { this.OnBeaconTriggered?.Invoke(senderActor); _beaconUsesSinceAudit++; } SyncGaugeTo(senderActor, beaconStatus); } private void HandleWolfModeSync(int senderActor, bool on) { WPlayer wPlayer = FindPlayer(senderActor); if (wPlayer == null || wPlayer.Role != Role.Werewolf || !wPlayer.Alive) { WLog.Line("role_action_dropped", true, ("sender", senderActor), ("reason", "not_alive_werewolf"), ("action", "wolfmode")); } else { _wolfMode[senderActor] = on; WLog.Line("wolfmode_sync", true, ("actor", senderActor), ("on", on)); bool arg = on && PerkFlagsUtil.Has(_gauge.UnlockedFlags, PerkId.EnemyIgnore); this.OnEnemyIgnoreChanged?.Invoke(senderActor, arg); } } public void OnMeetingStarted(long nowUnixMs, int extractedDollars = -1, int haulGoalDollars = -1, int checkmateLossDollars = -1) { if (!_meetingGaugeSent) { _meetingGaugeSent = true; int beaconUsesSinceAudit = _beaconUsesSinceAudit; _beaconUsesSinceAudit = 0; Send(new OutboundMessage(167, new object[1] { (byte)((beaconUsesSinceAudit > 255) ? 255u : ((uint)beaconUsesSinceAudit)) }, MessageTarget.All, null)); WLog.Line("beacon_audit_sent", false, ("uses", beaconUsesSinceAudit)); Send(new OutboundMessage(175, new object[3] { (byte)2, BuildMeetingGaugeData(extractedDollars, haulGoalDollars, checkmateLossDollars), 0L }, MessageTarget.All, null)); WLog.Line("meeting_gauge_sent", false, ("permille", _gauge.DisplayPermille)); ForceCatSync(nowUnixMs); } } public int[] BuildMeetingGaugeData(int extractedDollars, int haulGoalDollars, int checkmateLossDollars = -1) { return new int[13] { _gauge.DisplayPermille, (int)_gauge.BaseDollars, _config.StaminaUnlockPct, _config.JumpUnlockPct, _config.EnemyIgnoreUnlockPct, _catPossible ? _config.InformantThresholdPct : 0, _config.BeaconChargePct, (int)(_gauge.LostDollars + 0.5f), extractedDollars, haulGoalDollars, _bomberPossible ? _config.BomberAmmoRefillPct : 0, checkmateLossDollars, _config.HealUnlockPct }; } public void OnMeetingEnded(long nowUnixMs) { _meetingGaugeSent = false; long untilUnixMs = nowUnixMs + (long)_config.BeaconSuppressAfterMeetingSec * 1000L; foreach (BeaconState value in _beacons.Values) { value.Suppress(untilUnixMs); } SyncGaugeToTargets(); ForceCatSync(nowUnixMs); } public bool TryStartCurse(int executedActor, long nowUnixMs, out long holdExtensionMs, int[] voterActors = null) { holdExtensionMs = 0L; if (!_config.BlackCatCurseEnabled) { return false; } WPlayer wPlayer = FindPlayer(executedActor); if (wPlayer == null || wPlayer.Role != Role.BlackCat) { return false; } if (ActiveCurse != null && !ActiveCurse.Resolved) { return false; } long num = nowUnixMs + (long)_config.CurseWaitSec * 1000L; ActiveCurse = new CurseSession(executedActor, num, voterActors); holdExtensionMs = (long)(_config.CurseWaitSec + 2 - 5) * 1000L; if (voterActors != null && !wPlayer.IsBot) { Send(new OutboundMessage(179, new object[1] { voterActors }, MessageTarget.Actors, new int[1] { executedActor })); WLog.Line("curse_candidates_sent", true, ("catActor", executedActor), ("count", voterActors.Length)); } Send(new OutboundMessage(175, new object[3] { (byte)0, new int[1] { executedActor }, num }, MessageTarget.All, null)); WLog.Line("curse_started", false, ("catActor", executedActor), ("resolveAt", num), ("restricted", voterActors != null)); return true; } public void OnPlayerDied(int actorNumber) { if (_wolfMode.TryGetValue(actorNumber, out var value) && value) { _wolfMode[actorNumber] = false; this.OnEnemyIgnoreChanged?.Invoke(actorNumber, arg2: false); } } public void Tick(long nowUnixMs) { TickCatGaugeSync(nowUnixMs); CurseSession activeCurse = ActiveCurse; if (activeCurse == null || activeCurse.Resolved) { return; } CurseResolution curseResolution = activeCurse.TryResolve(nowUnixMs, _session.Players, InformantEstablished, _rng); if (curseResolution != null) { WLog.Line("curse_resolved", true, ("victim", curseResolution.VictimActor), ("designated", curseResolution.WasDesignated)); this.OnCurseSealed?.Invoke(activeCurse.CatActor); if (curseResolution.HasVictim) { this.OnCurseKill?.Invoke(curseResolution.VictimActor); } Send(new OutboundMessage(175, new object[3] { (byte)1, new int[1] { curseResolution.VictimActor }, 0L }, MessageTarget.All, null)); } } public void DebugAddGaugePct(int pct) { if (!_gauge.BaseFrozen) { FreezeBase(10000f); } AddValueLoss(_gauge.BaseDollars * (float)pct / 100f, isOrb: false); } public void DebugChargeBeacon(int count) { foreach (BeaconState value in _beacons.Values) { value.AddCharges(count); } SyncGaugeToTargets(); } public void DebugUnlockPerk(PerkId perk) { if (_gauge.DebugForceUnlock(perk)) { ApplyGaugeEvent(GaugeEvent.Unlocked(perk)); SyncGaugeToTargets(); } } public void DebugUseBeacon(int actorNumber, long nowUnixMs) { if (_beacons.TryGetValue(actorNumber, out var value)) { value.DebugClearRestrictions(); } HandleRoleAction(actorNumber, 1, 0, 0, nowUnixMs); } public void DebugForceInformant() { if (_gauge.DebugForceInformant()) { FireInformant(); SyncGaugeToTargets(); } } public void UpdateCheckmateLine(int lineDollars) { if (lineDollars != _checkmateLineDollars) { _checkmateLineDollars = lineDollars; SyncGaugeToTargets(); WLog.Line("checkmate_line_sync", false, ("line", lineDollars)); } } private void SyncGaugeToTargets() { _lastSyncedPermille = _gauge.DisplayPermille; bool flag = _config.CatGaugeSyncIntervalSec <= 0; bool blackCatSelfAwarenessIssued = _session.BlackCatSelfAwarenessIssued; foreach (WPlayer player in _session.Players) { if (!player.IsBot) { if (player.Role == Role.Werewolf || player.Role == Role.Bomber) { SyncGaugeTo(player.ActorNumber, BeaconStatus.Ok); } else if (player.Role == Role.BlackCat && blackCatSelfAwarenessIssued && flag) { SyncGaugeTo(player.ActorNumber, BeaconStatus.Ok); } } } } private void TickCatGaugeSync(long nowUnixMs) { if (_config.CatGaugeSyncIntervalSec > 0 && _session.Phase == GamePhase.Play && _session.BlackCatSelfAwarenessIssued && nowUnixMs >= _catNextSyncUnixMs) { SyncCatNow(nowUnixMs); } } private void ForceCatSync(long nowUnixMs) { if (_config.CatGaugeSyncIntervalSec > 0 && _session.BlackCatSelfAwarenessIssued) { SyncCatNow(nowUnixMs); } } private void SyncCatNow(long nowUnixMs) { _catNextSyncUnixMs = nowUnixMs + (long)_config.CatGaugeSyncIntervalSec * 1000L; foreach (WPlayer player in _session.Players) { if (!player.IsBot && player.Role == Role.BlackCat) { SyncGaugeTo(player.ActorNumber, BeaconStatus.Ok, _config.CatGaugeSyncIntervalSec); } } } private void SyncGaugeTo(int actorNumber, BeaconStatus status, int nextUpdateInSec = 0) { byte b = 0; long num = 0L; if (_beacons.TryGetValue(actorNumber, out var value)) { b = (byte)((value.Charges > 255) ? 255u : ((uint)value.Charges)); num = value.ReadyUnixMs; } Send(new OutboundMessage(171, new object[6] { _gauge.DisplayPermille, (byte)_gauge.UnlockedFlags, b, (byte)status, num, new int[10] { (int)_gauge.BaseDollars, _config.StaminaUnlockPct, _config.JumpUnlockPct, _config.EnemyIgnoreUnlockPct, _catPossible ? _config.InformantThresholdPct : 0, _config.BeaconChargePct, nextUpdateInSec, (int)(_gauge.LostDollars + 0.5f), _checkmateLineDollars, _config.HealUnlockPct } }, MessageTarget.Actors, new int[1] { actorNumber })); } private WPlayer FindPlayer(int actorNumber) { foreach (WPlayer player in _session.Players) { if (player.ActorNumber == actorNumber) { return player; } } return null; } private void Send(OutboundMessage message) { this.OnSend?.Invoke(message); } } public static class RoomStateKeys { public static class BombIndex { public const int BomberPossible = 0; public const int ProximityCm = 1; public const int GaugeFullMs = 2; public const int CooldownMs = 3; public const int BlastRadiusCm = 4; public const int BlastPlayerDamage = 5; public const int BlastEnemyDamage = 6; public const int InitialCooldownMs = 7; public const int Length = 8; } public static class ShamanIndex { public const int GazeFullMs = 0; public const int GhostCooldownMs = 1; public const int StormWeakCm = 2; public const int StormMediumCm = 3; public const int StormStrongCm = 4; public const int Length = 5; } public const string Phase = "WW_Phase"; public const string RoundEndTime = "WW_RoundEndTime"; public const string IsAlive = "WW_IsAlive"; public const string MeetingCaller = "WW_MeetingCaller"; public const string MeetingEndTime = "WW_MeetingEndTime"; public const string CfgMinimapHide = "WW_CfgMinimapHide"; public const string CfgCatPossible = "WW_CfgCatPossible"; public const string CfgValuableMapMode = "WW_CfgValuableMapMode"; public const string Rights = "WW_Rights"; public const string CfgShared = "WW_CfgShared"; public const string CfgNecroVoiceMode = "WW_CfgNecroVoiceMode"; public const string CfgExtraJump = "WW_CfgExtraJump"; public const string CfgConveneSuppressStart = "WW_CfgConveneSuppressStart"; public const string CfgConveneSuppressAfter = "WW_CfgConveneSuppressAfter"; public const string CfgHealInterval = "WW_CfgHealInterval"; public const string CfgBomb = "WW_CfgBomb"; public const string CfgShaman = "WW_CfgShaman"; public static readonly string[] AllKeys = new string[17] { "WW_Phase", "WW_RoundEndTime", "WW_IsAlive", "WW_MeetingCaller", "WW_MeetingEndTime", "WW_CfgMinimapHide", "WW_CfgCatPossible", "WW_CfgValuableMapMode", "WW_Rights", "WW_CfgShared", "WW_CfgNecroVoiceMode", "WW_CfgExtraJump", "WW_CfgConveneSuppressStart", "WW_CfgConveneSuppressAfter", "WW_CfgHealInterval", "WW_CfgBomb", "WW_CfgShaman" }; public static int[] EncodeBomb(GameConfig config, int playerCount) { return new int[8] { EncodeBool(config.BomberPossible(playerCount)), MetersToCm(config.BomberProximityMeters), SecondsToMs(config.BomberGaugeFullSec), config.BomberCooldownSec * 1000, MetersToCm(config.BomberBlastRadiusMeters), config.BomberBlastPlayerDamage, config.BomberBlastEnemyDamage, config.BomberInitialCooldownSec * 1000 }; } public static int[] EncodeShaman(GameConfig config) { return new int[5] { SecondsToMs(config.ShamanGazeFullSec), SecondsToMs(config.ShamanGhostCooldownSec), MetersToCm(config.ShamanStormWeakMeters), MetersToCm(config.ShamanStormMediumMeters), MetersToCm(config.ShamanStormStrongMeters) }; } private static int MetersToCm(float meters) { if (float.IsNaN(meters) || meters < 0f) { return 0; } return (int)Math.Round(meters * 100f); } private static int SecondsToMs(float seconds) { if (float.IsNaN(seconds) || seconds < 0f) { return 0; } return (int)Math.Round(seconds * 1000f); } public static byte EncodeBool(bool value) { return value ? ((byte)1) : ((byte)0); } public static bool DecodeBool(byte value) { return value != 0; } public static byte EncodeRights(int remaining) { if (remaining < 0) { return 0; } if (remaining > 255) { return byte.MaxValue; } return (byte)remaining; } public static int DecodeRights(byte value) { return value; } public static byte EncodeValuableMapMode(ValuableMapMode mode) { return (byte)mode; } public static ValuableMapMode DecodeValuableMapMode(byte value) { return value switch { 0 => ValuableMapMode.Realtime, 1 => ValuableMapMode.MeetingSync, 2 => ValuableMapMode.Hidden, _ => ValuableMapMode.MeetingSync, }; } public static byte EncodeNecroVoiceMode(NecroVoiceMode mode) { return (byte)mode; } public static NecroVoiceMode DecodeNecroVoiceMode(byte value) { return value switch { 0 => NecroVoiceMode.Off, 1 => NecroVoiceMode.NonWerewolfDead, 2 => NecroVoiceMode.AllDead, _ => NecroVoiceMode.Off, }; } public static byte EncodeExtraJump(int count) { if (count < -1) { count = -1; } if (count > 254) { count = 254; } return (byte)(count + 1); } public static int DecodeExtraJump(byte value) { return value - 1; } } public sealed class RoundTimer { private bool _started; private bool _expired; private bool _paused; private long _meetingStartUnixMs; public long EndUnixMs { get; private set; } public bool IsPaused => _paused; public bool Expired => _expired; public void Start(long nowUnixMs, int roundSeconds) { if (roundSeconds <= 0) { throw new ArgumentOutOfRangeException("roundSeconds", roundSeconds, "ラウンド制限時間は正の秒数であること。"); } EndUnixMs = nowUnixMs + (long)roundSeconds * 1000L; _started = true; _expired = false; _paused = false; } public long RemainingMs(long nowUnixMs) { if (!_started) { return 0L; } long num = (_paused ? _meetingStartUnixMs : nowUnixMs); long num2 = EndUnixMs - num; if (num2 <= 0) { return 0L; } return num2; } public bool CheckExpiry(long nowUnixMs) { if (!_started || _expired || _paused) { return false; } if (nowUnixMs < EndUnixMs) { return false; } _expired = true; return true; } public void PauseForMeeting(long nowUnixMs) { if (_started && !_paused) { _paused = true; _meetingStartUnixMs = nowUnixMs; } } public long ResumeFromMeeting(long nowUnixMs) { if (!_started || !_paused) { return EndUnixMs; } EndUnixMs += nowUnixMs - _meetingStartUnixMs; _paused = false; return EndUnixMs; } public void ForceExpire(long nowUnixMs) { if (_started && !_expired) { EndUnixMs = (_paused ? _meetingStartUnixMs : nowUnixMs); } } } public enum SessionEventKind : byte { PhaseChanged, PlayerDied, WinnerConfirmed } public sealed class SessionEvent { public SessionEventKind Kind { get; } public GamePhase Phase { get; private set; } public long RoundEndUnixMs { get; private set; } public int ActorNumber { get; private set; } public DeathCause DeathCause { get; private set; } public WinResult Winner { get; private set; } private SessionEvent(SessionEventKind kind) { Kind = kind; } public static SessionEvent ForPhaseChanged(GamePhase phase, long roundEndUnixMs) { return new SessionEvent(SessionEventKind.PhaseChanged) { Phase = phase, RoundEndUnixMs = roundEndUnixMs }; } public static SessionEvent ForPlayerDied(int actorNumber, DeathCause cause) { return new SessionEvent(SessionEventKind.PlayerDied) { ActorNumber = actorNumber, DeathCause = cause }; } public static SessionEvent ForWinnerConfirmed(WinResult winner) { return new SessionEvent(SessionEventKind.WinnerConfirmed) { Winner = winner }; } } public enum NoticeKind : byte { ConveneStarted, BeaconAudit, NoExecution, Executed, BlackCatRevealed, CurseVictim, ConveneDenied, CatAwakened, CorpseReportStarted, MeetingCancelled, PlayerDisconnected, ConveneHoldHint } public sealed class SessionNotice { public NoticeKind Kind { get; } public string ActorName { get; private set; } public ConveneRejectReason DenyReason { get; private set; } public int BeaconUseCount { get; private set; } private SessionNotice(NoticeKind kind) { Kind = kind; } public static SessionNotice ForConveneStarted(string callerName) { return new SessionNotice(NoticeKind.ConveneStarted) { ActorName = callerName }; } public static SessionNotice ForBeaconAudit(int useCount) { return new SessionNotice(NoticeKind.BeaconAudit) { BeaconUseCount = useCount }; } public static SessionNotice ForNoExecution() { return new SessionNotice(NoticeKind.NoExecution); } public static SessionNotice ForExecuted(string actorName) { return new SessionNotice(NoticeKind.Executed) { ActorName = actorName }; } public static SessionNotice ForBlackCatRevealed(string actorName) { return new SessionNotice(NoticeKind.BlackCatRevealed) { ActorName = actorName }; } public static SessionNotice ForCurseVictim(string actorName) { return new SessionNotice(NoticeKind.CurseVictim) { ActorName = actorName }; } public static SessionNotice ForConveneDenied(ConveneRejectReason reason) { return new SessionNotice(NoticeKind.ConveneDenied) { DenyReason = reason }; } public static SessionNotice ForCatAwakened() { return new SessionNotice(NoticeKind.CatAwakened); } public static SessionNotice ForCorpseReportStarted(string callerName) { return new SessionNotice(NoticeKind.CorpseReportStarted) { ActorName = callerName }; } public static SessionNotice ForMeetingCancelled() { return new SessionNotice(NoticeKind.MeetingCancelled); } public static SessionNotice ForPlayerDisconnected(string actorName) { return new SessionNotice(NoticeKind.PlayerDisconnected) { ActorName = actorName }; } public static SessionNotice ForConveneHoldHint() { return new SessionNotice(NoticeKind.ConveneHoldHint); } } public static class SettingsCatalog { public const int BlobVersion = 1; private const char VersionSep = '|'; private const char PairSep = ';'; private const char KvSep = '='; private static readonly Func DisplayBoolEnable = (string raw) => (!(raw == "1")) ? Texts.Get(TextId.SettingsBoolDisabled) : Texts.Get(TextId.SettingsBoolEnabled); private static readonly Func DisplayValuableMapMode = (string raw) => raw switch { "0" => Texts.Get(TextId.SettingsValuableMapRealtime), "1" => Texts.Get(TextId.SettingsValuableMapMeetingSync), "2" => Texts.Get(TextId.SettingsValuableMapHidden), _ => raw, }; private static readonly Func DisplayNecroVoiceMode = (string raw) => raw switch { "0" => Texts.Get(TextId.SettingsNecroVoiceOff), "1" => Texts.Get(TextId.SettingsNecroVoiceNonWerewolfDead), "2" => Texts.Get(TextId.SettingsNecroVoiceAllDead), _ => raw, }; private static readonly char[] BlobForbiddenChars = new char[3] { '|', ';', '=' }; private const int ItemNameDisplayCap = 3; private static string SectionGeneral => Texts.Get(TextId.SettingsSectionGeneral); private static string SectionMeeting => Texts.Get(TextId.SettingsSectionMeeting); private static string SectionRoleAssignment => Texts.Get(TextId.SettingsSectionRoleAssignment); private static string SectionRoles => Texts.Get(TextId.SettingsSectionRoles); private static string SectionBlackCat => Texts.Get(TextId.SettingsSectionBlackCat); private static string SectionBomber => Texts.Get(TextId.SettingsSectionBomber); private static string SectionShaman => Texts.Get(TextId.SettingsSectionShaman); private static string SectionWorldgen => Texts.Get(TextId.SettingsSectionWorldgen); private static string SectionStartItemList => Texts.Get(TextId.SettingsSectionStartItemList); private static string SectionStartUpgradeList => Texts.Get(TextId.SettingsSectionStartUpgradeList); public static IReadOnlyList Entries { get; } = BuildEntries(); private static string SanitizeMapNameRaw(string name) { if (string.IsNullOrEmpty(name)) { return ""; } if (name.IndexOfAny(BlobForbiddenChars) < 0) { return name; } return ""; } private static string DisplayItemsAggregate(string raw) { IReadOnlyDictionary readOnlyDictionary = WorldgenSpec.Decode(raw); if (readOnlyDictionary.Count == 0) { return Texts.Get(TextId.SettingsListEmpty); } int num = 0; List list = new List(readOnlyDictionary.Count); foreach (KeyValuePair item in readOnlyDictionary) { num += item.Value; list.Add(item.Key); } list.Sort(StringComparer.Ordinal); StringBuilder stringBuilder = new StringBuilder(); int num2 = ((list.Count < 3) ? list.Count : 3); for (int i = 0; i < num2; i++) { if (i > 0) { stringBuilder.Append(Texts.Get(TextId.SettingsListSeparator)); } stringBuilder.Append(list[i]); } if (list.Count > num2) { stringBuilder.Append(Texts.Get(TextId.SettingsAggregateMoreSuffix)); } return Texts.Format(TextId.SettingsItemsAggregateFormat, readOnlyDictionary.Count.ToString(CultureInfo.InvariantCulture), num.ToString(CultureInfo.InvariantCulture), stringBuilder.ToString()); } private static string DisplayUpgradesAggregate(string raw) { IReadOnlyDictionary readOnlyDictionary = WorldgenSpec.Decode(raw); if (readOnlyDictionary.Count == 0) { return Texts.Get(TextId.SettingsListEmpty); } List list = new List(readOnlyDictionary.Count); foreach (KeyValuePair item in readOnlyDictionary) { list.Add(item.Key); } list.Sort(StringComparer.Ordinal); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < list.Count; i++) { if (i > 0) { stringBuilder.Append(Texts.Get(TextId.SettingsListSeparator)); } stringBuilder.Append(Texts.Format(TextId.SettingsUpgradeItemFormat, list[i], readOnlyDictionary[list[i]].ToString(CultureInfo.InvariantCulture))); } return stringBuilder.ToString(); } private static Func DisplayAutoOr(string unit) { return (string raw) => (!(raw == "-1")) ? (raw + unit) : Texts.Get(TextId.SettingsAuto); } public static string EncodeBlob(GameConfig config) { if (config == null) { throw new ArgumentNullException("config"); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(1.ToString(CultureInfo.InvariantCulture)); stringBuilder.Append('|'); IReadOnlyList entries = Entries; for (int i = 0; i < entries.Count; i++) { SettingEntry settingEntry = entries[i]; if (i > 0) { stringBuilder.Append(';'); } stringBuilder.Append(settingEntry.SettingId); stringBuilder.Append('='); stringBuilder.Append(settingEntry.RawValue(config)); } return stringBuilder.ToString(); } public static bool TryDecodeBlob(string blob, out IReadOnlyDictionary values) { values = null; if (string.IsNullOrEmpty(blob)) { return false; } int num = blob.IndexOf('|'); if (num <= 0) { return false; } if (!int.TryParse(blob.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return false; } if (result != 1) { return false; } string text = blob.Substring(num + 1); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (text.Length > 0) { string[] array = text.Split(';'); foreach (string text2 in array) { if (text2.Length != 0) { int num2 = text2.IndexOf('='); if (num2 < 0) { return false; } if (num2 == 0) { return false; } string key = text2.Substring(0, num2); string value = text2.Substring(num2 + 1); dictionary[key] = value; } } } values = dictionary; return true; } public static IReadOnlyList BuildRows(IReadOnlyDictionary values) { if (values == null) { throw new ArgumentNullException("values"); } List list = new List(Entries.Count); foreach (SettingEntry entry in Entries) { if (values.TryGetValue(entry.SettingId, out var value)) { string text = entry.Display(value); list.Add(new SettingRow { Section = entry.Section, Label = entry.LabelJa, Value = text + entry.Unit }); } } AppendSpecListSection(list, values, "StartUpgradesSpec", SectionStartUpgradeList, (int n) => "+" + n.ToString(CultureInfo.InvariantCulture)); AppendSpecListSection(list, values, "StartItemsSpec", SectionStartItemList, (int n) => n.ToString(CultureInfo.InvariantCulture) + Texts.Get(TextId.SettingsUnitItems)); return list; } private static void AppendSpecListSection(List rows, IReadOnlyDictionary values, string settingId, string section, Func formatCount) { if (!values.TryGetValue(settingId, out var value)) { return; } List list = null; IReadOnlyDictionary readOnlyDictionary = null; if (!string.IsNullOrEmpty(value)) { IReadOnlyDictionary readOnlyDictionary2 = WorldgenSpec.Decode(value); if (readOnlyDictionary2.Count > 0) { list = new List(readOnlyDictionary2.Count); foreach (KeyValuePair item in readOnlyDictionary2) { if (item.Value > 0) { list.Add(item.Key); } } if (list.Count > 0) { list.Sort(StringComparer.Ordinal); readOnlyDictionary = readOnlyDictionary2; } else { list = null; } } } if (list == null) { rows.Add(new SettingRow { Section = section, Label = Texts.Get(TextId.SettingsListEmpty), Value = "" }); return; } foreach (string item2 in list) { rows.Add(new SettingRow { Section = section, Label = item2, Value = formatCount(readOnlyDictionary[item2]) }); } } private static IReadOnlyList BuildEntries() { return new List { new SettingEntry { SettingId = "WerewolfModeEnabled", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelWerewolfModeEnabled), Unit = "", RawValue = (GameConfig c) => (!c.WerewolfModeEnabled) ? "0" : "1", Display = DisplayBoolEnable }, new SettingEntry { SettingId = "RoundSeconds", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelRoundSeconds), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.RoundSeconds.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "MinimapHideEnabled", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelMinimapHideEnabled), Unit = "", RawValue = (GameConfig c) => (!c.MinimapHideEnabled) ? "0" : "1", Display = DisplayBoolEnable }, new SettingEntry { SettingId = "ValuableMapMode", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelValuableMapMode), Unit = "", RawValue = delegate(GameConfig c) { int valuableMapMode = (int)c.ValuableMapMode; return valuableMapMode.ToString(CultureInfo.InvariantCulture); }, Display = DisplayValuableMapMode }, new SettingEntry { SettingId = "OrbGaugeEnabled", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelOrbGaugeEnabled), Unit = "", RawValue = (GameConfig c) => (!c.OrbGaugeEnabled) ? "0" : "1", Display = DisplayBoolEnable }, new SettingEntry { SettingId = "NecroVoiceMode", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelNecroVoiceMode), Unit = "", RawValue = delegate(GameConfig c) { byte necroVoiceMode = (byte)c.NecroVoiceMode; return necroVoiceMode.ToString(CultureInfo.InvariantCulture); }, Display = DisplayNecroVoiceMode }, new SettingEntry { SettingId = "GameOverAutoReturnSec", Section = SectionGeneral, LabelJa = Texts.Get(TextId.SettingsLabelGameOverAutoReturnSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.GameOverAutoReturnSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "MeetingRightsPerPlayer", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelMeetingRightsPerPlayer), Unit = Texts.Get(TextId.SettingsUnitTimes), RawValue = (GameConfig c) => c.MeetingRightsPerPlayer.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ConveneSuppressStartSec", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelConveneSuppressStartSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.ConveneSuppressStartSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ConveneSuppressAfterSec", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelConveneSuppressAfterSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.ConveneSuppressAfterSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "MeetingCountdownSec", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelMeetingCountdownSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.MeetingCountdownSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "MeetingDurationSec", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelMeetingDurationSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.MeetingDurationSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "VoteTimeCutEnabled", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelVoteTimeCutEnabled), Unit = "", RawValue = (GameConfig c) => (!c.VoteTimeCutEnabled) ? "0" : "1", Display = DisplayBoolEnable }, new SettingEntry { SettingId = "ResultDisplaySec", Section = SectionMeeting, LabelJa = Texts.Get(TextId.SettingsLabelResultDisplaySec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.ResultDisplaySec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ShamanChancePercent", Section = SectionRoleAssignment, LabelJa = Texts.Get(TextId.SettingsLabelShamanChancePercent), Unit = Texts.Get(TextId.SettingsUnitPercent), RawValue = (GameConfig c) => c.ShamanChancePercent.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "WerewolfCount", Section = SectionRoleAssignment, LabelJa = Texts.Get(TextId.SettingsLabelWerewolfCount), Unit = Texts.Get(TextId.SettingsUnitPeople), RawValue = (GameConfig c) => c.WerewolfCount.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BlackCatChancePercent", Section = SectionRoleAssignment, LabelJa = Texts.Get(TextId.SettingsLabelBlackCatChancePercent), Unit = Texts.Get(TextId.SettingsUnitPercent), RawValue = (GameConfig c) => c.BlackCatChancePercent.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberChancePercent", Section = SectionRoleAssignment, LabelJa = Texts.Get(TextId.SettingsLabelBomberChancePercent), Unit = Texts.Get(TextId.SettingsUnitPercent), RawValue = (GameConfig c) => c.BomberChancePercent.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ShamanGazeFullSec", Section = SectionShaman, LabelJa = Texts.Get(TextId.SettingsLabelShamanGazeFullSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.ShamanGazeFullSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ShamanGhostCooldownSec", Section = SectionShaman, LabelJa = Texts.Get(TextId.SettingsLabelShamanGhostCooldownSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.ShamanGhostCooldownSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ShamanStormWeakMeters", Section = SectionShaman, LabelJa = Texts.Get(TextId.SettingsLabelShamanStormWeakMeters), Unit = Texts.Get(TextId.SettingsUnitMeters), RawValue = (GameConfig c) => c.ShamanStormWeakMeters.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ShamanStormMediumMeters", Section = SectionShaman, LabelJa = Texts.Get(TextId.SettingsLabelShamanStormMediumMeters), Unit = Texts.Get(TextId.SettingsUnitMeters), RawValue = (GameConfig c) => c.ShamanStormMediumMeters.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ShamanStormStrongMeters", Section = SectionShaman, LabelJa = Texts.Get(TextId.SettingsLabelShamanStormStrongMeters), Unit = Texts.Get(TextId.SettingsUnitMeters), RawValue = (GameConfig c) => c.ShamanStormStrongMeters.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "StaminaUnlockPct", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelStaminaUnlockPct), Unit = "%", RawValue = (GameConfig c) => c.StaminaUnlockPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "JumpUnlockPct", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelJumpUnlockPct), Unit = "%", RawValue = (GameConfig c) => c.JumpUnlockPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "EnemyIgnoreUnlockPct", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelEnemyIgnoreUnlockPct), Unit = "%", RawValue = (GameConfig c) => c.EnemyIgnoreUnlockPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "HealUnlockPct", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelHealUnlockPct), Unit = "%", RawValue = (GameConfig c) => c.HealUnlockPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "HealIntervalSec", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelHealIntervalSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.HealIntervalSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BeaconChargePct", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelBeaconChargePct), Unit = "%", RawValue = (GameConfig c) => c.BeaconChargePct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "ExtraJumpCount", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelExtraJumpCount), Unit = "", RawValue = (GameConfig c) => c.ExtraJumpCount.ToString(CultureInfo.InvariantCulture), Display = DisplayAutoOr(Texts.Get(TextId.SettingsUnitTimes)) }, new SettingEntry { SettingId = "BeaconCooldownSec", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelBeaconCooldownSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BeaconCooldownSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BeaconSuppressStartSec", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelBeaconSuppressStartSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BeaconSuppressStartSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BeaconSuppressAfterMeetingSec", Section = SectionRoles, LabelJa = Texts.Get(TextId.SettingsLabelBeaconSuppressAfterMeetingSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BeaconSuppressAfterMeetingSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BlackCatRevealDelaySec", Section = SectionBlackCat, LabelJa = Texts.Get(TextId.SettingsLabelBlackCatRevealDelaySec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BlackCatRevealDelaySec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BlackCatCurseEnabled", Section = SectionBlackCat, LabelJa = Texts.Get(TextId.SettingsLabelBlackCatCurseEnabled), Unit = "", RawValue = (GameConfig c) => (!c.BlackCatCurseEnabled) ? "0" : "1", Display = DisplayBoolEnable }, new SettingEntry { SettingId = "InformantThresholdPct", Section = SectionBlackCat, LabelJa = Texts.Get(TextId.SettingsLabelInformantThresholdPct), Unit = "%", RawValue = (GameConfig c) => c.InformantThresholdPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "CatGaugeSyncIntervalSec", Section = SectionBlackCat, LabelJa = Texts.Get(TextId.SettingsLabelCatGaugeSyncIntervalSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.CatGaugeSyncIntervalSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberProximityMeters", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberProximityMeters), Unit = Texts.Get(TextId.SettingsUnitMeters), RawValue = (GameConfig c) => c.BomberProximityMeters.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberGaugeFullSec", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberGaugeFullSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BomberGaugeFullSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberInitialCooldownSec", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberInitialCooldownSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BomberInitialCooldownSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberCooldownSec", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberCooldownSec), Unit = Texts.Get(TextId.SettingsUnitSeconds), RawValue = (GameConfig c) => c.BomberCooldownSec.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberBlastRadiusMeters", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberBlastRadiusMeters), Unit = Texts.Get(TextId.SettingsUnitMeters), RawValue = (GameConfig c) => c.BomberBlastRadiusMeters.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberBlastPlayerDamage", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberBlastPlayerDamage), Unit = "", RawValue = (GameConfig c) => c.BomberBlastPlayerDamage.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberBlastEnemyDamage", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberBlastEnemyDamage), Unit = "", RawValue = (GameConfig c) => c.BomberBlastEnemyDamage.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "BomberAmmoRefillPct", Section = SectionBomber, LabelJa = Texts.Get(TextId.SettingsLabelBomberAmmoRefillPct), Unit = Texts.Get(TextId.SettingsUnitPercent), RawValue = (GameConfig c) => c.BomberAmmoRefillPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "StartLevelNumber", Section = SectionWorldgen, LabelJa = Texts.Get(TextId.SettingsLabelStartLevelNumber), Unit = "", RawValue = (GameConfig c) => c.StartLevelNumber.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "StartMapName", Section = SectionWorldgen, LabelJa = Texts.Get(TextId.SettingsLabelStartMapName), Unit = "", RawValue = (GameConfig c) => SanitizeMapNameRaw(c.StartMapName), Display = (string raw) => (!string.IsNullOrEmpty(raw)) ? raw : Texts.Get(TextId.SettingsRandom) }, new SettingEntry { SettingId = "StartItemsSpec", Section = SectionWorldgen, LabelJa = Texts.Get(TextId.SettingsLabelStartItemsSpec), Unit = "", RawValue = (GameConfig c) => c.StartItemsSpec ?? "", Display = DisplayItemsAggregate }, new SettingEntry { SettingId = "StartEnergyPct", Section = SectionWorldgen, LabelJa = Texts.Get(TextId.SettingsLabelStartEnergyPct), Unit = "%", RawValue = (GameConfig c) => c.StartEnergyPct.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw }, new SettingEntry { SettingId = "StartUpgradesSpec", Section = SectionWorldgen, LabelJa = Texts.Get(TextId.SettingsLabelStartUpgradesSpec), Unit = "", RawValue = (GameConfig c) => c.StartUpgradesSpec ?? "", Display = DisplayUpgradesAggregate }, new SettingEntry { SettingId = "OrbDropMax", Section = SectionWorldgen, LabelJa = Texts.Get(TextId.SettingsLabelOrbDropMax), Unit = Texts.Get(TextId.SettingsUnitItems), RawValue = (GameConfig c) => c.OrbDropMax.ToString(CultureInfo.InvariantCulture), Display = (string raw) => raw } }; } } public sealed class SettingEntry { public string SettingId; public string Section; public string LabelJa; public string Unit; public Func RawValue; public Func Display; } public sealed class SettingRow { public string Section; public string Label; public string Value; } public enum ShamanStormTier : byte { None, Weak, Medium, Strong } public sealed class ShamanSense { public const float GhostDisplaySec = 1f; public const float StormExitMarginMeters = 1f; public const float TranceEntrySec = 1f; private readonly float _gazeFullSec; private readonly float _cooldownSec; private readonly float _decayMultiplier; private float _gauge; private float _ghostRemainingSec; private float _cooldownRemainingSec; private ShamanStormTier _stormTier; private float _stillSec; private float _dripCountdownSec; public bool GhostVisible => _ghostRemainingSec > 0f; public ShamanStormTier StormTier => _stormTier; public bool TranceActive => _stillSec >= 1f; public bool GazeArmed { get { if (TranceActive && _stormTier == ShamanStormTier.None && _ghostRemainingSec <= 0f) { return _cooldownRemainingSec <= 0f; } return false; } } public ShamanSense(float gazeFullSec, float cooldownSec, float decayMultiplier = 2f) { _gazeFullSec = ((gazeFullSec > 0f) ? gazeFullSec : 1f); _cooldownSec = ((cooldownSec > 0f) ? cooldownSec : 0f); _decayMultiplier = ((decayMultiplier > 0f) ? decayMultiplier : 1f); } public bool TickGaze(bool corpseInView, bool stationary, float deltaSeconds, bool suspend, out bool dripFired) { dripFired = false; if (suspend) { _gauge = 0f; _ghostRemainingSec = 0f; _stillSec = 0f; _dripCountdownSec = 0f; return false; } if (deltaSeconds <= 0f) { return false; } if (stationary) { _stillSec += deltaSeconds; } else { _stillSec = 0f; _dripCountdownSec = 0f; } if (_ghostRemainingSec > 0f) { _ghostRemainingSec -= deltaSeconds; if (_ghostRemainingSec <= 0f) { _ghostRemainingSec = 0f; _cooldownRemainingSec = _cooldownSec; } return false; } if (_cooldownRemainingSec > 0f) { _cooldownRemainingSec -= deltaSeconds; if (_cooldownRemainingSec < 0f) { _cooldownRemainingSec = 0f; } return false; } if (_stormTier != ShamanStormTier.None) { corpseInView = false; } if (!TranceActive) { corpseInView = false; } _gauge = (corpseInView ? (_gauge + deltaSeconds) : (_gauge - deltaSeconds * _decayMultiplier)); if (_gauge < 0f) { _gauge = 0f; } if (_gauge >= _gazeFullSec) { _gauge = 0f; _ghostRemainingSec = 1f; _dripCountdownSec = 0f; return true; } if (GazeArmed) { if (_dripCountdownSec <= 0f) { _dripCountdownSec = _gazeFullSec; } else { _dripCountdownSec -= deltaSeconds; if (_dripCountdownSec <= 0f) { dripFired = true; _dripCountdownSec = _gazeFullSec; } } } return false; } public ShamanStormTier TickStorm(float? nearestDistanceMeters, bool suspend, float weakMeters, float mediumMeters, float strongMeters) { if (suspend || !nearestDistanceMeters.HasValue) { _stormTier = ShamanStormTier.None; return _stormTier; } float value = nearestDistanceMeters.Value; ShamanStormTier shamanStormTier = RawTier(value, weakMeters, mediumMeters, strongMeters); if ((int)shamanStormTier < (int)_stormTier && value <= TierRadius(_stormTier, weakMeters, mediumMeters, strongMeters) + 1f) { return _stormTier; } _stormTier = shamanStormTier; return _stormTier; } public void BeginCooldown() { _cooldownRemainingSec = _cooldownSec; } public void Reset() { _gauge = 0f; _ghostRemainingSec = 0f; _cooldownRemainingSec = 0f; _stormTier = ShamanStormTier.None; _stillSec = 0f; _dripCountdownSec = 0f; } private static ShamanStormTier RawTier(float d, float weakM, float mediumM, float strongM) { if (d <= strongM) { return ShamanStormTier.Strong; } if (d <= mediumM) { return ShamanStormTier.Medium; } if (d <= weakM) { return ShamanStormTier.Weak; } return ShamanStormTier.None; } private static float TierRadius(ShamanStormTier tier, float weakM, float mediumM, float strongM) { return tier switch { ShamanStormTier.Strong => strongM, ShamanStormTier.Medium => mediumM, ShamanStormTier.Weak => weakM, _ => 0f, }; } } public static class ShamanSenseGate { public static bool ShouldSuspend(GamePhase phase, bool localAlive, bool meetingActive, bool warpDone, ConveneKind kind) { if (!localAlive) { return true; } if (phase != GamePhase.Play && phase != GamePhase.Meeting) { return true; } if (!meetingActive) { return phase != GamePhase.Play; } if (!warpDone) { return kind == ConveneKind.CorpseReport; } return true; } } public enum StartHoldPhase { Idle, Holding, Released } public enum StartHoldRelease { None, GameStart, Failsafe } public sealed class StartHoldGate { public const int FailsafeSec = 20; private long _heldAtUnixMs; private long _failsafeReleasedAtUnixMs; private bool _lateGameStartPending; public StartHoldPhase Phase { get; private set; } public long LateGameStartGapMs { get; private set; } = -1L; public long HeldMs(long nowUnixMs) { if (Phase != StartHoldPhase.Holding) { return 0L; } return nowUnixMs - _heldAtUnixMs; } public bool Tick(bool inRunLevel, bool operable, bool werewolfExpected, bool gameStartReceived, long nowUnixMs, out StartHoldRelease released) { released = StartHoldRelease.None; LateGameStartGapMs = -1L; if (!inRunLevel) { Phase = StartHoldPhase.Idle; _lateGameStartPending = false; return false; } switch (Phase) { case StartHoldPhase.Idle: if (!operable) { return false; } if (!werewolfExpected || gameStartReceived) { Phase = StartHoldPhase.Released; return false; } Phase = StartHoldPhase.Holding; _heldAtUnixMs = nowUnixMs; return true; case StartHoldPhase.Holding: if (gameStartReceived) { Phase = StartHoldPhase.Released; released = StartHoldRelease.GameStart; return false; } if (nowUnixMs - _heldAtUnixMs >= 20000) { Phase = StartHoldPhase.Released; released = StartHoldRelease.Failsafe; _failsafeReleasedAtUnixMs = nowUnixMs; _lateGameStartPending = true; return false; } return true; default: if (_lateGameStartPending && gameStartReceived) { _lateGameStartPending = false; LateGameStartGapMs = nowUnixMs - _failsafeReleasedAtUnixMs; } return false; } } } public enum Language { Japanese, English } public enum TextId { NoticeConveneStartedFormat, NoticeBeaconAuditFormat, NoticeBeaconAuditNone, NoticeNoExecution, NoticeExecutedFormat, NoticeBlackCatRevealedFormat, NoticeCurseVictimFormat, NoticeCatAwakened, NoticeConveneDeniedNoRight, NoticeConveneDeniedSuppressed, NoticeConveneDeniedWrongPhase, NoticeConveneDeniedOther, NoticeCorpseReportStartedFormat, NoticeMeetingCancelledExtraction, NoticeConveneDeniedLastRun, NoticeConveneDeniedNoCorpse, NoticePlayerDisconnectedFormat, NoticeConveneHoldHint, RevealTeammatePrefix, RevealHeadingWinCondition, RevealHeadingAbility, RevealVillagerTitleMaybeCat, RevealVillagerTitle, RevealVillagerWinCondition1, RevealVillagerWinCondition2, RevealVillagerFlavor, RevealWolfTeamWinCondition1, RevealWolfTeamWinCondition2, RevealWolfTeamWinCondition3, RevealWolfAbility1, RevealWolfAbility2, RevealWolfAbility3, RevealBomberAbility1, RevealBomberAbility2, RevealBomberAbility3, RevealBlackCatAbility, RevealBlackCatNoCurse, RevealWerewolfTitle, RevealBlackCatTitle, RevealBlackCatAwakeningTitle, RevealBomberTitle, RevealShamanTitle, RevealShamanAbility1, RevealShamanAbility2, RevealHeadingTips, RevealVillagerTipConvene, RevealVillagerTipReport, RevealVillagerTipValuableMap, RevealVillagerTipAliveCheck, RevealSkipHint, RoleNameWerewolf, RoleNameBlackCat, RoleNameVillager, RoleNameBomber, RoleNameShaman, GaugePerkStaminaLabel, GaugePerkJumpLabel, GaugePerkEnemyIgnoreLabel, GaugePerkHealLabel, GaugePerkInformantLabel, GaugeBeaconRuleFormat, GaugeBombRuleFormat, GaugePercentDollarsFormat, GaugeLossOverBaseFormat, GaugeNextUpdateFormat, HudTimerFrozenFormat, HudGaugeFormat, HudRightsFormat, HudTimeRemainingFormat, HudWolfToggleFormat, HudBeaconKeyFormat, HudBeaconLabel, HudTestPlayBanner, StartHoldWaitingOthers, ResultBannerVillagerWin, ResultBannerWerewolfWin, ResultBannerDefault, ResultStatusAlive, ResultStatusDead, ResultStatusExecuted, ResultStatusDisconnected, ResultReturnPromptFormat, ResultWaitingHost, ResultFooterWithCountdownFormat, ResultDigestHeader, DigestMatchStart, DigestMeetingButtonFormat, DigestMeetingReportFormat, DigestExecutedFormat, DigestNoExecution, DigestCurseStartedFormat, DigestCurseFollowFormat, DigestDeathFormat, DigestBombDetonatedFormat, DigestCheckmate, DigestMatchEndFormat, DigestReasonWerewolvesEradicated, DigestReasonVillagersEradicated, DigestReasonExtractionCompleted, DigestReasonTimerExpired, DigestReasonExtractionFailed, DigestReasonValueCheckmate, DigestExtractionDoneFormat, DigestPerkUnlockedFormat, DigestInformant, DigestFinalBalanceFormat, VoteMeetingTitle, VoteSkipLabel, VoteConfirmLabel, VoteVoteLabel, VoteWerewolfMarkerLabel, VoteBomberMarkerLabel, VoteCountFormat, VoteExecutedFormat, VoteNoExecution, VoteSkipSuffixFormat, ChatLogTitle, ChatLogEmpty, ChatLogDeadHint, ChatLogToggleLabelFormat, ChatLogVoted, RecapTitle, RecapNameSeparator, RecapDeathsFormat, RecapDeathsNone, RecapLostFormat, RecapHaulFormat, RecapBeaconFormat, RecapBeaconNone, DeathRevealTitle, DeathRevealNone, CheckmateTitle, MapOverlayToggleLabelFormat, LobbySettingsFooterHintFormat, LobbySettingsMiniHintFormat, ModIntegrityHeaderAllMatchFormat, ModIntegrityHeaderCountsFormat, ModIntegritySelfDifferenceFormat, ModIntegritySelfUnavailable, ModIntegrityPanelTitle, ModIntegrityFilterNeedsReview, ModIntegrityFilterMatch, ModIntegrityFilterAll, ModIntegrityStatusBaseline, ModIntegrityStatusPending, ModIntegrityStatusMatch, ModIntegrityStatusDifference, ModIntegrityStatusUnavailable, ModIntegrityReasonNoResponse, ModIntegrityReasonUnsupportedProtocol, ModIntegrityReasonInvalidPayload, ModIntegrityReasonTooLarge, ModIntegrityReasonCollectionFailed, ModIntegrityDetailLoading, ModIntegrityDetailFailed, ModIntegrityDisclaimer, ModIntegrityDetailMissingFormat, ModIntegrityDetailExtraFormat, ModIntegrityDetailVersionFormat, ModIntegrityDetailContentFormat, ModIntegrityStartCautionTitle, ModIntegrityStartSevereTitle, ModIntegrityStartBodyFormat, ModIntegrityButtonBack, ModIntegrityButtonDetails, ModIntegrityButtonContinue, ModIntegrityButtonClose, LobbyStartTooFewPlayersTitle, LobbyStartTooFewPlayersBodyFormat, LobbyStartTeamOverflowTitle, LobbyStartTeamOverflowBodyFormat, ConveneCountdownDefaultCallerName, ConveneCountdownHeaderFormat, ConveneCountdownCorpseHeaderFormat, HudCorpseReportKeyFormat, MeetingButtonSuppressCountdownFormat, MeetingButtonConveneGrabPrompt, MeetingButtonConveneInteractPrompt, MeetingButtonSuppressedPrompt, MeetingButtonRightsSuffixFormat, CurseBlackCatRevealedFormat, CurseNoVictim, SettingsSectionGeneral, SettingsSectionMeeting, SettingsSectionRoleAssignment, SettingsSectionRoles, SettingsSectionBlackCat, SettingsSectionBomber, SettingsSectionShaman, SettingsSectionWorldgen, SettingsSectionStartItemList, SettingsSectionStartUpgradeList, SettingsBoolEnabled, SettingsBoolDisabled, SettingsAuto, SettingsListEmpty, SettingsPresent, SettingsRandom, SettingsValuableMapRealtime, SettingsValuableMapMeetingSync, SettingsValuableMapHidden, SettingsNecroVoiceOff, SettingsNecroVoiceNonWerewolfDead, SettingsNecroVoiceAllDead, SettingsItemsAggregateFormat, SettingsAggregateMoreSuffix, SettingsUpgradeItemFormat, SettingsListSeparator, SettingsUnitSeconds, SettingsUnitTimes, SettingsUnitItems, SettingsUnitPeople, SettingsUnitMeters, SettingsUnitPercent, SettingsUnitDamage, SettingsLabelWerewolfCount, SettingsLabelBlackCatChancePercent, SettingsLabelBomberChancePercent, SettingsLabelShamanChancePercent, SettingsLabelRoundSeconds, SettingsLabelBlackCatRevealDelaySec, SettingsLabelBlackCatCurseEnabled, SettingsLabelMeetingRightsPerPlayer, SettingsLabelConveneSuppressStartSec, SettingsLabelConveneSuppressAfterSec, SettingsLabelMeetingCountdownSec, SettingsLabelMeetingDurationSec, SettingsLabelVoteTimeCutEnabled, SettingsLabelResultDisplaySec, SettingsLabelStaminaUnlockPct, SettingsLabelJumpUnlockPct, SettingsLabelEnemyIgnoreUnlockPct, SettingsLabelHealUnlockPct, SettingsLabelHealIntervalSec, SettingsLabelBeaconChargePct, SettingsLabelInformantThresholdPct, SettingsLabelExtraJumpCount, SettingsLabelBeaconCooldownSec, SettingsLabelBeaconSuppressStartSec, SettingsLabelBeaconSuppressAfterMeetingSec, SettingsLabelCatGaugeSyncIntervalSec, SettingsLabelOrbGaugeEnabled, SettingsLabelWerewolfModeEnabled, SettingsLabelMinimapHideEnabled, SettingsLabelValuableMapMode, SettingsLabelGameOverAutoReturnSec, SettingsLabelNecroVoiceMode, SettingsLabelStartLevelNumber, SettingsLabelStartMapName, SettingsLabelStartItemsSpec, SettingsLabelStartEnergyPct, SettingsLabelStartUpgradesSpec, SettingsLabelOrbDropMax, SettingsLabelBomberProximityMeters, SettingsLabelBomberGaugeFullSec, SettingsLabelBomberInitialCooldownSec, SettingsLabelBomberCooldownSec, SettingsLabelBomberBlastRadiusMeters, SettingsLabelBomberBlastPlayerDamage, SettingsLabelBomberBlastEnemyDamage, SettingsLabelBomberAmmoRefillPct, SettingsLabelShamanGazeFullSec, SettingsLabelShamanGhostCooldownSec, SettingsLabelShamanStormWeakMeters, SettingsLabelShamanStormMediumMeters, SettingsLabelShamanStormStrongMeters, HudBomberPlantKeyFormat, HudBomberDetonateKeyFormat, HudValuableRecordOnFormat, HudValuableRecordOffFormat, BomberDenyNoAmmo, BomberDenyNoFullTarget, BomberDenyPlantCooldown, BomberDenyDetonateCooldown, BomberDenyNoBomb, BomberDenyMeetingLocked, BomberDenyTruckZone, BomberDudTargetDead, BomberTargetDisconnected, BomberProximityWarning, TutorialCorpseDiscovery, TutorialMeetingCountdownStarted, TutorialFirstMeetingAsVillager, TutorialWerewolfRoleDrawn, TutorialFirstValuableSeen, TutorialWolfModeFirstUnlock, TutorialBeaconFirstCharged, TutorialFirstMeetingAsWerewolf, TutorialFirstMeetingAsBlackCat, TutorialVillagerSeesCatAwakened, TutorialBlackCatRoleDrawn, TutorialBlackCatRoleDrawnNoCurse, TutorialLastRunApproaching, TutorialRoundTimeWarningVillager, TutorialRoundTimeWarningWerewolf, TutorialFinalExtractionVillager, TutorialFinalExtractionWerewolf, TutorialInformantUnlockedAsWerewolf, TutorialInformantUnlockedAsBlackCat, TutorialEnemyIgnoreUnlockedAsWerewolf, TutorialNaturalHealUnlockedAsWerewolf, TutorialWerewolfSeesCatAwakened, TutorialBeaconFirstUsedAsWerewolf, TutorialBlackCatSelectedForExecution, TutorialBlackCatExecutionRevealed, TutorialFirstDeath, TutorialBomberRoleDrawn, TutorialBombPlantedAsBomber, TutorialBomberProximityWarnedAsVillager, TutorialSelfBombExplodedAsVillager, TutorialShamanRoleDrawn, TutorialShamanGhostSighted, TutorialShamanTranceEntered, TutorialShamanStormEntered, TutorialEquipBlockedByOtherGrabber, TutorialValuableRecordSuppressed, ManualToggleLabelFormat, ManualPageFooterFormat, ManualNavHint, ManualSectionBasics, ManualSectionExploration, ManualSectionMeeting, ManualSectionGauge, ManualSectionVillager, ManualSectionShaman, ManualSectionWerewolf, ManualSectionBlackCat, ManualSectionBomber, ManualSectionAfterDeath, ManualWelcomeTitle, ManualWelcomeBody, ManualGameFlowTitle, ManualGameFlowBody, ManualVillagerWinTitle, ManualVillagerWinBody, ManualWerewolfWinTitle, ManualWerewolfWinBody, ManualValuablesMapTitle, ManualValuablesMapBody, ManualValuableRecordTitle, ManualValuableRecordBody, ManualValuableRecordToggle, ManualCombatTitle, ManualCombatBody, ManualEndgamePrepTitle, ManualEndgamePrepBody, ManualCorpseTitle, ManualCorpseBody, ManualConveneTitle, ManualConveneBody, ManualMeetingFlowTitle, ManualMeetingFlowBody, ManualVotingTitle, ManualVotingBody, ManualGaugeBasicsTitle, ManualGaugeIntro, ManualGaugeLoss, ManualGaugeDelivery, ManualGaugeLines, ManualRoleVillagerTitle, ManualRoleVillagerBody, ManualRoleWerewolfTitle, ManualRoleWerewolfIntro, ManualRoleWerewolfEnemyMap, ManualRoleWerewolfPerksTitle, ManualRoleWerewolfPerkStamina, ManualRoleWerewolfPerkJump, ManualRoleWerewolfPerksTitle2, ManualRoleWerewolfPerkEnemyIgnore, ManualRoleWerewolfPerkHeal, ManualRoleWerewolfPerkToggle, ManualRoleWerewolfBeaconTitle, ManualRoleWerewolfBeaconBody, ManualRoleBlackCatTitle, ManualRoleBlackCatIntro, ManualBlackCatInformantTitle, ManualRoleBlackCatInformant, ManualRoleBlackCatGaugeNote, ManualBlackCatCounterTitle, ManualBlackCatCounterBody, ManualRoleBomberTitle, ManualRoleBomberIntro, ManualBomberPlantTitle, ManualRoleBomberPlant, ManualRoleBomberDetonateTitle, ManualRoleBomberDetonateBody, ManualRoleShamanTitle, ManualRoleShamanIntro, ManualShamanSenseTitle, ManualShamanGhost, ManualShamanStorm, ManualAfterDeathTitle, ManualAfterDeathBody } public static class Texts { private static IReadOnlyDictionary _override; public static Action FormatErrorLogger; private static readonly HashSet _formatErrorLogged = new HashSet(); private static readonly IReadOnlyDictionary JapaneseTable = new Dictionary { [TextId.NoticeConveneStartedFormat] = "{0}が緊急会議を招集しました", [TextId.NoticeBeaconAuditFormat] = "前回の会議以降、ビーコンは{0}回使用されました", [TextId.NoticeBeaconAuditNone] = "前回の会議以降、ビーコンは使用されませんでした", [TextId.NoticeNoExecution] = "誰も処刑されませんでした", [TextId.NoticeExecutedFormat] = "{0}が処刑されました", [TextId.NoticeBlackCatRevealedFormat] = "{0}は黒猫でした", [TextId.NoticeCurseVictimFormat] = "{0}は道連れにされました", [TextId.NoticeCatAwakened] = "もし黒猫がいるなら、目覚めている頃です…", [TextId.NoticeConveneDeniedNoRight] = "会議を開催できません(開催権がありません)", [TextId.NoticeConveneDeniedSuppressed] = "会議を開催できません(現在は抑止時間中です)", [TextId.NoticeConveneDeniedWrongPhase] = "会議を開催できません(今は開催できません)", [TextId.NoticeConveneDeniedOther] = "会議を開催できません", [TextId.NoticeCorpseReportStartedFormat] = "{0}が死体の頭を発見しました", [TextId.NoticeMeetingCancelledExtraction] = "最終抽出直前のため死体の通報は中止されました", [TextId.NoticeConveneDeniedLastRun] = "会議を開催できません(最終抽出直前からは通報できません)", [TextId.NoticeConveneDeniedNoCorpse] = "会議を開催できません(通報できる死体が無い)", [TextId.NoticePlayerDisconnectedFormat] = "{0}がゲームから切断されました", [TextId.NoticeConveneHoldHint] = "会議を招集するには、ボタンを長押ししてください", [TextId.RevealTeammatePrefix] = "人狼仲間:", [TextId.RevealHeadingWinCondition] = "◆ 勝利条件(いずれか)", [TextId.RevealHeadingAbility] = "◆ あなたの能力", [TextId.RevealVillagerTitleMaybeCat] = "あなたは村人…かもしれません。", [TextId.RevealVillagerTitle] = "あなたは村人です", [TextId.RevealVillagerWinCondition1] = "全ての抽出を完了し、トラックを発車させる", [TextId.RevealVillagerWinCondition2] = "人狼陣営を全滅させる", [TextId.RevealVillagerFlavor] = "特別な能力はない。貴重品の回収と会議で人狼に立ち向かえ。", [TextId.RevealWolfTeamWinCondition1] = "その時点で存在する貴重品を全て集めても、最後の抽出を完了できない状態にする", [TextId.RevealWolfTeamWinCondition2] = "時間切れまでトラックを発車させない", [TextId.RevealWolfTeamWinCondition3] = "村人陣営を全滅させる", [TextId.RevealWolfAbility1] = "貴重品を壊すほどバフがかかる", [TextId.RevealWolfAbility2] = "マップで敵の位置が分かる", [TextId.RevealWolfAbility3] = "ビーコンで敵を今いる場所におびき寄せる", [TextId.RevealBomberAbility1] = "十分な時間近くで過ごした他のプレイヤーを爆弾に変えられる", [TextId.RevealBomberAbility2] = "好きなタイミングで起爆し、周囲を破壊する。爆弾にされた本人もダメージを受けるが、HP1で耐える", [TextId.RevealBomberAbility3] = "その爆発に巻き込まれると自分が即死する", [TextId.RevealBlackCatAbility] = "処刑対象に選ばれると、自分に投票したプレイヤーのうち1人を道連れに死亡させる", [TextId.RevealBlackCatNoCurse] = "人狼とは互いに正体を知らされないが、人狼陣営として勝敗を共にする", [TextId.RevealWerewolfTitle] = "あなたは人狼です", [TextId.RevealBlackCatTitle] = "あなたは黒猫です", [TextId.RevealBlackCatAwakeningTitle] = "あなたは黒猫でした。", [TextId.RevealBomberTitle] = "あなたは爆弾魔です", [TextId.RevealShamanTitle] = "あなたは祈祷師です", [TextId.RevealShamanAbility1] = "立ち止まって視線を据えると視界が褪せ、霊視が始まる。未発見の死体がある方を見つめ続けると、霊障がする", [TextId.RevealShamanAbility2] = "未発見の死体に近づくほど、画面に強い霊障が出る", [TextId.RevealHeadingTips] = "◆ ヒント", [TextId.RevealVillagerTipConvene] = "トラック最奥の赤いボタン長押しで会議を開ける", [TextId.RevealVillagerTipReport] = "死体の近くで通報キーを押すと会議を開ける", [TextId.RevealVillagerTipValuableMap] = "会議のたびにマップの貴重品情報が更新される", [TextId.RevealVillagerTipAliveCheck] = "会議では全プレイヤーの生死を確認できる", [TextId.RevealSkipHint] = "[menu] でスキップ", [TextId.RoleNameWerewolf] = "人狼", [TextId.RoleNameBlackCat] = "黒猫", [TextId.RoleNameVillager] = "村人", [TextId.RoleNameBomber] = "爆弾魔", [TextId.RoleNameShaman] = "祈祷師", [TextId.GaugePerkStaminaLabel] = "無限スタミナ", [TextId.GaugePerkJumpLabel] = "追加ジャンプ", [TextId.GaugePerkEnemyIgnoreLabel] = "敵認識無効", [TextId.GaugePerkHealLabel] = "自然治癒", [TextId.GaugePerkInformantLabel] = "黒猫に人狼開示", [TextId.GaugeBeaconRuleFormat] = "ビーコン+1/{0}%", [TextId.GaugeBombRuleFormat] = "爆弾+1/{0}%", [TextId.GaugePercentDollarsFormat] = "{0}% (${1})", [TextId.GaugeLossOverBaseFormat] = "${0} / ${1}", [TextId.GaugeNextUpdateFormat] = "次の更新まで {0}秒", [TextId.HudTimerFrozenFormat] = "{0} 停止中", [TextId.HudGaugeFormat] = "ゲージ {0}%", [TextId.HudRightsFormat] = "会議招集残 {0} 回", [TextId.HudTimeRemainingFormat] = "残り {0}:{1}", [TextId.HudWolfToggleFormat] = "狼化 [{0}]", [TextId.HudBeaconKeyFormat] = "ビーコン [{0}]", [TextId.HudBeaconLabel] = "ビーコン", [TextId.HudTestPlayBanner] = "テストプレイ中:ホストがデバッグモードを有効にしています", [TextId.StartHoldWaitingOthers] = "他のプレイヤーの準備を待っています…", [TextId.ResultBannerVillagerWin] = "村人陣営の勝利", [TextId.ResultBannerWerewolfWin] = "人狼陣営の勝利", [TextId.ResultBannerDefault] = "試合結果", [TextId.ResultStatusAlive] = "生存", [TextId.ResultStatusDead] = "死亡", [TextId.ResultStatusExecuted] = "処刑", [TextId.ResultStatusDisconnected] = "切断", [TextId.ResultReturnPromptFormat] = "[{0}] ロビーへ戻る", [TextId.ResultWaitingHost] = "ホストの操作を待っています…", [TextId.ResultFooterWithCountdownFormat] = "{0}\u3000|\u3000自動帰還まで約 {1} 秒", [TextId.ResultDigestHeader] = "── 試合ログ(マウスホイールでスクロール)──", [TextId.DigestMatchStart] = "試合開始", [TextId.DigestMeetingButtonFormat] = "{0} が会議を召集", [TextId.DigestMeetingReportFormat] = "{0} が死体を通報", [TextId.DigestExecutedFormat] = "投票により {0} が処刑", [TextId.DigestNoExecution] = "開票: 処刑なし", [TextId.DigestCurseStartedFormat] = "処刑された {0} は黒猫だった", [TextId.DigestCurseFollowFormat] = "黒猫の道連れで {0} が死亡", [TextId.DigestDeathFormat] = "{0} が死亡", [TextId.DigestBombDetonatedFormat] = "{0} に仕掛けられた爆弾が爆発", [TextId.DigestCheckmate] = "資産詰みが成立", [TextId.DigestMatchEndFormat] = "{0}({1})", [TextId.DigestReasonWerewolvesEradicated] = "人狼全滅", [TextId.DigestReasonVillagersEradicated] = "村人陣営全滅", [TextId.DigestReasonExtractionCompleted] = "トラック発車", [TextId.DigestReasonTimerExpired] = "時間切れ", [TextId.DigestReasonExtractionFailed] = "抽出失敗", [TextId.DigestReasonValueCheckmate] = "資産詰み", [TextId.DigestExtractionDoneFormat] = "納品完了({0}/{1})", [TextId.DigestPerkUnlockedFormat] = "人狼特典「{0}」解禁", [TextId.DigestInformant] = "内通が成立(黒猫へ人狼陣営を開示)", [TextId.DigestFinalBalanceFormat] = "最終収支: 納品 ${0} / 残ノルマ ${1} / 回収可能 ${2}", [TextId.VoteMeetingTitle] = "緊急会議", [TextId.VoteSkipLabel] = "スキップ", [TextId.VoteConfirmLabel] = "本当に?", [TextId.VoteVoteLabel] = "投票", [TextId.VoteWerewolfMarkerLabel] = "狼", [TextId.VoteBomberMarkerLabel] = "爆", [TextId.VoteCountFormat] = "{0}票", [TextId.VoteExecutedFormat] = "処刑: {0}", [TextId.VoteNoExecution] = "処刑なし", [TextId.VoteSkipSuffixFormat] = "(スキップ {0}票)", [TextId.ChatLogTitle] = "会議チャットログ", [TextId.ChatLogEmpty] = "まだ発言はありません", [TextId.ChatLogDeadHint] = "灰色の発言は冥界にだけ見えています", [TextId.ChatLogToggleLabelFormat] = "会議チャットログ [{0}]", [TextId.ChatLogVoted] = "が投票しました。", [TextId.RecapTitle] = "ここまでの経過", [TextId.RecapNameSeparator] = "、", [TextId.RecapDeathsFormat] = "死亡: {0}", [TextId.RecapDeathsNone] = "死亡: なし", [TextId.RecapLostFormat] = "破壊された貴重品: ${0}", [TextId.RecapHaulFormat] = "納品: ${0} / ノルマ ${1}", [TextId.RecapBeaconFormat] = "ビーコン使用: {0}回", [TextId.RecapBeaconNone] = "ビーコン使用: なし", [TextId.DeathRevealTitle] = "死亡者", [TextId.DeathRevealNone] = "誰も死んでいない", [TextId.CheckmateTitle] = "債権はもはや徴収不能だ", [TextId.MapOverlayToggleLabelFormat] = "全体マップ [{0}]", [TextId.LobbySettingsFooterHintFormat] = "[{0}]キー : パネルを隠す / ホイール : スクロール", [TextId.LobbySettingsMiniHintFormat] = "[{0}]キー : 人狼の部屋設定を表示", [TextId.ModIntegrityHeaderAllMatchFormat] = "✓ MOD構成一致 {0}/{1}", [TextId.ModIntegrityHeaderCountsFormat] = "基準 {0} ✓ {1} ! {2} × {3} ? {4}", [TextId.ModIntegritySelfDifferenceFormat] = "あなたの構成に差分があります(不足{0} / 追加{1} / Version{2} / 内容{3})", [TextId.ModIntegritySelfUnavailable] = "あなたのMOD情報を確認できていません", [TextId.ModIntegrityPanelTitle] = "MOD構成(ルーム基準比較)", [TextId.ModIntegrityFilterNeedsReview] = "要確認", [TextId.ModIntegrityFilterMatch] = "一致", [TextId.ModIntegrityFilterAll] = "すべて", [TextId.ModIntegrityStatusBaseline] = "基準", [TextId.ModIntegrityStatusPending] = "確認中", [TextId.ModIntegrityStatusMatch] = "一致", [TextId.ModIntegrityStatusDifference] = "差分あり", [TextId.ModIntegrityStatusUnavailable] = "取得不可", [TextId.ModIntegrityReasonNoResponse] = "応答なし", [TextId.ModIntegrityReasonUnsupportedProtocol] = "非対応バージョン", [TextId.ModIntegrityReasonInvalidPayload] = "比較不能な応答", [TextId.ModIntegrityReasonTooLarge] = "情報量が上限超過", [TextId.ModIntegrityReasonCollectionFailed] = "MOD情報の収集失敗", [TextId.ModIntegrityDetailLoading] = "詳細を取得中…", [TextId.ModIntegrityDetailFailed] = "詳細を取得できませんでした 再取得", [TextId.ModIntegrityDisclaimer] = "この表示はクライアント申告のルーム基準比較であり、チート不在を保証しません。", [TextId.ModIntegrityDetailMissingFormat] = "不足: {0} ({1})", [TextId.ModIntegrityDetailExtraFormat] = "追加: {0} ({1})", [TextId.ModIntegrityDetailVersionFormat] = "Version: {0} 基準 {1} → 参加者 {2}", [TextId.ModIntegrityDetailContentFormat] = "内容: {0} 基準 {1} → 参加者 {2}", [TextId.ModIntegrityStartCautionTitle] = "MOD構成に差分があります", [TextId.ModIntegrityStartSevereTitle] = "未確認のMOD構成があります", [TextId.ModIntegrityStartBodyFormat] = "差分あり {0}人 / 取得不可 {1}人 / 確認中 {2}人\n公平性へ影響する可能性があります。", [TextId.ModIntegrityButtonBack] = "戻る", [TextId.ModIntegrityButtonDetails] = "構成を確認", [TextId.ModIntegrityButtonContinue] = "それでも開始する", [TextId.ModIntegrityButtonClose] = "閉じる", [TextId.LobbyStartTooFewPlayersTitle] = "人数が足りません", [TextId.LobbyStartTooFewPlayersBodyFormat] = "現在 {0}人 / 人狼で遊ぶには最低でも {1}人 必要です\n通常のREPOとして遊びたい場合は、ルーム設定で人狼モード\n(WerewolfModeEnabled)をOFFにしてください。", [TextId.LobbyStartTeamOverflowTitle] = "人狼の人数が多すぎます", [TextId.LobbyStartTeamOverflowBodyFormat] = "人狼の設定 {0}人 / 現在の参加者 {1}人\n村人が1人もいない配役は成立しません。ルーム設定で\n人狼の人数(WerewolfCount)を下げてください。", [TextId.ConveneCountdownDefaultCallerName] = "誰か", [TextId.ConveneCountdownHeaderFormat] = "{0}が会議を招集しました!\nワープまで残り…", [TextId.ConveneCountdownCorpseHeaderFormat] = "{0}が事件現場を通報しました!\nワープまで残り…", [TextId.HudCorpseReportKeyFormat] = "通報 [{0}]", [TextId.MeetingButtonSuppressCountdownFormat] = "緊急会議(あと {0}秒)", [TextId.MeetingButtonConveneGrabPrompt] = "緊急会議を招集 [Grabを長押し]", [TextId.MeetingButtonConveneInteractPrompt] = "緊急会議を招集 [Interactを長押し]", [TextId.MeetingButtonSuppressedPrompt] = "緊急会議(現在は招集不可)", [TextId.MeetingButtonRightsSuffixFormat] = "(残り{0}回)", [TextId.CurseBlackCatRevealedFormat] = "{0}は黒猫でした。道連れを選択中…", [TextId.CurseNoVictim] = "誰も道連れになりませんでした", [TextId.SettingsSectionGeneral] = "基本", [TextId.SettingsSectionMeeting] = "会議", [TextId.SettingsSectionRoleAssignment] = "役職 - 役職配分", [TextId.SettingsSectionRoles] = "役職 - 人狼", [TextId.SettingsSectionBlackCat] = "役職 - 黒猫", [TextId.SettingsSectionBomber] = "役職 - 爆弾魔", [TextId.SettingsSectionShaman] = "役職 - 祈祷師", [TextId.SettingsSectionWorldgen] = "開始環境", [TextId.SettingsSectionStartItemList] = "持ち込みアイテム一覧", [TextId.SettingsSectionStartUpgradeList] = "能力強化(全員)", [TextId.SettingsBoolEnabled] = "有効", [TextId.SettingsBoolDisabled] = "無効", [TextId.SettingsAuto] = "自動", [TextId.SettingsListEmpty] = "なし", [TextId.SettingsPresent] = "あり", [TextId.SettingsRandom] = "ランダム", [TextId.SettingsValuableMapRealtime] = "リアルタイム", [TextId.SettingsValuableMapMeetingSync] = "会議同期", [TextId.SettingsValuableMapHidden] = "非表示", [TextId.SettingsNecroVoiceOff] = "OFF", [TextId.SettingsNecroVoiceNonWerewolfDead] = "人狼以外の死者", [TextId.SettingsNecroVoiceAllDead] = "全死者", [TextId.SettingsItemsAggregateFormat] = "{0}種{1}個({2})", [TextId.SettingsAggregateMoreSuffix] = "…", [TextId.SettingsUpgradeItemFormat] = "{0}+{1}", [TextId.SettingsListSeparator] = "、", [TextId.SettingsUnitSeconds] = "秒", [TextId.SettingsUnitTimes] = "回", [TextId.SettingsUnitItems] = "個", [TextId.SettingsUnitPeople] = "人", [TextId.SettingsUnitMeters] = "m", [TextId.SettingsUnitPercent] = "%", [TextId.SettingsUnitDamage] = "ダメージ", [TextId.SettingsLabelWerewolfCount] = "人狼の人数", [TextId.SettingsLabelBlackCatChancePercent] = "黒猫の出現確率", [TextId.SettingsLabelBomberChancePercent] = "爆弾魔の出現確率", [TextId.SettingsLabelShamanChancePercent] = "祈祷師の出現確率", [TextId.SettingsLabelRoundSeconds] = "ラウンド制限時間", [TextId.SettingsLabelBlackCatRevealDelaySec] = "黒猫自覚の遅延", [TextId.SettingsLabelBlackCatCurseEnabled] = "黒猫の道連れ", [TextId.SettingsLabelMeetingRightsPerPlayer] = "1人あたりの会議開催権", [TextId.SettingsLabelConveneSuppressStartSec] = "開始直後の召集抑止", [TextId.SettingsLabelConveneSuppressAfterSec] = "会議後の召集抑止", [TextId.SettingsLabelMeetingCountdownSec] = "会議開始までの予告時間", [TextId.SettingsLabelMeetingDurationSec] = "会議の制限時間", [TextId.SettingsLabelVoteTimeCutEnabled] = "投票による会議時間短縮", [TextId.SettingsLabelResultDisplaySec] = "開票結果の表示保持", [TextId.SettingsLabelStaminaUnlockPct] = "無限スタミナ解禁閾値", [TextId.SettingsLabelJumpUnlockPct] = "追加ジャンプ解禁閾値", [TextId.SettingsLabelEnemyIgnoreUnlockPct] = "敵認識無効の解禁閾値", [TextId.SettingsLabelHealUnlockPct] = "自然治癒の解禁閾値", [TextId.SettingsLabelHealIntervalSec] = "自然治癒の回復間隔", [TextId.SettingsLabelBeaconChargePct] = "ビーコン補充の閾値", [TextId.SettingsLabelInformantThresholdPct] = "黒猫の人狼開示の閾値", [TextId.SettingsLabelExtraJumpCount] = "滞空中の追加ジャンプ回数", [TextId.SettingsLabelBeaconCooldownSec] = "ビーコンのクールダウン", [TextId.SettingsLabelBeaconSuppressStartSec] = "開始直後のビーコン抑止", [TextId.SettingsLabelBeaconSuppressAfterMeetingSec] = "会議後のビーコン抑止", [TextId.SettingsLabelCatGaugeSyncIntervalSec] = "黒猫ゲージの更新間隔", [TextId.SettingsLabelOrbGaugeEnabled] = "オーブの減額ゲージ算入", [TextId.SettingsLabelWerewolfModeEnabled] = "人狼モード", [TextId.SettingsLabelMinimapHideEnabled] = "死体のミニマップ非表示", [TextId.SettingsLabelValuableMapMode] = "貴重品マップ表示モード", [TextId.SettingsLabelGameOverAutoReturnSec] = "結果画面の自動帰還秒数(0=自動で戻らない)", [TextId.SettingsLabelNecroVoiceMode] = "冥界の声(死者→生存人狼の傍聴)", [TextId.SettingsLabelStartLevelNumber] = "レベル", [TextId.SettingsLabelStartMapName] = "マップ", [TextId.SettingsLabelStartItemsSpec] = "持ち込みアイテム", [TextId.SettingsLabelStartEnergyPct] = "トラックの充電器", [TextId.SettingsLabelStartUpgradesSpec] = "開始時の能力強化", [TextId.SettingsLabelOrbDropMax] = "敵が落とすオーブの数", [TextId.SettingsLabelBomberProximityMeters] = "濃厚接触判定距離", [TextId.SettingsLabelBomberGaugeFullSec] = "濃厚接触判定秒", [TextId.SettingsLabelBomberInitialCooldownSec] = "開始直後クールダウン", [TextId.SettingsLabelBomberCooldownSec] = "通常クールダウン", [TextId.SettingsLabelBomberBlastRadiusMeters] = "爆風・警告音半径", [TextId.SettingsLabelBomberBlastPlayerDamage] = "プレイヤーへのダメージ", [TextId.SettingsLabelBomberBlastEnemyDamage] = "敵ダメージ", [TextId.SettingsLabelBomberAmmoRefillPct] = "弾1発の補充閾値", [TextId.SettingsLabelShamanGazeFullSec] = "霊視の注視秒数", [TextId.SettingsLabelShamanGhostCooldownSec] = "霊視クールダウン", [TextId.SettingsLabelShamanStormWeakMeters] = "霊障(弱)半径", [TextId.SettingsLabelShamanStormMediumMeters] = "霊障(中)半径", [TextId.SettingsLabelShamanStormStrongMeters] = "霊障(強)半径", [TextId.HudBomberPlantKeyFormat] = "爆弾にする [{0}]", [TextId.HudBomberDetonateKeyFormat] = "起爆 [{0}]", [TextId.HudValuableRecordOnFormat] = "記録する [{0}長押し]", [TextId.HudValuableRecordOffFormat] = "記録しない [{0}長押し]", [TextId.BomberDenyNoAmmo] = "残弾がありません", [TextId.BomberDenyNoFullTarget] = "爆弾にできる対象がいません", [TextId.BomberDenyPlantCooldown] = "爆弾にできません(クールダウン中)", [TextId.BomberDenyDetonateCooldown] = "起爆できません(クールダウン中)", [TextId.BomberDenyNoBomb] = "起爆できません(爆弾なし)", [TextId.BomberDenyMeetingLocked] = "起爆できません(会議中)", [TextId.BomberDenyTruckZone] = "起爆できません(対象がトラック付近)", [TextId.BomberDudTargetDead] = "対象者は既に死亡していたので不発に終わりました", [TextId.BomberTargetDisconnected] = "対象者が切断したため爆弾は消滅しました", [TextId.BomberProximityWarning] = "爆弾を仕掛けられたかもしれない…", [TextId.TutorialCorpseDiscovery] = "未発見の死体を発見した。Good Job!\ud83d\ude02\n死者は蘇らないから抽出場所に運ぶ必要はない。\n未発見の死体の近くでは、開催権に関係なく通報ができる(右下HUDのアイコンが色付きで脈動する)。\nどんな状況で死んでいたか、会議で皆に共有すること。", [TextId.TutorialMeetingCountdownStarted] = "会議が招集された。トラックへワープするまでのカウントダウンだ。\n手に持っている貴重品・武器・ドローンは、その場に残される(インベントリの中身は持ち帰れる)\n貴重品を落として傷つけたり、通路をふさいでモンスターに壊されないよう急いで片付けること。", [TextId.TutorialFirstMeetingAsVillager] = "会議中は、貴重品の位置と現存状況が更新された全体マップを見られる。\n状況を確認し、情報を交換し、誰が怪しいか、誰が怪しくないか報告し合うこと。\n怪しい奴には投票しろ。処刑できる。", [TextId.TutorialWerewolfRoleDrawn] = "お前は人狼だ。おめでとう\ud83d\ude02\n貴重品を壊すほど、強力な能力が解禁される。\n村人にバレないように貴重品を壊し、特典を受け取ること。\n人狼にだけは、マップにモンスターの位置が表示される。うまく使え。", [TextId.TutorialFirstValuableSeen] = "貴重品を発見した。\nマップに位置が登録されるが、その後に移動・破壊されてもマップは更新されない。\n更新されるのは、会議の開始時と抽出の完了時だけだ。\nマップにあるはずの貴重品がそこに無いなら、誰かが運び去ったか、壊したということだ。", [TextId.TutorialWolfModeFirstUnlock] = "十分な額の貴重品が破壊された。狼化が解禁だ。\n狼化中は強力な能力が使える。\n村人の前でうっかり能力を使うな。狼化はオフにもできる。", [TextId.TutorialBeaconFirstCharged] = "十分な額の貴重品が破壊された。ビーコンが解禁だ。\nビーコンは、マップの広い範囲からモンスターを現在位置へ誘導する。倒されて消えたモンスターも呼び戻す。\nモンスターが殺到している場所にいた奴は発信源だと怪しまれる。使う場所を選ぶこと。\nマップでモンスターの位置を確認しろ。自分が巻き込まれても知らないぞ。", [TextId.TutorialFirstMeetingAsWerewolf] = "会議中はマップの貴重品情報が更新され、様々な情報が全員に共有される。\n人狼が処刑されると、人狼陣営は不利になる。\n言い訳をしろ。潔白を装え。無意味な情報を話し、大事な情報は隠せ。嘘で怪しい奴を増やせ。\n健闘を祈る\ud83d\ude02", [TextId.TutorialFirstMeetingAsBlackCat] = "黒猫は会議で処刑されると、自分に投票した奴の中から誰か1人を道連れにできる。\n道連れにしたい相手が決まっているなら、そいつの票が自分に入るよう誘導すること。\n仲間の人狼も、黒猫に投票していれば道連れ対象に入る。連れて行く相手は慎重に選べ。", [TextId.TutorialVillagerSeesCatAwakened] = "業務連絡だ。この村には人狼陣営に黒猫がいるかもしれない。\n黒猫は会議で処刑されると、黒猫に投票した者の中から1人を道連れに殺せる。\n会議で処刑するか。反撃とリソース消費を覚悟のうえで、実力行使で排除するか。\n慎重に決めること。", [TextId.TutorialBlackCatRoleDrawn] = "お前は黒猫だ。おめでとう\ud83d\ude02\n会議の投票で処刑されると、黒猫に投票した奴の中から誰か1人を道連れにできる。\n村人のフリで忙しい人狼の代わりに、大暴れしてサポートしろ。\nただし、誰が人狼かは知らされない。うっかり人狼を道連れにしないこと。\n黒猫は人狼陣営だが、人狼の生存数には数えない。人狼が全滅すれば、黒猫が生きていても村人陣営の勝ちだ。", [TextId.TutorialBlackCatRoleDrawnNoCurse] = "お前は黒猫だ。おめでとう\ud83d\ude02\n黒猫は人狼陣営だが、人狼と黒猫はお互いの正体を知らされない。\n村人のフリで忙しい人狼の代わりに、大暴れしてサポートしろ。\n黒猫は人狼の生存数には数えない。人狼が全滅すれば、黒猫が生きていても村人陣営の勝ちだ。", [TextId.TutorialLastRunApproaching] = "最終抽出指令が出た。これ以降、死体通報は受理されない。\n会議はトラックの会議ボタンからのみ開ける。\n誰をトラックに残し、誰に最後の抽出を任せるか慎重に決めろ。", [TextId.TutorialRoundTimeWarningVillager] = "間もなく納期だ。\n納期までに全ての抽出を終え、トラックを発車させろ。できなければ村人の負けだ。\n間に合わないなら、人狼を全滅させるしかない。", [TextId.TutorialRoundTimeWarningWerewolf] = "間もなく納期だ。\n納期までにトラックの発車を阻止すれば、人狼の勝ちだ。\n最後まで気を抜くな。", [TextId.TutorialFinalExtractionVillager] = "最終抽出を完了した。\nトラックの発車時に村人が1人でも生きていれば勝利だ。\n最後まで気を抜くな。", [TextId.TutorialFinalExtractionWerewolf] = "最終抽出が完了してしまった。\nトラックの発車時に村人が生きていれば人狼の負けだ。\nこうなったら、村人を全滅させるしかない。", [TextId.TutorialInformantUnlockedAsWerewolf] = "もし黒猫がいたらの話だが、黒猫は誰が人狼か知ったようだ。\nどこかでこっそり接触できるように動くと良いだろう。\nそれか、会議で向こうから仕掛けてくるかもしれない。うまく話を合わせろ。", [TextId.TutorialInformantUnlockedAsBlackCat] = "十分な額の貴重品が破壊された。人狼が誰か分かるようになった。\n名前が赤く見えたり、会議の時に狼のマークがついているのが人狼だ。\n村人にバレないようにこっそり人狼に自分が黒猫だとアピールしろ。", [TextId.TutorialEnemyIgnoreUnlockedAsWerewolf] = "十分な額の貴重品が破壊された。モンスターはお前を仲間だとみなすようになった。\n狼化が有効な間は、ほとんどのモンスターはお前を見ても反応しなくなるぞ。\n音には反応するし攻撃の巻き添えは食らうがな。\nちなみに、大きな音を出すと消えたモンスターが早く復活することはもう知っているか?", [TextId.TutorialNaturalHealUnlockedAsWerewolf] = "十分な額の貴重品が破壊された。お前の肉体は人間離れしつつある。\n狼化が有効な間、体力が少しずつ自然に回復するようになった。\n光ったり音が出たりはしないが、背中の体力ゲージは他のプレイヤーからも見える。\nさっきまで瀕死だったのにいつの間にか回復していたら、言い逃れは難しいだろう。", [TextId.TutorialWerewolfSeesCatAwakened] = "業務連絡だ。この村には人狼陣営に黒猫がいるかもしれない。\n人狼と黒猫は仲間同士だが、人狼からは誰が黒猫かは分からない。\n黒猫は人狼陣営だが、人狼の生存数には数えない。(人狼が全滅すれば、黒猫の生死に関わらず村人陣営の勝利)", [TextId.TutorialBeaconFirstUsedAsWerewolf] = "ビーコンを使用したな。\nマップ中のモンスターがビーコンを使った場所へ集まってくるだろう。\nビーコンが何回使われたかは会議で明かされるから、村人に推理材料を与えないように気をつけろ。", [TextId.TutorialBlackCatSelectedForExecution] = "処刑対象に選ばれたな。Good Job!\ud83d\ude02\n誰を道連れにするか選べ。\n選ばないとランダムで選ばれる。", [TextId.TutorialBlackCatExecutionRevealed] = "最多票を集めたのは黒猫だった。\n黒猫が処刑されると、黒猫に投票したプレイヤーの中から誰か1人が道連れにされる。", [TextId.TutorialFirstDeath] = "お前の身体は破壊され、今やクラウドにアップロードされた精神データだけの存在だ。\n試合の間は新しい身体が支給されることはない。\n死体の頭を使って生きている連中と話すこともできない。", [TextId.TutorialBomberRoleDrawn] = "お前は爆弾魔だ。おめでとう\ud83d\ude02\n誰かとしばらく一緒に過ごすと、そいつを人間爆弾に変えられるようになる。\n好きなタイミングで起爆できる。爆弾にされた本人も少しはダメージを受けるが、HP1で耐え、吹き飛ばない。\n自分が巻き込まれたら即死する。距離を取ってから起爆しろ。", [TextId.TutorialBombPlantedAsBomber] = "上手く爆弾を取り付けたな。Good Job!\ud83d\ude02\nあとは自分が巻き込まれないように気をつけながら起爆するだけだ。\n取り付けた相手がトラック周辺にいたら起爆させないぞ。トラックが傷つくからな。", [TextId.TutorialBomberProximityWarnedAsVillager] = "貴重品を効率よく差し押さえるためには他の誰かとの協力が必要なこともあるだろう。\nしかし、爆弾魔はしばらく近くで過ごした他人に爆弾を仕掛けることができる。\n後で自分が爆弾にされていたと分かったときのために、誰と一緒に仕事していたか覚えておくんだな。", [TextId.TutorialSelfBombExplodedAsVillager] = "お前は爆弾魔に爆弾を仕掛けられていたようだ。\n爆発の中心にいたため傷ついたが、爆弾そのものでは死なず、吹き飛ばされることもない。\n爆弾魔はしばらく近くで過ごした他人を爆弾に変え、好きなタイミングで起爆できる。\n爆弾魔自身はその爆発に巻き込まれると即死する。\n爆弾魔が取り付けられる爆弾は同時に一つまでだ。", [TextId.TutorialShamanRoleDrawn] = "お前は祈祷師だ。\nしばらく立ち止まり、視線を止めると霊視ができる。\n未周知の死体の近くにいるときは霊障が出る。\nお前が死体にならないように気をつけろ。", [TextId.TutorialShamanGhostSighted] = "霊視が反応した。\n今向いていた方向に未周知の死体があるということだ。", [TextId.TutorialShamanTranceEntered] = "水滴が聞こえた。\nこれは「今向いている方向に未周知の死体はない」という合図だ。", [TextId.TutorialShamanStormEntered] = "霊障が発生した。\n近くに未周知の死体がある。霊障が強いほど、死体は近い。", [TextId.TutorialEquipBlockedByOtherGrabber] = "他人が掴んでいるアイテムはインベントリに入れられない。\n奪いたいなら、近接武器で殴るなりして落とさせてから拾え。", [TextId.TutorialValuableRecordSuppressed] = "貴重品を見つけたが、地図には記録しなかった。\n人狼陣営は既定で貴重品を発見しない。誰にも知られていない貴重品なら、\n壊しても「あったはずの物が無い」と気づかれずに済む。\n記録したくなったら、通報キーの長押しで切り替えろ。", [TextId.ManualToggleLabelFormat] = "説明書 [{0}]", [TextId.ManualPageFooterFormat] = "{0}\u3000\u3000{1} / {2}", [TextId.ManualNavHint] = "← → ページ送り\u3000\u3000Shift+← → 章送り\u3000\u3000{0} または [menu] で閉じる", [TextId.ManualSectionBasics] = "基本ルール", [TextId.ManualSectionExploration] = "探索と戦闘", [TextId.ManualSectionMeeting] = "会議と投票", [TextId.ManualSectionGauge] = "貴重品減額ゲージ", [TextId.ManualSectionVillager] = "村人", [TextId.ManualSectionShaman] = "祈祷師", [TextId.ManualSectionWerewolf] = "人狼", [TextId.ManualSectionBlackCat] = "黒猫", [TextId.ManualSectionBomber] = "爆弾魔", [TextId.ManualSectionAfterDeath] = "死亡後", [TextId.ManualWelcomeTitle] = "REPO人狼へようこそ", [TextId.ManualWelcomeBody] = "REPO人狼では、プレイヤーは村人陣営と人狼陣営に分かれ、それぞれの勝利を目指して水面下で争います。\n村人陣営の目標は、通常のREPOと同じです。マップから貴重品を集めて全ての抽出を完了させ、トラックを出発させることです。\n人狼陣営の目標は、正体を隠したままそれを妨害することです。\n誰が誰を疑い、誰が誰と手を組んでいるのか?\n疑心暗鬼の中から真実を見つけ出せるでしょうか。それとも、偽りを真実だと信じ込ませられるでしょうか?\n安易な答えに飛びついて同士討ちを始めたとき、プレイヤー自身が最凶のモンスターと化すでしょう。", [TextId.ManualGameFlowTitle] = "ゲームの流れ", [TextId.ManualGameFlowBody] = "REPO人狼を遊ぶには最低3人のプレイヤーが必要です。\nホストのルーム設定から、好きなステージやレベル、持ち込むアイテムやプレイヤーのアップグレード状況などを調整できます。\n既存のセーブデータをロードして試合を開始すると、そのデータは試合用の状態に上書きされて消去されるので注意してください。\nショップや次のレベルという概念はなく、1つのレベルで1試合が完結します。\nどちらかの陣営の勝利が確定すると試合結果が表示され、そのままロビーに戻ります。\nまた、試合中はCosmetic Boxが出現しません。その代わり、一定の条件を満たすと、試合終了後にトークンを獲得できます。", [TextId.ManualVillagerWinTitle] = "村人陣営の勝利条件", [TextId.ManualVillagerWinBody] = "村人陣営の勝利条件は次の2つです。どちらかを満たせば勝利します。\n・全ての抽出を完了させ、村人陣営が1人以上生存した状態でトラックを出発させる\n・人狼陣営が全滅する(黒猫が生き残っていても勝利できます)\nトラックを発車させた人物の陣営は勝敗に影響しません。発車時の村人陣営の生存状況によって決まります。\nつまり、通常のREPOと同じようにゲームクリアを目指せば勝利に近づきます。\nただし人狼陣営はあの手この手で妨害してくるため、普通のプレイとは違う戦略が必要になるでしょう。", [TextId.ManualWerewolfWinTitle] = "人狼陣営の勝利条件", [TextId.ManualWerewolfWinBody] = "人狼陣営の勝利条件は次の3つです。いずれかを満たせば勝利します。\n・場に出ている全ての貴重品を集めてもノルマを達成できない状態(詰み)にする\n・制限時間切れまでトラックの発車を阻止する\n・村人陣営が全滅する\n「詰み」が成立すると、その瞬間に人狼陣営の勝利が確定します。\n最終抽出が完了していても、トラックの発車までに村人陣営を全滅させれば人狼陣営の勝利です。\n人狼がトラックを発車させ、置き去りによって村人陣営が全滅した場合もこれに含まれます。", [TextId.ManualValuablesMapTitle] = "貴重品とマップ", [TextId.ManualValuablesMapBody] = "誰かが貴重品を発見したり、敵がオーブを落としたりすると、マップに黄色いマーカーが登録されます。\n通常のREPOと違い、標準設定ではこのマーカーはリアルタイムに更新されません。\n更新されるのは、抽出を完了したときと会議を開いたときだけです。\nマーカーの場所に現物が無い場合、誰かが運んだか、壊されたかのどちらかです。\nマップからは区別できません。\n注意深い村人は、会議のたびに「あの貴重品はどこへ行ったのか」を棚卸しして推理します。", [TextId.ManualValuableRecordTitle] = "貴重品を記録しない(人狼陣営)", [TextId.ManualValuableRecordBody] = "貴重品マーカーがマップに登録されるのは「誰かがその貴重品を視界に捉えた」瞬間です。\n人狼陣営(人狼・爆弾魔・自覚した黒猫)は、この記録を既定で行いません。\nそのため、まだ誰にも見つかっていない貴重品を人狼陣営が先回りして壊した場合、マップには最初から何も残りません。\n逆に、村人が先に見つけて記録済みの貴重品を壊せば、マーカーだけが残り「あったはずの物が無い」と気づかれます。", [TextId.ManualValuableRecordToggle] = "記録するかどうかは、通報キーの長押しでいつでも切り替えられます(右下のアイコンが現在の状態です)。\n村人に紛れて探索するときなど、必要に応じて記録をONにしましょう。", [TextId.ManualCombatTitle] = "PvP", [TextId.ManualCombatBody] = "通常のREPO本編と違い、人狼モードでは近接武器でプレイヤーにダメージを与えられ、武器のエネルギーも消費されます(スマブラ風アリーナと同様)。\n致命的なダメージを瀕死で耐える(いわゆる根性・気合・踏ん張り)救済措置もありません。\n近接武器には武装解除の能力が付与されています。\n近接武器で殴られると手で掴んでいるものを落とす上に、インベントリの所持品が一つ外に飛び出します。\n逆に、他のプレイヤーが掴んでいるアイテムを掴んでインベントリに入れることはできません。奪うには殴って叩き落とす必要があります。\nつまり、先に攻撃を当てた側が有利です。背中を預ける相手は慎重に選びましょう。", [TextId.ManualEndgamePrepTitle] = "最終局面への備え", [TextId.ManualEndgamePrepBody] = "試合終盤、会議による決着が難しくなると、人狼陣営と村人陣営が直接対決する最終局面に移ることがあります。\n最終局面での戦いやすさは、それまでに各陣営が築いた状況によって変化します。\n村人陣営は、危険な武器を確保・没収して人狼の手に渡るのを防ぐほか、信頼できる仲間を守り、回復手段を残しておくことで有利に戦えます。\n人狼陣営は、貴重品を破壊して特典を解禁するほか、信頼を得ている村人を殺害し、村人同士の連携を崩すことで優位を築けます。\n最後にぶつかるのは、両陣営が試合を通して築いてきた優位性です。\n終盤の展開も見据え、序盤から準備を進めましょう。", [TextId.ManualCorpseTitle] = "死体について", [TextId.ManualCorpseBody] = "REPO人狼では、どんな方法でも死者は蘇生できません。\nまた、死体の位置はマップに表示されません。死体は自分の目で見つける必要があります。\nまだ会議で周知されていない死体の近くで通報キー(画面右下に表示されます)を押すと、その場から会議を開催できます。", [TextId.ManualConveneTitle] = "会議の開き方", [TextId.ManualConveneBody] = "会議を開く方法は2つあります。\n・トラック最奥にある赤いボタンを掴んで長押しする\n・未周知の死体の近くで通報キーを押す\nボタンで会議を開ける回数は1人1回までです(ルーム設定で変更できます)。\nゲーム開始直後と会議終了直後のしばらくは、ボタンで会議を開けません。\nまた、未完了の抽出地点が最後の1箇所になると、死体通報による会議の開催はできなくなります(例: 抽出ノルマが4箇所なら、3箇所目の完了以降は通報不可)。\nボタンでの会議開催は最後まで可能です。", [TextId.ManualMeetingFlowTitle] = "会議の流れ", [TextId.ManualMeetingFlowBody] = "会議が始まると、全員がトラックへワープして身動きが取れなくなります。\n会議中は敵が消え、新たに現れることもないので安全です。\nまず前回の会議以降の死亡者が発表され、続いて貴重品減額ゲージの変化が開示されます。\nその後、投票が始まります。会議中は全体マップを開いて、更新された貴重品の情報を確認できます。\n試合の制限時間は会議中は停止するので、議論で持ち時間は減りません。\nただし、会議中にも敵のリスポーンタイマーは減っていくので、会議で時間を浪費しすぎると敵が復活してしまいます。\n会議終了時点でリスポーンの準備が整っている敵は即座にスポーンします。", [TextId.ManualVotingTitle] = "投票と処刑", [TextId.ManualVotingBody] = "会議では生存者だけが1人1票を投じます。スキップ(誰にも投票しない)も選べます。\n誰が投票を済ませたかは全員に見えますが、誰に投票したかは分かりません。\n最も票を集めたプレイヤーが処刑されます。最多票が同数の場合は誰も処刑されません。\n会議には制限時間があり、誰かが投票するたびに残り時間が少し減ります。", [TextId.ManualGaugeBasicsTitle] = "貴重品減額ゲージの見方", [TextId.ManualGaugeIntro] = "貴重品減額ゲージは、村人陣営と人狼陣営の貴重品の奪い合いを視覚化したものです。試合開始時点でマップにある貴重品の総額が基準になります。", [TextId.ManualGaugeLoss] = "黄色のゲージ(左から伸びる)…傷つき、失われた金額", [TextId.ManualGaugeDelivery] = "水色のゲージ(右から伸びる)…抽出を完了して納品した金額", [TextId.ManualGaugeLines] = "・青い縦線…ノルマ達成に必要な金額。水色のゲージがここに届けば、トラックの発車が可能になります\n・赤い縦線…場に出ている貴重品を全て集めてもノルマを達成できなくなる「詰み」のライン。黄色のゲージがここに達すると、その瞬間に人狼陣営が勝利します\n敵がオーブを落としたり、新たな貴重品が場に増えたりすると、赤い縦線は右へ移動します(村人側の余裕が増えます)。人狼陣営の詰み勝利が確定した後に移動しても覆りません。", [TextId.ManualRoleVillagerTitle] = "役職: 村人", [TextId.ManualRoleVillagerBody] = "所属陣営:村人陣営\n村人に特殊な能力はありません。通常のREPOプレイヤーとして貴重品を集め、抽出を目指します。\n最大の武器は観察と議論です。貴重品マップの棚卸し・死体の発見・他プレイヤーの行動の記憶を持ち寄り、会議で人狼をあぶり出しましょう。", [TextId.ManualRoleWerewolfTitle] = "役職: 人狼", [TextId.ManualRoleWerewolfIntro] = "所属陣営:人狼陣営\n人狼(役職)は人狼陣営の中心となる役職で、最低1人は必ず配役されます。\n貴重品が傷つくほど強くなります。\n誰が傷つけたかに関係なく、貴重品の減額が一定額に達するたびに特典が順番に解禁されます。", [TextId.ManualRoleWerewolfEnemyMap] = "人狼(役職)だけは、マップで敵の位置が分かります。", [TextId.ManualRoleWerewolfPerksTitle] = "役職: 人狼 — 特典(1/2)", [TextId.ManualRoleWerewolfPerkStamina] = "スタミナ無限…ダッシュや壁捕まりでスタミナが減らない", [TextId.ManualRoleWerewolfPerkJump] = "追加ジャンプ…滞空中に多段ジャンプが可能(回数はルーム設定次第)", [TextId.ManualRoleWerewolfPerksTitle2] = "役職: 人狼 — 特典(2/2)", [TextId.ManualRoleWerewolfPerkEnemyIgnore] = "敵無視…ほとんどの敵に狙われなくなる(ただし音には反応されます)", [TextId.ManualRoleWerewolfPerkHeal] = "自然治癒…狼化中、時間経過で体力が少しずつ回復する(回復エフェクトは出ませんが、背中の体力ゲージは他のプレイヤーからも見えます)", [TextId.ManualRoleWerewolfPerkToggle] = "特典は狼化キーでON/OFFできます。村人に特典の使用を見られると正体がバレるので注意。", [TextId.ManualRoleWerewolfBeaconTitle] = "役職: 人狼 — ビーコン", [TextId.ManualRoleWerewolfBeaconBody] = "ビーコンは人狼(役職)の特殊能力です。貴重品を一定額傷つけるたびに使用回数がチャージされます。\n使用すると、プレイヤーには聞こえない音を発してマップ中の敵をその場所へ呼び寄せます。倒されて消えているモンスターも呼び戻します(呼び戻しには人狼陣営で共有のクールダウンがあります)。\nはぐれたプレイヤーを\"事故死\"させたり、村人の集団に敵をけしかけてパニックにさせたりできます。\nただし、オーブの供給を増やしてノルマ達成に近づけてしまったり、自分自身が戦闘に巻き込まれるリスクもあります。\n使用したこと自体はその場では通知されませんが、次の会議で「前回の会議以降にビーコンが何回使われたか」が全員に公開されます。", [TextId.ManualRoleBlackCatTitle] = "役職: 黒猫", [TextId.ManualRoleBlackCatIntro] = "所属陣営:人狼陣営(勝敗のみ共有)\n元となる役職:村人\nルーム設定により、村人の中から割り当てられます。\n試合開始時には本人にも「村人」と伝えられ、しばらくしてから正体を自覚します。\n能力は村人と同等です。ルーム設定で道連れが有効な場合、会議で処刑されると、自分に投票したプレイヤーの中から1人を選んで道連れにします。選ばなかった場合はランダムに決まります。\n人狼からは誰が黒猫か分からず、黒猫からも最初は誰が人狼か分かりません。", [TextId.ManualBlackCatInformantTitle] = "役職: 黒猫 — 内通", [TextId.ManualRoleBlackCatInformant] = "特典「内通」…貴重品が一定額傷つくと、黒猫にだけ人狼陣営のメンバーが表示されるようになります。", [TextId.ManualRoleBlackCatGaugeNote] = "黒猫にも貴重品減額ゲージが表示されますが、更新はリアルタイムではなく一定間隔です。", [TextId.ManualBlackCatCounterTitle] = "黒猫への対処法", [TextId.ManualBlackCatCounterBody] = "ルーム設定で有効な場合、黒猫の道連れは「投票による処刑」のときにだけ発動します。\nつまり、黒猫か疑わしいときは安易に処刑してはいけません。処刑すると、黒猫に投票したプレイヤーの誰かが道連れになります。\nどうしても処刑する場合は、道連れになる覚悟のある人だけが投票しましょう。\n武器や落下など、処刑以外の方法で死亡した場合、道連れは発動しませんがPvPに発展するリスクもあります。\n合意を経ない私刑を行えば、他の村人には正当な理由があったのか判断できません。\n吊るほどの確証がない相手は、武器を没収して様子を見るのも一つの手です。武器がなければ、たとえ人狼陣営でも簡単には人を殺せません。", [TextId.ManualRoleBomberTitle] = "役職: 爆弾魔", [TextId.ManualRoleBomberIntro] = "所属陣営:人狼陣営\n元となる役職:人狼\nルーム設定により、人狼(役職)の中から割り当てられます。\n近くで一緒に過ごしたプレイヤーを爆弾に変え、好きなタイミングで起爆できる役職です。", [TextId.ManualBomberPlantTitle] = "役職: 爆弾魔 — 爆弾にする", [TextId.ManualRoleBomberPlant] = "試合開始直後と会議のあとは「爆弾にする」にクールダウンがあり、明けてから他のプレイヤーの近くに居続けると、そのプレイヤーに黄色いメーターが溜まっていきます(壁越し無効、離れると減少)。\nメーターが満タンになり緑色になったら、画面左下の「爆弾にする」キーでそのプレイヤーを爆弾に変えられます。\n爆弾にされたプレイヤーは、爆弾魔にだけ見える爆弾アイコンが付きます。本人は爆弾にされたことに気づきません。\n爆弾魔自身を爆弾にすることはできませんが、人狼や黒猫を爆弾にすることもできます。\n爆弾にできるのは同時に1人まで。起爆前なら別のプレイヤーに付け替えられます。", [TextId.ManualRoleBomberDetonateTitle] = "役職: 爆弾魔 — 起爆", [TextId.ManualRoleBomberDetonateBody] = "「起爆」のクールダウンが明けてからキーを押すと、爆弾にしたプレイヤーを中心に爆発が起きます。\n周囲のプレイヤー・貴重品・敵が巻き込まれます。爆弾にされた本人も少しダメージを受けますが、体力が1を下回ることはなく、吹き飛ばされません。\n爆風に爆弾魔自身が巻き込まれると即死します。\n対象がトラックの近くにいる間は起爆できません。\n対象が死亡した状態で起爆すると不発になり、爆弾は失われます。\n爆弾の残数は、貴重品が一定額傷つくたびに補充されます。", [TextId.ManualRoleShamanTitle] = "役職: 祈祷師", [TextId.ManualRoleShamanIntro] = "所属陣営:村人陣営\n元となる役職:村人\nルーム設定により、村人の中から割り当てられます。\n会議でまだ発表されていない「未周知の死体」の気配を感じ取れる役職です。\n死体がある方向や、死亡した時期を推測できます。\nただし霊視には立ち止まる必要があるため隙が大きく、霊障が発生すると視界も悪くなります。\nミイラ取りがミイラにならないよう、注意してください。", [TextId.ManualShamanSenseTitle] = "役職: 祈祷師 — 霊視と霊障", [TextId.ManualShamanGhost] = "霊視は遠くの死体がある方向を察知できる能力です。\nその場に立ち止まり、視線を一点に据えると視界が色褪せて、霊視が始まります。\n霊視中に一定間隔で聞こえる水滴の音は「その方向に未周知の死体はない」の合図です。\n未周知の死体がある方向を数秒間見つめ続けると、画面が一瞬強く乱れます。\n壁や距離の制限はなく、一度画面が乱れるとしばらくの間は霊視が反応しなくなります。試合開始直後も、しばらくは霊視が反応しません。\n未周知の死体が複数ある場合、自分から最も近い死体だけを対象とし、それより遠い死体には反応しません。\n移動したり視線を大きく動かすと霊視は中断します。死体が近くて霊障が発生している間は霊視ができません。\n視界が色褪せるのは、霊視が働いている間だけです。\n会議で発表済みの死体には反応しません。", [TextId.ManualShamanStorm] = "霊障は近くの死体の距離を察知できる能力です。\n未周知の死体の近くにいると霊障が発生し、その強さは距離に応じて弱・中・強の3段階に変化します。\n未完了の抽出地点が最後の1箇所になると、霊障による視界の歪みは消え、音だけになります。\n会議で発表済みの死体には反応しません。", [TextId.ManualAfterDeathTitle] = "死んでしまったら", [TextId.ManualAfterDeathBody] = "死亡すると観戦モードになり、そのまま試合の行方を見届けることになります。\n死者同士では会話できますが、通常のREPOのようにDeathHeadなどを用いて生存者に情報を伝える手段は一切ありません。\nルーム設定次第では人狼陣営にエコーがかった声で死者の声が聞こえます。\n勝敗は陣営で共有されるため、死亡していても所属陣営が勝てば勝利です。" }; private static readonly IReadOnlyDictionary EnglishTable = new Dictionary { [TextId.NoticeConveneStartedFormat] = "{0} called an emergency meeting", [TextId.NoticeBeaconAuditFormat] = "Beacon uses since the last meeting: {0}", [TextId.NoticeBeaconAuditNone] = "The beacon has not been used since the last meeting", [TextId.NoticeNoExecution] = "No one was executed", [TextId.NoticeExecutedFormat] = "{0} was executed", [TextId.NoticeBlackCatRevealedFormat] = "{0} was the Black Cat", [TextId.NoticeCurseVictimFormat] = "{0} was taken down with the Black Cat", [TextId.NoticeCatAwakened] = "If there is a Black Cat, they should be awake by now…", [TextId.NoticeConveneDeniedNoRight] = "Cannot call a meeting (no calls remaining)", [TextId.NoticeConveneDeniedSuppressed] = "Cannot call a meeting (currently suppressed)", [TextId.NoticeConveneDeniedWrongPhase] = "Cannot call a meeting (not available right now)", [TextId.NoticeConveneDeniedOther] = "Cannot call a meeting", [TextId.NoticeCorpseReportStartedFormat] = "{0} found a dead body", [TextId.NoticeMeetingCancelledExtraction] = "The body report was canceled because the final extraction is imminent", [TextId.NoticeConveneDeniedLastRun] = "Cannot call a meeting (body reports are disabled this close to the final extraction)", [TextId.NoticeConveneDeniedNoCorpse] = "Cannot call a meeting (no body to report)", [TextId.NoticePlayerDisconnectedFormat] = "{0} has disconnected from the game", [TextId.NoticeConveneHoldHint] = "Hold the button to call an emergency meeting", [TextId.RevealTeammatePrefix] = "Fellow werewolves: ", [TextId.RevealHeadingWinCondition] = "◆ Win Conditions (meet any one)", [TextId.RevealHeadingAbility] = "◆ Your Abilities", [TextId.RevealVillagerTitleMaybeCat] = "You may be a Villager…", [TextId.RevealVillagerTitle] = "You are a Villager", [TextId.RevealVillagerWinCondition1] = "Complete every extraction and send the truck on its way", [TextId.RevealVillagerWinCondition2] = "Eliminate the werewolf team", [TextId.RevealVillagerFlavor] = "No special abilities. Recover the valuables and stand up to the werewolves in meetings.", [TextId.RevealWolfTeamWinCondition1] = "Make the final extraction impossible even if every available valuable were collected", [TextId.RevealWolfTeamWinCondition2] = "Keep the truck from departing until time runs out", [TextId.RevealWolfTeamWinCondition3] = "Eliminate the villager team", [TextId.RevealWolfAbility1] = "Gain stronger perks as more valuables are damaged", [TextId.RevealWolfAbility2] = "See enemy locations on the map", [TextId.RevealWolfAbility3] = "Use the beacon to lure enemies to your current location", [TextId.RevealBomberAbility1] = "Turn another player into a bomb after spending enough time near them", [TextId.RevealBomberAbility2] = "Detonate whenever you like and destroy the surroundings. The bomb carrier also takes damage, but survives with 1 HP", [TextId.RevealBomberAbility3] = "If you are caught in that blast, you die instantly", [TextId.RevealBlackCatAbility] = "When chosen for execution, take one of the players who voted for you down with you", [TextId.RevealBlackCatNoCurse] = "You and the werewolves do not know each other's identities, but win or lose together as the werewolf team", [TextId.RevealWerewolfTitle] = "You are a Werewolf", [TextId.RevealBlackCatTitle] = "You are the Black Cat", [TextId.RevealBlackCatAwakeningTitle] = "You were the Black Cat all along.", [TextId.RevealBomberTitle] = "You are the Bomber", [TextId.RevealShamanTitle] = "You are the Shaman", [TextId.RevealShamanAbility1] = "Stand still and hold your gaze steady as your vision fades and spirit vision begins. Keep looking toward an unreported body to trigger a haunting.", [TextId.RevealShamanAbility2] = "The closer you get to an unreported body, the stronger the haunting on your screen", [TextId.RevealHeadingTips] = "◆ Tips", [TextId.RevealVillagerTipConvene] = "Open a meeting by holding the red button at the back of the truck", [TextId.RevealVillagerTipReport] = "Press the report key near a body to call a meeting", [TextId.RevealVillagerTipValuableMap] = "The map's data on valuables is updated at every meeting", [TextId.RevealVillagerTipAliveCheck] = "Meetings show whether each player is alive or dead", [TextId.RevealSkipHint] = "[menu] to skip", [TextId.RoleNameWerewolf] = "Werewolf", [TextId.RoleNameBlackCat] = "Black Cat", [TextId.RoleNameVillager] = "Villager", [TextId.RoleNameBomber] = "Bomber", [TextId.RoleNameShaman] = "Shaman", [TextId.GaugePerkStaminaLabel] = "Infinite Stamina", [TextId.GaugePerkJumpLabel] = "Extra Jumps", [TextId.GaugePerkEnemyIgnoreLabel] = "Monster Camouflage", [TextId.GaugePerkHealLabel] = "Regeneration", [TextId.GaugePerkInformantLabel] = "Werewolves Revealed to Black Cat", [TextId.GaugeBeaconRuleFormat] = "Beacon +1/{0}%", [TextId.GaugeBombRuleFormat] = "Bomb +1/{0}%", [TextId.GaugePercentDollarsFormat] = "{0}% (${1})", [TextId.GaugeLossOverBaseFormat] = "${0} / ${1}", [TextId.GaugeNextUpdateFormat] = "Next update in {0}s", [TextId.HudTimerFrozenFormat] = "{0} paused", [TextId.HudGaugeFormat] = "Gauge {0}%", [TextId.HudRightsFormat] = "Meeting calls left: {0}", [TextId.HudTimeRemainingFormat] = "{0}:{1} left", [TextId.HudWolfToggleFormat] = "Wolf Mode [{0}]", [TextId.HudBeaconKeyFormat] = "Beacon [{0}]", [TextId.HudBeaconLabel] = "Beacon", [TextId.HudTestPlayBanner] = "TEST PLAY: the host has debug mode enabled", [TextId.StartHoldWaitingOthers] = "Waiting for other players…", [TextId.ResultBannerVillagerWin] = "Villager Team Wins", [TextId.ResultBannerWerewolfWin] = "Werewolf Team Wins", [TextId.ResultBannerDefault] = "Match Results", [TextId.ResultStatusAlive] = "Alive", [TextId.ResultStatusDead] = "Dead", [TextId.ResultStatusExecuted] = "Executed", [TextId.ResultStatusDisconnected] = "Disconnected", [TextId.ResultReturnPromptFormat] = "[{0}] Return to Lobby", [TextId.ResultWaitingHost] = "Waiting for the host…", [TextId.ResultFooterWithCountdownFormat] = "{0} | Auto-return in about {1}s", [TextId.ResultDigestHeader] = "── Match Timeline (scroll with mouse wheel) ──", [TextId.DigestMatchStart] = "Match start", [TextId.DigestMeetingButtonFormat] = "{0} called a meeting", [TextId.DigestMeetingReportFormat] = "{0} reported a body", [TextId.DigestExecutedFormat] = "{0} was executed by vote", [TextId.DigestNoExecution] = "Vote result: no one was executed", [TextId.DigestCurseStartedFormat] = "{0} was the Black Cat", [TextId.DigestCurseFollowFormat] = "{0} was taken down with the Black Cat", [TextId.DigestDeathFormat] = "{0} died", [TextId.DigestBombDetonatedFormat] = "The bomb planted on {0} detonated", [TextId.DigestCheckmate] = "Value Checkmate", [TextId.DigestMatchEndFormat] = "{0} ({1})", [TextId.DigestReasonWerewolvesEradicated] = "all werewolves eliminated", [TextId.DigestReasonVillagersEradicated] = "villager team eliminated", [TextId.DigestReasonExtractionCompleted] = "truck departed", [TextId.DigestReasonTimerExpired] = "time expired", [TextId.DigestReasonExtractionFailed] = "extraction failed", [TextId.DigestReasonValueCheckmate] = "Value Checkmate", [TextId.DigestExtractionDoneFormat] = "Extraction point completed ({0}/{1})", [TextId.DigestPerkUnlockedFormat] = "Werewolf perk \"{0}\" unlocked", [TextId.DigestInformant] = "Informant activated (werewolf team revealed to the Black Cat)", [TextId.DigestFinalBalanceFormat] = "Final tally: delivered ${0} / quota left ${1} / obtainable ${2}", [TextId.VoteMeetingTitle] = "Emergency Meeting", [TextId.VoteSkipLabel] = "Skip", [TextId.VoteConfirmLabel] = "Sure?", [TextId.VoteVoteLabel] = "Vote", [TextId.VoteWerewolfMarkerLabel] = "W", [TextId.VoteBomberMarkerLabel] = "B", [TextId.VoteCountFormat] = "{0} votes", [TextId.VoteExecutedFormat] = "Executed: {0}", [TextId.VoteNoExecution] = "No execution", [TextId.VoteSkipSuffixFormat] = " (Skip: {0})", [TextId.ChatLogTitle] = "Meeting Chat Log", [TextId.ChatLogEmpty] = "No messages yet", [TextId.ChatLogDeadHint] = "Gray messages are visible only to the dead", [TextId.ChatLogToggleLabelFormat] = "Meeting Chat Log [{0}]", [TextId.ChatLogVoted] = "has voted.", [TextId.RecapTitle] = "Recap", [TextId.RecapNameSeparator] = ", ", [TextId.RecapDeathsFormat] = "Deaths: {0}", [TextId.RecapDeathsNone] = "Deaths: none", [TextId.RecapLostFormat] = "Valuables destroyed: ${0}", [TextId.RecapHaulFormat] = "Delivered: ${0} / Quota ${1}", [TextId.RecapBeaconFormat] = "Beacon uses: {0}", [TextId.RecapBeaconNone] = "Beacon uses: none", [TextId.DeathRevealTitle] = "Deaths", [TextId.DeathRevealNone] = "No one died", [TextId.CheckmateTitle] = "THE DEBT CAN NO LONGER BE COLLECTED", [TextId.MapOverlayToggleLabelFormat] = "Full Map [{0}]", [TextId.LobbySettingsFooterHintFormat] = "[{0}]: Hide panel / Mouse wheel: Scroll", [TextId.LobbySettingsMiniHintFormat] = "[{0}]: Show Werewolf room settings", [TextId.ModIntegrityHeaderAllMatchFormat] = "✓ Matching mod configurations: {0}/{1}", [TextId.ModIntegrityHeaderCountsFormat] = "Baseline {0} ✓ {1} ! {2} × {3} ? {4}", [TextId.ModIntegritySelfDifferenceFormat] = "Your mod configuration differs (missing {0} / extra {1} / version {2} / content {3})", [TextId.ModIntegritySelfUnavailable] = "Your mod info could not be verified", [TextId.ModIntegrityPanelTitle] = "Mod Configurations (Room Baseline Comparison)", [TextId.ModIntegrityFilterNeedsReview] = "Needs review", [TextId.ModIntegrityFilterMatch] = "Match", [TextId.ModIntegrityFilterAll] = "All", [TextId.ModIntegrityStatusBaseline] = "Baseline", [TextId.ModIntegrityStatusPending] = "Checking", [TextId.ModIntegrityStatusMatch] = "Match", [TextId.ModIntegrityStatusDifference] = "Differs", [TextId.ModIntegrityStatusUnavailable] = "Unavailable", [TextId.ModIntegrityReasonNoResponse] = "No response", [TextId.ModIntegrityReasonUnsupportedProtocol] = "Unsupported version", [TextId.ModIntegrityReasonInvalidPayload] = "Invalid comparison data", [TextId.ModIntegrityReasonTooLarge] = "Data exceeds the size limit", [TextId.ModIntegrityReasonCollectionFailed] = "Failed to collect mod info", [TextId.ModIntegrityDetailLoading] = "Loading details…", [TextId.ModIntegrityDetailFailed] = "Failed to load details Retry", [TextId.ModIntegrityDisclaimer] = "This view compares client self-reports against the room baseline and does not guarantee the absence of cheats.", [TextId.ModIntegrityDetailMissingFormat] = "Missing: {0} ({1})", [TextId.ModIntegrityDetailExtraFormat] = "Extra: {0} ({1})", [TextId.ModIntegrityDetailVersionFormat] = "Version: {0} baseline {1} → player {2}", [TextId.ModIntegrityDetailContentFormat] = "Content: {0} baseline {1} → player {2}", [TextId.ModIntegrityStartCautionTitle] = "Mod configurations differ", [TextId.ModIntegrityStartSevereTitle] = "Some mod configurations are unverified", [TextId.ModIntegrityStartBodyFormat] = "Different: {0} Unavailable: {1} Checking: {2}\nThis may affect fair play.", [TextId.ModIntegrityButtonBack] = "Back", [TextId.ModIntegrityButtonDetails] = "View mod configurations", [TextId.ModIntegrityButtonContinue] = "Start anyway", [TextId.ModIntegrityButtonClose] = "Close", [TextId.LobbyStartTooFewPlayersTitle] = "Not enough players", [TextId.LobbyStartTooFewPlayersBodyFormat] = "Current players: {0} / Minimum required: {1}\nTo play regular R.E.P.O., turn Werewolf Mode\n(WerewolfModeEnabled) off in the room settings.", [TextId.LobbyStartTeamOverflowTitle] = "Too many werewolves", [TextId.LobbyStartTeamOverflowBodyFormat] = "Werewolves: {0} / Current players: {1}\nA game with no villagers cannot start. Lower the\nWerewolf count (WerewolfCount) in the room settings.", [TextId.ConveneCountdownDefaultCallerName] = "Someone", [TextId.ConveneCountdownHeaderFormat] = "{0} called a meeting!\nWarping in…", [TextId.ConveneCountdownCorpseHeaderFormat] = "{0} reported a body!\nWarping in…", [TextId.HudCorpseReportKeyFormat] = "Report [{0}]", [TextId.MeetingButtonSuppressCountdownFormat] = "Emergency Meeting (in {0}s)", [TextId.MeetingButtonConveneGrabPrompt] = "Call Emergency Meeting [Hold Grab]", [TextId.MeetingButtonConveneInteractPrompt] = "Call Emergency Meeting [Hold Interact]", [TextId.MeetingButtonSuppressedPrompt] = "Emergency Meeting (unavailable now)", [TextId.MeetingButtonRightsSuffixFormat] = " ({0} left)", [TextId.CurseBlackCatRevealedFormat] = "{0} was the Black Cat. They are choosing someone to take with them…", [TextId.CurseNoVictim] = "The Black Cat took no one with them", [TextId.SettingsSectionGeneral] = "General", [TextId.SettingsSectionMeeting] = "Meeting", [TextId.SettingsSectionRoleAssignment] = "Roles - Assignment", [TextId.SettingsSectionRoles] = "Roles - Werewolf", [TextId.SettingsSectionBlackCat] = "Roles - Black Cat", [TextId.SettingsSectionBomber] = "Roles - Bomber", [TextId.SettingsSectionShaman] = "Roles - Shaman", [TextId.SettingsSectionWorldgen] = "Starting Environment", [TextId.SettingsSectionStartItemList] = "Starting Items", [TextId.SettingsSectionStartUpgradeList] = "Upgrades (everyone)", [TextId.SettingsBoolEnabled] = "On", [TextId.SettingsBoolDisabled] = "Off", [TextId.SettingsAuto] = "Auto", [TextId.SettingsListEmpty] = "None", [TextId.SettingsPresent] = "Yes", [TextId.SettingsRandom] = "Random", [TextId.SettingsValuableMapRealtime] = "Real-Time", [TextId.SettingsValuableMapMeetingSync] = "Meeting sync", [TextId.SettingsValuableMapHidden] = "Hidden", [TextId.SettingsNecroVoiceOff] = "OFF", [TextId.SettingsNecroVoiceNonWerewolfDead] = "Dead non-werewolves", [TextId.SettingsNecroVoiceAllDead] = "All dead players", [TextId.SettingsItemsAggregateFormat] = "{0} item types, {1} total ({2})", [TextId.SettingsAggregateMoreSuffix] = "…", [TextId.SettingsUpgradeItemFormat] = "{0}+{1}", [TextId.SettingsListSeparator] = ", ", [TextId.SettingsUnitSeconds] = "s", [TextId.SettingsUnitTimes] = " time(s)", [TextId.SettingsUnitItems] = " item(s)", [TextId.SettingsUnitPeople] = " player(s)", [TextId.SettingsUnitMeters] = "m", [TextId.SettingsUnitPercent] = "%", [TextId.SettingsUnitDamage] = " dmg", [TextId.SettingsLabelWerewolfCount] = "Werewolves", [TextId.SettingsLabelBlackCatChancePercent] = "Black Cat chance", [TextId.SettingsLabelBomberChancePercent] = "Bomber chance", [TextId.SettingsLabelShamanChancePercent] = "Shaman chance", [TextId.SettingsLabelRoundSeconds] = "Round time limit", [TextId.SettingsLabelBlackCatRevealDelaySec] = "Black Cat awakening delay", [TextId.SettingsLabelBlackCatCurseEnabled] = "Black Cat's Revenge", [TextId.SettingsLabelMeetingRightsPerPlayer] = "Meeting calls per player", [TextId.SettingsLabelConveneSuppressStartSec] = "Meeting lockout after start", [TextId.SettingsLabelConveneSuppressAfterSec] = "Meeting lockout after meeting", [TextId.SettingsLabelMeetingCountdownSec] = "Meeting warp countdown", [TextId.SettingsLabelMeetingDurationSec] = "Meeting time limit", [TextId.SettingsLabelVoteTimeCutEnabled] = "Votes shorten the meeting", [TextId.SettingsLabelResultDisplaySec] = "Vote result display duration", [TextId.SettingsLabelStaminaUnlockPct] = "Infinite stamina unlock", [TextId.SettingsLabelJumpUnlockPct] = "Extra jump unlock", [TextId.SettingsLabelEnemyIgnoreUnlockPct] = "Monster camouflage unlock", [TextId.SettingsLabelHealUnlockPct] = "Regeneration unlock", [TextId.SettingsLabelHealIntervalSec] = "Regeneration interval", [TextId.SettingsLabelBeaconChargePct] = "Beacon recharge threshold", [TextId.SettingsLabelInformantThresholdPct] = "Informant threshold", [TextId.SettingsLabelExtraJumpCount] = "Extra mid-air jumps", [TextId.SettingsLabelBeaconCooldownSec] = "Beacon cooldown", [TextId.SettingsLabelBeaconSuppressStartSec] = "Beacon lockout after start", [TextId.SettingsLabelBeaconSuppressAfterMeetingSec] = "Beacon lockout after meeting", [TextId.SettingsLabelCatGaugeSyncIntervalSec] = "Black Cat gauge update interval", [TextId.SettingsLabelOrbGaugeEnabled] = "Include orb value in loss gauge", [TextId.SettingsLabelWerewolfModeEnabled] = "Werewolf Mode", [TextId.SettingsLabelMinimapHideEnabled] = "Hide corpses on minimap", [TextId.SettingsLabelValuableMapMode] = "Valuables map mode", [TextId.SettingsLabelGameOverAutoReturnSec] = "Result screen auto-return (0 = never)", [TextId.SettingsLabelNecroVoiceMode] = "Voices of the dead (dead → living werewolves)", [TextId.SettingsLabelStartLevelNumber] = "Level", [TextId.SettingsLabelStartMapName] = "Map", [TextId.SettingsLabelStartItemsSpec] = "Starting items", [TextId.SettingsLabelStartEnergyPct] = "Starting truck charge", [TextId.SettingsLabelStartUpgradesSpec] = "Starting upgrades", [TextId.SettingsLabelOrbDropMax] = "Orbs dropped by enemies", [TextId.SettingsLabelBomberProximityMeters] = "Bomb-planting range", [TextId.SettingsLabelBomberGaugeFullSec] = "Time required to plant", [TextId.SettingsLabelBomberInitialCooldownSec] = "Starting cooldown", [TextId.SettingsLabelBomberCooldownSec] = "Regular cooldown", [TextId.SettingsLabelBomberBlastRadiusMeters] = "Blast and warning radius", [TextId.SettingsLabelBomberBlastPlayerDamage] = "Player damage", [TextId.SettingsLabelBomberBlastEnemyDamage] = "Enemy damage", [TextId.SettingsLabelBomberAmmoRefillPct] = "Bomb refill threshold", [TextId.SettingsLabelShamanGazeFullSec] = "Spirit vision gaze time", [TextId.SettingsLabelShamanGhostCooldownSec] = "Spirit vision cooldown", [TextId.SettingsLabelShamanStormWeakMeters] = "Haunting (weak) radius", [TextId.SettingsLabelShamanStormMediumMeters] = "Haunting (medium) radius", [TextId.SettingsLabelShamanStormStrongMeters] = "Haunting (strong) radius", [TextId.HudBomberPlantKeyFormat] = "Plant Bomb [{0}]", [TextId.HudBomberDetonateKeyFormat] = "Detonate [{0}]", [TextId.HudValuableRecordOnFormat] = "Recording [hold {0}]", [TextId.HudValuableRecordOffFormat] = "Not Recording [hold {0}]", [TextId.BomberDenyNoAmmo] = "No bombs left", [TextId.BomberDenyNoFullTarget] = "No target is ready for a bomb", [TextId.BomberDenyPlantCooldown] = "Cannot plant a bomb (on cooldown)", [TextId.BomberDenyDetonateCooldown] = "Cannot detonate (on cooldown)", [TextId.BomberDenyNoBomb] = "Cannot detonate (no bomb planted)", [TextId.BomberDenyMeetingLocked] = "Cannot detonate (meeting in progress)", [TextId.BomberDenyTruckZone] = "Cannot detonate (target is near the truck)", [TextId.BomberDudTargetDead] = "The target was already dead. The bomb was a dud", [TextId.BomberTargetDisconnected] = "The target disconnected. The bomb is gone", [TextId.BomberProximityWarning] = "Someone may have planted a bomb on me…", [TextId.TutorialCorpseDiscovery] = "You found an unreported body. Good job! \ud83d\ude02\nThe dead don't come back, so no need to haul them to an extraction point.\nYou can report an unreported body even with no meeting calls left (the icon at the bottom right of the HUD pulses in color when you are close enough).\nAt the meeting, tell everyone where and how you found the body.", [TextId.TutorialMeetingCountdownStarted] = "A meeting has been called. This is the countdown until everyone warps to the truck.\nWhatever you're holding—valuables, weapons, or drones—stays behind. (Your inventory comes with you.)\nHurry and secure any valuables so they are not dropped and damaged or left in a corridor where monsters can smash them.", [TextId.TutorialFirstMeetingAsVillager] = "During a meeting, you can view the full map with updated locations and status information for the valuables.\nAssess the situation, share information, and tell everyone whom you do and do not suspect.\nVote for anyone you find suspicious. You can have them executed.", [TextId.TutorialWerewolfRoleDrawn] = "You are a Werewolf. Congratulations! \ud83d\ude02\nThe more valuables you destroy, the more powerful perks you unlock.\nDamage valuables without the villagers noticing and reap the rewards.\nOnly Werewolves can see monster positions on the map. Use that knowledge well.", [TextId.TutorialFirstValuableSeen] = "You found a valuable.\nIts position is recorded on the map, but the map will not update if it is moved or destroyed afterward.\nIt only updates when a meeting starts or an extraction is completed.\nIf a valuable isn't where the map says it should be, someone carried it off—or destroyed it.", [TextId.TutorialWolfModeFirstUnlock] = "Enough valuables have been destroyed. Wolf Mode is unlocked.\nYou can use powerful abilities while Wolf Mode is active.\nDo not get careless and use them in front of the villagers. You can turn Wolf Mode off to avoid giving yourself away.", [TextId.TutorialBeaconFirstCharged] = "Enough valuables have been destroyed. The beacon is unlocked.\nThe beacon lures monsters from across the map to your current position. It even brings back monsters that were killed and despawned.\nAnyone seen at the center of a monster swarm will look like the source. Choose your location carefully.\nCheck the monsters' positions on the map first. Do not blame me if you get caught in the chaos yourself.", [TextId.TutorialFirstMeetingAsWerewolf] = "During a meeting, the valuables map is updated and the latest information is shared with everyone.\nEvery Werewolf executed puts the werewolf team at a disadvantage.\nMake excuses. Play innocent. Share useless information and hide what matters. Use lies to make others look suspicious.\nGood luck \ud83d\ude02", [TextId.TutorialFirstMeetingAsBlackCat] = "If the Black Cat is executed at a meeting, they can take one of the players who voted for them down with them.\nIf you already know who you want to take with you, steer them into voting for you.\nAny Werewolf who voted for you is a valid target too. Choose whom you take with you carefully.", [TextId.TutorialVillagerSeesCatAwakened] = "Official notice: there may be a Black Cat on the werewolf team.\nWhen the Black Cat is executed at a meeting, they can take one of the players who voted for them down with them.\nWill you execute them at a meeting? Or remove them by force and accept the risk of retaliation and the cost in resources?\nDecide carefully.", [TextId.TutorialBlackCatRoleDrawn] = "You are the Black Cat. Congratulations! \ud83d\ude02\nWhen you are executed by vote, you can take one of the players who voted for you down with you.\nWhile the Werewolves are busy posing as Villagers, run wild and support them.\nBut you are not told who the Werewolves are. Try not to take one down by accident.\nThe Black Cat belongs to the werewolf team but does not count as a surviving Werewolf. If every Werewolf dies, the villager team wins even if the Black Cat is still alive.", [TextId.TutorialBlackCatRoleDrawnNoCurse] = "You are the Black Cat. Congratulations! \ud83d\ude02\nThe Black Cat belongs to the werewolf team, but the Werewolves and the Black Cat do not know each other's identities.\nWhile the Werewolves are busy posing as Villagers, run wild and support them.\nThe Black Cat does not count as a surviving Werewolf. If every Werewolf dies, the villager team wins even if the Black Cat is still alive.", [TextId.TutorialLastRunApproaching] = "The final extraction order has been issued. Body reports are no longer accepted.\nMeetings can now only be called using the button in the truck.\nChoose carefully who stays with the truck and who handles the final extraction.", [TextId.TutorialRoundTimeWarningVillager] = "The deadline is coming up.\nComplete every extraction and send the truck on its way before the deadline. If you fail, the villagers lose.\nIf you can't make it in time, wiping out the werewolves is your only option.", [TextId.TutorialRoundTimeWarningWerewolf] = "The deadline is coming up.\nKeep the truck from departing until the deadline, and the werewolves win.\nStay sharp until the very end.", [TextId.TutorialFinalExtractionVillager] = "The final extraction is complete.\nIf even one villager is alive when the truck departs, you win.\nStay sharp until the very end.", [TextId.TutorialFinalExtractionWerewolf] = "The final extraction has been completed.\nIf any villager is alive when the truck departs, the werewolves lose.\nAt this point, wiping out the villagers is your only option.", [TextId.TutorialInformantUnlockedAsWerewolf] = "If there is a Black Cat, they now know who the Werewolves are.\nTry to arrange a discreet meeting somewhere.\nOr they may make the first move during a meeting. Be ready to play along.", [TextId.TutorialInformantUnlockedAsBlackCat] = "Enough valuables have been destroyed. You can now tell who the Werewolves are.\nPlayers whose names appear in red—or who have a wolf icon during meetings—are Werewolves.\nQuietly let the Werewolves know you are the Black Cat without alerting the villagers.", [TextId.TutorialEnemyIgnoreUnlockedAsWerewolf] = "Enough valuables have been destroyed. Monsters now see you as one of their own.\nWhile Wolf Mode is on, most monsters won't react to seeing you.\nThey still react to sound, and you can still be caught in their attacks.\nBy the way, did you know that loud noises make vanished monsters return sooner?", [TextId.TutorialNaturalHealUnlockedAsWerewolf] = "Enough valuables have been destroyed. Your body is becoming something beyond human.\nWhile Wolf Mode is on, your health now slowly recovers on its own.\nThere is no visual effect or sound, but other players can see the health bar on your back.\nIf you were near death moments ago but your health has mysteriously recovered, explaining it will not be easy.", [TextId.TutorialWerewolfSeesCatAwakened] = "Official notice: there may be a Black Cat on the werewolf team.\nThe Werewolves and the Black Cat are allies, but the Werewolves are not told who the Black Cat is.\nThe Black Cat belongs to the werewolf team but does not count as a surviving Werewolf. If every Werewolf dies, the villager team wins whether the Black Cat is alive or dead.", [TextId.TutorialBeaconFirstUsedAsWerewolf] = "So you used the beacon.\nMonsters across the map will now gather at that spot.\nThe number of beacon uses is revealed at the next meeting, so be careful not to hand the villagers any clues.", [TextId.TutorialBlackCatSelectedForExecution] = "You've been chosen for execution. Good job! \ud83d\ude02\nPick someone to take down with you.\nIf you don't, someone will be picked at random.", [TextId.TutorialBlackCatExecutionRevealed] = "The player with the most votes was the Black Cat.\nWhen the Black Cat is executed, one of the players who voted for them is taken down with them.", [TextId.TutorialFirstDeath] = "Your body has been destroyed. You now exist only as consciousness data uploaded to the cloud.\nNo new body will be issued during this match.\nYou cannot use your own Death Head to talk to the living, either.", [TextId.TutorialBomberRoleDrawn] = "You are the Bomber. Congratulations! \ud83d\ude02\nSpend enough time near someone and you can turn them into a walking bomb.\nDetonate whenever you like. The bomb carrier takes some damage, but survives with 1 HP and is not thrown by the blast.\nIf you get caught in the blast, you die instantly. Get clear before you detonate.", [TextId.TutorialBombPlantedAsBomber] = "Bomb planted. Good job! \ud83d\ude02\nNow get clear of the blast and detonate it.\nYou cannot detonate while your target is near the truck. We cannot have the truck getting scratched.", [TextId.TutorialBomberProximityWarnedAsVillager] = "Repossessing valuables efficiently sometimes requires help from another player.\nBut a Bomber can plant a bomb on someone after staying near them for a while.\nRemember who you've been working beside, in case you later discover that you were turned into a bomb.", [TextId.TutorialSelfBombExplodedAsVillager] = "It looks like the Bomber planted a bomb on you.\nThe blast injured you, but the bomb itself cannot kill or throw its carrier.\nThe Bomber can turn someone into a bomb by staying near them for a while, then detonate them at any time.\nThe Bomber dies instantly if caught in that blast.\nThe Bomber can only have one bomb planted at a time.", [TextId.TutorialShamanRoleDrawn] = "You are the Shaman.\nStand still for a moment and hold your gaze steady to use spirit vision.\nA haunting sets in when you are near an unreported body.\nBe careful not to become the next corpse.", [TextId.TutorialShamanGhostSighted] = "Your spirit vision reacted.\nThat means there is an unreported body in the direction you were just facing.", [TextId.TutorialShamanTranceEntered] = "You heard a drip.\nThat means there is no unreported body in the direction you are facing.", [TextId.TutorialShamanStormEntered] = "A haunting has set in.\nAn unreported body is nearby. The stronger the haunting, the closer the body.", [TextId.TutorialEquipBlockedByOtherGrabber] = "You cannot put an item into your inventory while another player is holding it.\nIf you want to take it, hit them with a melee weapon to knock it loose, then pick it up.", [TextId.TutorialValuableRecordSuppressed] = "You spotted a valuable, but did not record it on the map.\nThe werewolf team does not record newly discovered valuables by default. If nobody else knows\nabout a valuable, you can destroy it without leaving a marker to show that something is missing.\nHold the report key whenever you want to turn recording on.", [TextId.ManualToggleLabelFormat] = "Manual [{0}]", [TextId.ManualPageFooterFormat] = "{0}\u3000\u3000{1} / {2}", [TextId.ManualNavHint] = "← →: Pages\u3000\u3000Shift + ← →: Sections\u3000\u3000{0} or [menu]: Close", [TextId.ManualSectionBasics] = "Basics", [TextId.ManualSectionExploration] = "Exploration and Combat", [TextId.ManualSectionMeeting] = "Meetings and Voting", [TextId.ManualSectionGauge] = "Valuable Loss Gauge", [TextId.ManualSectionVillager] = "Villager", [TextId.ManualSectionShaman] = "Shaman", [TextId.ManualSectionWerewolf] = "Werewolf", [TextId.ManualSectionBlackCat] = "Black Cat", [TextId.ManualSectionBomber] = "Bomber", [TextId.ManualSectionAfterDeath] = "After Death", [TextId.ManualWelcomeTitle] = "Welcome to REPO Werewolf", [TextId.ManualWelcomeBody] = "In REPO Werewolf, players are split into the villager and werewolf teams, each working behind the scenes to secure victory.\nThe villager team's goal is the same as in regular R.E.P.O.: collect valuables, complete every extraction, and send the truck on its way.\nThe werewolf team's goal is to sabotage those efforts without revealing their identities.\nWho suspects whom, and who is working with whom?\nCan you uncover the truth amid all that suspicion? Or can you convince everyone that a lie is the truth?\nThe moment you seize on an easy answer and turn on your own, the players themselves become the most dangerous monsters of all.", [TextId.ManualGameFlowTitle] = "How a Match Works", [TextId.ManualGameFlowBody] = "REPO Werewolf requires at least 3 players.\nIn the room settings, the host can choose the stage and level, starting items, player upgrades, and more.\nWarning: starting a match from an existing save file overwrites that save with the match state, erasing its previous contents.\nThere is no shop or next level—each match takes place in a single level.\nOnce either team secures victory, the results are shown and everyone returns to the lobby.\nCosmetic Boxes do not appear during a match. Instead, you can earn tokens afterward by meeting certain conditions.", [TextId.ManualVillagerWinTitle] = "Villager Team Win Conditions", [TextId.ManualVillagerWinBody] = "The villager team has two win conditions. Satisfy either one to win the match.\n・Complete every extraction and send the truck on its way with at least one villager still alive\n・Eliminate the werewolf team (the villagers still win if the Black Cat survives)\nThe team of the player who starts the truck has no bearing on the result. What matters is whether any villagers are still alive when it departs.\nIn other words, completing the level as you normally would in R.E.P.O. also brings the villagers closer to victory.\nThe werewolf team will use every trick available to interfere, however, so this mode demands a different strategy from a normal run.", [TextId.ManualWerewolfWinTitle] = "Werewolf Team Win Conditions", [TextId.ManualWerewolfWinBody] = "The werewolf team has three win conditions. Satisfy any one to win the match.\n・Make the quota impossible to reach even if every available valuable were collected (value checkmate)\n・Keep the truck from departing until time runs out\n・Eliminate the villager team\nThe moment value checkmate is reached, the werewolf team's victory is final.\nEven after the final extraction is complete, the werewolf team wins if every villager dies before the truck departs.\nThis includes a Werewolf starting the truck and leaving every villager behind to die.", [TextId.ManualValuablesMapTitle] = "Valuables and the Map", [TextId.ManualValuablesMapBody] = "When a player discovers a valuable or an enemy drops an orb, a yellow marker is added to the map.\nUnlike regular R.E.P.O., these markers do not update in real time under the default settings.\nThey update only when an extraction is completed or a meeting starts.\nIf no valuable is at a marker, someone either carried it away or destroyed it.\nThe map cannot tell you which.\nCareful villagers take inventory at every meeting and ask where each missing valuable went.", [TextId.ManualValuableRecordTitle] = "Not Recording Valuables (Werewolf Team)", [TextId.ManualValuableRecordBody] = "A marker is added the moment a player sees a valuable.\nBy default, the werewolf team (Werewolves, Bombers, and awakened Black Cats) does not record newly discovered valuables.\nIf a member of the werewolf team reaches and destroys a valuable before anyone else finds it, the map shows no trace that it ever existed.\nIf a villager has already found and recorded it, however, the marker remains—alerting everyone that something is missing.", [TextId.ManualValuableRecordToggle] = "You can switch recording on and off at any time by holding the report key (the icon at the bottom right shows the current state).\nTurn recording on as needed when you want to blend in with villagers while exploring.", [TextId.ManualCombatTitle] = "PvP", [TextId.ManualCombatBody] = "Unlike in regular R.E.P.O., melee weapons can damage players in Werewolf Mode, consuming weapon energy in the process (just as they do in the Super Smash Bros.-style arena).\nThere is also no safeguard that lets you survive an otherwise fatal blow at low health.\nMelee attacks can disarm players as well.\nWhen struck by a melee weapon, you drop whatever you are holding and one item is knocked out of your inventory.\nYou also cannot place an item in your inventory while another player is holding it. To take it, you must knock it out of their hands first.\nIn other words, whoever lands the first hit has the advantage. Choose carefully whom you trust to watch your back.", [TextId.ManualEndgamePrepTitle] = "Preparing for the Endgame", [TextId.ManualEndgamePrepBody] = "Late in the match, when meetings can no longer settle the conflict, the game may shift into an endgame where the werewolf team and the villager team face off directly.\nHow well each side can fight in the endgame depends on the position it has built over the course of the match.\nVillagers can gain an advantage by securing and confiscating dangerous weapons to keep them out of the werewolf team's hands, protecting trusted allies, and keeping healing supplies in reserve.\nThe werewolf team can build its advantage by destroying valuables to unlock perks, killing the most trusted villagers, and disrupting the villagers' coordination.\nThe final confrontation is where the advantages both teams have built throughout the match come into play.\nKeep the endgame in mind and prepare for it from the beginning.", [TextId.ManualCorpseTitle] = "About Corpses", [TextId.ManualCorpseBody] = "In REPO Werewolf, dead players cannot be revived by any means.\nBody locations are not shown on the map, either. You must find them with your own eyes.\nPress the report key (shown at the bottom right of the screen) near an unreported body to call a meeting on the spot.", [TextId.ManualConveneTitle] = "Calling a Meeting", [TextId.ManualConveneBody] = "There are two ways to call a meeting.\n・Grab and hold the red button at the back of the truck\n・Press the report key near an unreported body\nEach player can use the button to call a meeting once (this can be changed in the room settings).\nThe button is temporarily unavailable just after the match starts and after a meeting ends.\nOnce only one incomplete extraction point remains, meetings can no longer be called by reporting a body (for example, if the quota is 4 extraction points, reports are disabled after the 3rd is completed).\nThe button remains available until the very end.", [TextId.ManualMeetingFlowTitle] = "How Meetings Work", [TextId.ManualMeetingFlowBody] = "When a meeting starts, everyone is warped to the truck and immobilized.\nDuring a meeting, enemies disappear and no new ones spawn, so you are safe.\nDeaths since the previous meeting are announced first, followed by changes to the valuable loss gauge.\nVoting begins afterward. During the meeting, you can open the full map to review the updated information on valuables.\nThe match time limit is paused during a meeting, so discussion does not eat into your remaining time.\nEnemy respawn timers continue to count down, however, so an overly long meeting brings the enemies back sooner.\nAny enemy whose respawn timer has expired by the end of the meeting spawns immediately.", [TextId.ManualVotingTitle] = "Voting and Execution", [TextId.ManualVotingBody] = "Each surviving player casts one vote at a meeting. Skipping (voting for no one) is also an option.\nEveryone can see who has finished voting, but not who they voted for.\nThe player with the most votes is executed. If there is a tie for the most votes, no one is executed.\nMeetings have a time limit, and each vote cast reduces the remaining time slightly.", [TextId.ManualGaugeBasicsTitle] = "Reading the Valuable Loss Gauge", [TextId.ManualGaugeIntro] = "The valuable loss gauge tracks the struggle over valuables between the villager and werewolf teams. Its baseline is the total value of all valuables on the map when the match starts.", [TextId.ManualGaugeLoss] = "Yellow bar (grows from the left)… total value lost through damage", [TextId.ManualGaugeDelivery] = "Cyan bar (grows from the right)… total value delivered through completed extractions", [TextId.ManualGaugeLines] = "・Blue line… the value required to meet the quota. Once the cyan bar reaches this line, the truck can depart\n・Red line… the value checkmate threshold, where the quota can no longer be met even if every available valuable is collected. The moment the yellow bar reaches this line, the werewolf team wins\nWhen an enemy drops an orb or new valuables appear on the map, the red line moves to the right, giving the villagers more leeway. This cannot overturn a checkmate victory once it is locked in.", [TextId.ManualRoleVillagerTitle] = "Role: Villager", [TextId.ManualRoleVillagerBody] = "Team: Villagers\nVillagers have no special abilities. Play as you would in regular R.E.P.O.: collect valuables and complete the extractions.\nYour greatest weapons are observation and discussion. Compare notes on the valuables map, any bodies found, and what other players have done, then expose the Werewolves during meetings.", [TextId.ManualRoleWerewolfTitle] = "Role: Werewolf", [TextId.ManualRoleWerewolfIntro] = "Team: Werewolves\nThe Werewolf role is the core of the werewolf team, and at least one player is always assigned this role.\nWerewolves grow stronger as valuables are damaged.\nRegardless of who caused the damage, perks unlock in sequence as the total value lost reaches each threshold.", [TextId.ManualRoleWerewolfEnemyMap] = "Only players with the Werewolf role can see enemy positions on the map.", [TextId.ManualRoleWerewolfPerksTitle] = "Role: Werewolf — Perks (1/2)", [TextId.ManualRoleWerewolfPerkStamina] = "Infinite Stamina… dashing and ledge-grabbing no longer drain stamina", [TextId.ManualRoleWerewolfPerkJump] = "Extra Jumps… jump multiple times in midair (the number depends on the room settings)", [TextId.ManualRoleWerewolfPerksTitle2] = "Role: Werewolf — Perks (2/2)", [TextId.ManualRoleWerewolfPerkEnemyIgnore] = "Monster Camouflage… most enemies stop targeting you (they still react to sound)", [TextId.ManualRoleWerewolfPerkHeal] = "Regeneration… while Wolf Mode is on, your health slowly recovers over time (no healing effect is shown, but the health gauge on your back is visible to other players)", [TextId.ManualRoleWerewolfPerkToggle] = "Use the Wolf Mode key to turn perks on or off. Be careful—a villager who sees you use one will know what you are.", [TextId.ManualRoleWerewolfBeaconTitle] = "Role: Werewolf — Beacon", [TextId.ManualRoleWerewolfBeaconBody] = "The Beacon is a special ability of the Werewolf role. You gain one use each time valuables lose a certain total amount of value.\nWhen activated, it emits a sound that players cannot hear and draws enemies from across the map to its location. It also brings back monsters that have been killed and despawned, subject to a revival cooldown shared by the entire werewolf team.\nUse it to arrange an \"accident\" for an isolated player or send enemies after a group of villagers to cause panic.\nHowever, this may create more available orbs and help the villagers reach their quota. You also risk being caught in the ensuing fight.\nUsing the Beacon is not announced immediately. At the next meeting, however, everyone is told how many times it has been used since the previous meeting.", [TextId.ManualRoleBlackCatTitle] = "Role: Black Cat", [TextId.ManualRoleBlackCatIntro] = "Team: Werewolves (win/loss only)\nBase role: Villager\nDepending on the room settings, the Black Cat is assigned from among the Villagers.\nAt the start of the match, even the Black Cat is told they are a Villager. They awaken to their true identity after a short delay.\nOtherwise, they have the same abilities as a Villager. If Black Cat's Revenge is enabled in the room settings, being executed at a meeting lets them choose one of their voters to take down with them. If they choose no one, a target is selected at random.\nThe Werewolves do not know who the Black Cat is, and initially the Black Cat does not know who the Werewolves are.", [TextId.ManualBlackCatInformantTitle] = "Role: Black Cat — Informant", [TextId.ManualRoleBlackCatInformant] = "Informant… once valuables lose a certain total amount of value, the members of the werewolf team are revealed to the Black Cat alone.", [TextId.ManualRoleBlackCatGaugeNote] = "The Black Cat can also see the valuable loss gauge, but it updates at intervals rather than in real time.", [TextId.ManualBlackCatCounterTitle] = "Dealing with the Black Cat", [TextId.ManualBlackCatCounterBody] = "When enabled in the room settings, the Black Cat's Revenge ability triggers only when they are executed by vote.\nDo not rush to execute someone you suspect is the Black Cat. If you are right, one of the players who voted for them will be taken down too.\nIf an execution is unavoidable, only players prepared to be taken down should cast a vote.\nThe ability does not trigger if the Black Cat dies another way, such as to a weapon or a fall, but using force may ignite a fight.\nIf you kill someone without the group's agreement, the other villagers have no way to know whether you had good reason.\nIf your suspicion falls short of an execution, confiscating that player's weapons and watching them is another option. Without a weapon, even the werewolf team cannot easily kill anyone.", [TextId.ManualRoleBomberTitle] = "Role: Bomber", [TextId.ManualRoleBomberIntro] = "Team: Werewolves\nBase role: Werewolf\nDepending on the room settings, the Bomber is assigned from among the Werewolves.\nThe Bomber can turn a nearby player into a bomb after spending enough time near them, then detonate the bomb at any time.", [TextId.ManualBomberPlantTitle] = "Role: Bomber — Planting a Bomb", [TextId.ManualRoleBomberPlant] = "Plant Bomb is on cooldown at the start of the match and after each meeting. Once the cooldown ends, staying near another player gradually fills a yellow meter for that player. The meter does not fill through walls and drains when you move away.\nWhen the meter is full and turns green, press the Plant Bomb key shown in the bottom-left corner of the screen to turn that player into a bomb.\nA bomb icon visible only to the Bomber marks the affected player. They do not know they have been turned into a bomb.\nThe Bomber cannot turn themselves into a bomb, but can target a Werewolf or the Black Cat.\nOnly one player can be a bomb at a time. Before detonating it, you can reassign the bomb to another player.", [TextId.ManualRoleBomberDetonateTitle] = "Role: Bomber — Detonation", [TextId.ManualRoleBomberDetonateBody] = "Once the Detonate cooldown ends, press the key to trigger an explosion centered on the player you turned into a bomb.\nThe blast hits nearby players, valuables, and enemies. The bomb carrier also takes some damage, but their health never drops below 1 and they are not thrown.\nIf the Bomber is caught in the blast, they die instantly.\nYou cannot detonate while the target is near the truck.\nDetonating after the target has died produces a dud, and the bomb is lost.\nYour supply of bombs is replenished whenever valuables lose a certain total amount of value.", [TextId.ManualRoleShamanTitle] = "Role: Shaman", [TextId.ManualRoleShamanIntro] = "Team: Villagers\nBase role: Villager\nDepending on the room settings, the Shaman is assigned from among the Villagers.\nThe Shaman can sense unreported bodies—those not yet revealed at a meeting.\nThey can infer the direction of a body and roughly when the player died.\nSpirit vision requires standing still, leaving you vulnerable, and a haunting obscures your view when it sets in.\nBe careful not to end up as the next body.", [TextId.ManualShamanSenseTitle] = "Role: Shaman — Spirit Vision and Haunting", [TextId.ManualShamanGhost] = "Spirit vision lets you sense the direction of a distant body.\nStand still and hold your gaze on one point. Your vision fades as spirit vision begins.\nA dripping sound heard at regular intervals during spirit vision means there is no unreported body in that direction.\nKeep looking toward an unreported body for several seconds, and your screen becomes heavily distorted for a moment.\nWalls and distance do not matter. After the screen distorts, spirit vision will not react again until its cooldown ends. It is also unavailable briefly after the match starts.\nIf several unreported bodies exist, only the one nearest to you counts. Spirit vision will not react to any body farther away.\nMoving or turning your gaze too far interrupts spirit vision. You cannot use it while a body is close enough to cause a haunting.\nYour vision remains faded only while spirit vision is active.\nBodies already announced at a meeting produce no response.", [TextId.ManualShamanStorm] = "A haunting lets you sense how close a nearby body is.\nNear an unreported body, the haunting changes with distance across three levels: weak, medium, and strong.\nOnce only the final extraction point remains, the visual distortion disappears and only the sound remains.\nBodies already announced at a meeting produce no response.", [TextId.ManualAfterDeathTitle] = "When You Die", [TextId.ManualAfterDeathBody] = "When you die, you enter spectator mode and watch the rest of the match play out.\nThe dead can talk to one another, but there is no way to pass information to the living—no Death Head tricks like in regular R.E.P.O.\nDepending on the room settings, the werewolf team may hear the voices of the dead with an echo effect.\nVictory and defeat are shared by the team, so you still win if your team wins, even after you die." }; public static Language Current { get; set; } = Language.Japanese; public static void SetOverride(IReadOnlyDictionary table) { _override = table; _formatErrorLogged.Clear(); } public static void ClearOverride() { _override = null; _formatErrorLogged.Clear(); } public static string Get(TextId id) { if (_override != null && _override.TryGetValue(id, out var value)) { return value; } if (TableFor(Current).TryGetValue(id, out var value2)) { return value2; } if (!JapaneseTable.TryGetValue(id, out var value3)) { return string.Empty; } return value3; } public static string Format(TextId id, params object[] args) { string text = Get(id); if (args == null || args.Length == 0) { return text; } try { return string.Format(CultureInfo.InvariantCulture, text, args); } catch (FormatException) { LogFormatError(id); string value; string text2 = (JapaneseTable.TryGetValue(id, out value) ? value : text); try { return string.Format(CultureInfo.InvariantCulture, text2, args); } catch (FormatException) { return text2; } } } public static string ExportTemplate() { StringBuilder stringBuilder = new StringBuilder(); foreach (TextId value2 in Enum.GetValues(typeof(TextId))) { string value; string text = (JapaneseTable.TryGetValue(value2, out value) ? value : string.Empty); stringBuilder.Append("# ").Append(text.Replace("\n", "\\n")).Append('\n'); stringBuilder.Append(value2).Append('=').Append(text.Replace("\n", "\\n")) .Append('\n'); } return stringBuilder.ToString(); } private static void LogFormatError(TextId id) { if (!_formatErrorLogged.Add(id)) { return; } try { FormatErrorLogger?.Invoke(id.ToString()); } catch { } } internal static IReadOnlyDictionary TableFor(Language language) { if (language != Language.Japanese && language == Language.English) { return EnglishTable; } return JapaneseTable; } } public sealed class ToastEntry { public string Message { get; } public long ExpiresAtUnixMs { get; } public long RemainingMs { get; } internal ToastEntry(string message, long expiresAtUnixMs, long remainingMs) { Message = message; ExpiresAtUnixMs = expiresAtUnixMs; RemainingMs = remainingMs; } } public sealed class ToastQueue { private readonly List<(string Message, long ExpiresAtUnixMs)> _entries = new List<(string, long)>(); private readonly long _durationMs; private const int MaxVisibleEntries = 5; public ToastQueue(int durationSec) { if (durationSec <= 0) { throw new ArgumentOutOfRangeException("durationSec"); } _durationMs = (long)durationSec * 1000L; } public void Push(string message, long nowUnixMs) { if (!string.IsNullOrEmpty(message)) { PruneExpired(nowUnixMs); _entries.Add((message, nowUnixMs + _durationMs)); } } public IReadOnlyList Visible(long nowUnixMs) { PruneExpired(nowUnixMs); List list = new List(); int num = _entries.Count - 1; while (num >= 0 && list.Count < 5) { (string Message, long ExpiresAtUnixMs) tuple = _entries[num]; string item = tuple.Message; long item2 = tuple.ExpiresAtUnixMs; long num2 = item2 - nowUnixMs; if (num2 < 0) { num2 = 0L; } list.Add(new ToastEntry(item, item2, num2)); num--; } return list; } private void PruneExpired(long nowUnixMs) { _entries.RemoveAll(((string Message, long ExpiresAtUnixMs) e) => e.ExpiresAtUnixMs <= nowUnixMs); } } public static class TutorialCatalog { public static bool ShouldShow(TutorialId id, bool blackCatCurseEnabled) { if (!blackCatCurseEnabled) { if (id != TutorialId.FirstMeetingAsBlackCat && id != TutorialId.VillagerSeesCatAwakened && id != TutorialId.BlackCatSelectedForExecution) { return id != TutorialId.BlackCatExecutionRevealed; } return false; } return true; } public static string Format(TutorialId id, bool blackCatCurseEnabled = true) { return id switch { TutorialId.CorpseDiscovery => Texts.Get(TextId.TutorialCorpseDiscovery), TutorialId.MeetingCountdownStarted => Texts.Get(TextId.TutorialMeetingCountdownStarted), TutorialId.FirstMeetingAsVillager => Texts.Get(TextId.TutorialFirstMeetingAsVillager), TutorialId.WerewolfRoleDrawn => Texts.Get(TextId.TutorialWerewolfRoleDrawn), TutorialId.FirstValuableSeen => Texts.Get(TextId.TutorialFirstValuableSeen), TutorialId.WolfModeFirstUnlock => Texts.Get(TextId.TutorialWolfModeFirstUnlock), TutorialId.BeaconFirstCharged => Texts.Get(TextId.TutorialBeaconFirstCharged), TutorialId.FirstMeetingAsWerewolf => Texts.Get(TextId.TutorialFirstMeetingAsWerewolf), TutorialId.FirstMeetingAsBlackCat => Texts.Get(TextId.TutorialFirstMeetingAsBlackCat), TutorialId.VillagerSeesCatAwakened => Texts.Get(TextId.TutorialVillagerSeesCatAwakened), TutorialId.BlackCatRoleDrawn => Texts.Get(blackCatCurseEnabled ? TextId.TutorialBlackCatRoleDrawn : TextId.TutorialBlackCatRoleDrawnNoCurse), TutorialId.LastRunApproaching => Texts.Get(TextId.TutorialLastRunApproaching), TutorialId.RoundTimeWarningVillager => Texts.Get(TextId.TutorialRoundTimeWarningVillager), TutorialId.RoundTimeWarningWerewolf => Texts.Get(TextId.TutorialRoundTimeWarningWerewolf), TutorialId.FinalExtractionVillager => Texts.Get(TextId.TutorialFinalExtractionVillager), TutorialId.FinalExtractionWerewolf => Texts.Get(TextId.TutorialFinalExtractionWerewolf), TutorialId.InformantUnlockedAsWerewolf => Texts.Get(TextId.TutorialInformantUnlockedAsWerewolf), TutorialId.InformantUnlockedAsBlackCat => Texts.Get(TextId.TutorialInformantUnlockedAsBlackCat), TutorialId.EnemyIgnoreUnlockedAsWerewolf => Texts.Get(TextId.TutorialEnemyIgnoreUnlockedAsWerewolf), TutorialId.NaturalHealUnlockedAsWerewolf => Texts.Get(TextId.TutorialNaturalHealUnlockedAsWerewolf), TutorialId.WerewolfSeesCatAwakened => Texts.Get(TextId.TutorialWerewolfSeesCatAwakened), TutorialId.BeaconFirstUsedAsWerewolf => Texts.Get(TextId.TutorialBeaconFirstUsedAsWerewolf), TutorialId.BlackCatSelectedForExecution => Texts.Get(TextId.TutorialBlackCatSelectedForExecution), TutorialId.BlackCatExecutionRevealed => Texts.Get(TextId.TutorialBlackCatExecutionRevealed), TutorialId.FirstDeath => Texts.Get(TextId.TutorialFirstDeath), TutorialId.BomberRoleDrawn => Texts.Get(TextId.TutorialBomberRoleDrawn), TutorialId.BombPlantedAsBomber => Texts.Get(TextId.TutorialBombPlantedAsBomber), TutorialId.BomberProximityWarnedAsVillager => Texts.Get(TextId.TutorialBomberProximityWarnedAsVillager), TutorialId.SelfBombExplodedAsVillager => Texts.Get(TextId.TutorialSelfBombExplodedAsVillager), TutorialId.ShamanRoleDrawn => Texts.Get(TextId.TutorialShamanRoleDrawn), TutorialId.ShamanGhostSighted => Texts.Get(TextId.TutorialShamanGhostSighted), TutorialId.ShamanTranceEntered => Texts.Get(TextId.TutorialShamanTranceEntered), TutorialId.ShamanStormEntered => Texts.Get(TextId.TutorialShamanStormEntered), TutorialId.EquipBlockedByOtherGrabber => Texts.Get(TextId.TutorialEquipBlockedByOtherGrabber), TutorialId.ValuableRecordSuppressed => Texts.Get(TextId.TutorialValuableRecordSuppressed), _ => null, }; } } public enum TutorialId : byte { CorpseDiscovery, MeetingCountdownStarted, FirstMeetingAsVillager, WerewolfRoleDrawn, FirstValuableSeen, WolfModeFirstUnlock, BeaconFirstCharged, FirstMeetingAsWerewolf, FirstMeetingAsBlackCat, VillagerSeesCatAwakened, BlackCatRoleDrawn, LastRunApproaching, RoundTimeWarningVillager, RoundTimeWarningWerewolf, FinalExtractionVillager, FinalExtractionWerewolf, InformantUnlockedAsWerewolf, InformantUnlockedAsBlackCat, EnemyIgnoreUnlockedAsWerewolf, WerewolfSeesCatAwakened, BeaconFirstUsedAsWerewolf, BlackCatSelectedForExecution, BlackCatExecutionRevealed, FirstDeath, BomberRoleDrawn, BombPlantedAsBomber, BomberProximityWarnedAsVillager, SelfBombExplodedAsVillager, NaturalHealUnlockedAsWerewolf, ShamanRoleDrawn, ShamanGhostSighted, ShamanTranceEntered, ShamanStormEntered, EquipBlockedByOtherGrabber, ValuableRecordSuppressed } public enum ValuableMapMode : byte { Realtime, MeetingSync, Hidden } public static class ValuableMapGate { public static bool ShouldSuppressAdd(ValuableMapMode mode, bool roundActive) { if (roundActive) { if (mode != ValuableMapMode.Hidden) { return mode == ValuableMapMode.MeetingSync; } return true; } return false; } public static bool ShouldSnapshotOnDiscover(ValuableMapMode mode, bool roundActive) { if (roundActive) { return mode == ValuableMapMode.MeetingSync; } return false; } public static bool ShouldRefreshSnapshotAtInventoryPoint(ValuableMapMode mode, bool roundActive) { if (roundActive) { return mode == ValuableMapMode.MeetingSync; } return false; } public static bool ShouldRestoreValuablesOnEnd(ValuableMapMode mode) { if (mode != ValuableMapMode.Hidden) { return mode == ValuableMapMode.MeetingSync; } return true; } } public static class ValuableRecordGate { public static bool IsWerewolfTeam(Role? localRole) { if (localRole.HasValue) { return RoleDistribution.TeamOf(localRole.Value) == Team.Werewolves; } return false; } public static bool ShouldSuppressDiscover(Role? localRole, bool alive, bool roundActive, bool recordOn) { if (roundActive && alive && !recordOn) { return IsWerewolfTeam(localRole); } return false; } public static bool CanOperate(Role? localRole, bool alive, GamePhase phase, bool warpedInMeeting) { if ((phase == GamePhase.Play || phase == GamePhase.Meeting) && !warpedInMeeting && alive) { return IsWerewolfTeam(localRole); } return false; } } public sealed class ValuableRecordHold { public const float HoldSeconds = 1f; private float _held; private bool _fired; private bool _consumed; public float Ratio { get { if (_fired) { return 1f; } if (_consumed) { return 0f; } float num = _held / 1f; if (num < 0f) { return 0f; } if (!(num > 1f)) { return num; } return 1f; } } public bool IsCharging { get { if (!_fired && !_consumed) { return _held > 0f; } return false; } } public bool Tick(bool held, bool pressConsumed, float deltaSeconds) { if (!held) { _held = 0f; _fired = false; _consumed = false; return false; } if (pressConsumed) { _consumed = true; } if (_consumed || _fired) { return false; } if (deltaSeconds > 0f) { _held += deltaSeconds; } if (_held >= 1f) { _held = 1f; _fired = true; return true; } return false; } public void Reset() { _held = 0f; _fired = false; _consumed = false; } } public enum VoicePlanKind : byte { None, ResultAll, Eavesdrop } public static class VoiceRules { public static VoicePlanKind DecideKind(GamePhase phase, Role? localRole, bool localAlive, NecroVoiceMode mode) { switch (phase) { case GamePhase.GameOver: return VoicePlanKind.ResultAll; default: return VoicePlanKind.None; case GamePhase.Play: if ((localRole == Role.Werewolf || localRole == Role.Bomber) && localAlive && mode != NecroVoiceMode.Off) { return VoicePlanKind.Eavesdrop; } return VoicePlanKind.None; } } public static bool IsEavesdropTarget(bool targetDead, bool targetIsKnownWerewolf, NecroVoiceMode mode) { if (!targetDead) { return false; } return mode switch { NecroVoiceMode.AllDead => true, NecroVoiceMode.NonWerewolfDead => !targetIsKnownWerewolf, _ => false, }; } public static bool IsDeadCueMuteActive(GamePhase phase, bool localAlive) { return (phase == GamePhase.Play || phase == GamePhase.Meeting) && localAlive; } public static bool IsDeadCueMuteTarget(bool targetDead, bool targetEavesdropAudible) { if (targetDead) { return !targetEavesdropAudible; } return false; } public static bool ShouldShowDeadCues(GamePhase phase, bool localAlive, bool speakerDead, bool speakerEavesdropAudible) { if (!speakerDead) { return true; } if (!localAlive) { return true; } if (phase == GamePhase.GameOver) { return true; } return speakerEavesdropAudible; } public static bool ShouldShowDeadText(GamePhase phase, bool localAlive, bool speakerDead) { if (!speakerDead) { return true; } if (!localAlive) { return true; } if (phase == GamePhase.Lobby || phase == GamePhase.GameOver) { return true; } return false; } public static bool ShouldApplyNecroFilter(VoicePlanKind plan, bool targetEavesdropAudible) { return plan == VoicePlanKind.Eavesdrop && targetEavesdropAudible; } } public enum VoteRejectReason : byte { None, NotVotingStage, VoterDead, VoterUnknown, AlreadyVoted, TargetDead, TargetGone, TargetUnknown } public sealed class MeetingOutcome { public int ExecutedActor; public int[] TargetActors; public int[] VoteCounts; } public sealed class VoteBox { private readonly HashSet _originalVoters; private readonly HashSet _expectedVoters; private readonly HashSet _originalTargets; private readonly HashSet _deadTargets = new HashSet(); private readonly HashSet _disconnectedActors = new HashSet(); private readonly Dictionary _votes = new Dictionary(); public int ExpectedVoterCount => _expectedVoters.Count; public int RemainingVoterCount { get { int num = 0; foreach (int expectedVoter in _expectedVoters) { if (!_votes.ContainsKey(expectedVoter)) { num++; } } return num; } } public bool AllVotesIn { get { foreach (int expectedVoter in _expectedVoters) { if (!_votes.ContainsKey(expectedVoter)) { return false; } } return true; } } public IReadOnlyCollection VotedActors => _votes.Keys; public VoteBox(IEnumerable aliveActors) { if (aliveActors == null) { throw new ArgumentNullException("aliveActors"); } _originalVoters = new HashSet(aliveActors); _expectedVoters = new HashSet(_originalVoters); _originalTargets = new HashSet(_originalVoters); } public int[] VotersFor(int targetActor) { List list = new List(); foreach (KeyValuePair vote in _votes) { if (vote.Value == targetActor) { list.Add(vote.Key); } } list.Sort(); return list.ToArray(); } public VoteRejectReason TryCast(int voterActor, int targetActor) { if (!_originalVoters.Contains(voterActor)) { return VoteRejectReason.VoterUnknown; } if (!_expectedVoters.Contains(voterActor)) { return VoteRejectReason.VoterDead; } if (_votes.ContainsKey(voterActor)) { return VoteRejectReason.AlreadyVoted; } if (targetActor != -1) { if (_deadTargets.Contains(targetActor)) { return VoteRejectReason.TargetDead; } if (!_originalTargets.Contains(targetActor)) { return VoteRejectReason.TargetUnknown; } } _votes[voterActor] = targetActor; return VoteRejectReason.None; } public void RemoveVoter(int actorNumber) { _expectedVoters.Remove(actorNumber); } public void RemoveTarget(int actorNumber, bool disconnected = false) { if (disconnected) { _disconnectedActors.Add(actorNumber); } else { _deadTargets.Add(actorNumber); } } public MeetingOutcome Tally(Func isEligibleForExecution) { Dictionary dictionary = new Dictionary(); foreach (int expectedVoter in _expectedVoters) { int value; int key = (_votes.TryGetValue(expectedVoter, out value) ? value : (-1)); dictionary.TryGetValue(key, out var value2); dictionary[key] = value2 + 1; } foreach (int disconnectedActor in _disconnectedActors) { if (_originalVoters.Contains(disconnectedActor) && !_votes.ContainsKey(disconnectedActor)) { dictionary.TryGetValue(-1, out var value3); dictionary[-1] = value3 + 1; } } List list = new List(dictionary.Keys); list.Sort(); int[] array = new int[list.Count]; int[] array2 = new int[list.Count]; int num = 0; for (int i = 0; i < list.Count; i++) { array[i] = list[i]; array2[i] = dictionary[list[i]]; if (array2[i] > num) { num = array2[i]; } } int executedActor = -1; if (num > 0) { int num2 = 0; int num3 = -1; for (int j = 0; j < array.Length; j++) { if (array2[j] == num) { num2++; num3 = array[j]; } } if (num2 == 1 && num3 != -1 && (isEligibleForExecution == null || isEligibleForExecution(num3))) { executedActor = num3; } } return new MeetingOutcome { ExecutedActor = executedActor, TargetActors = array, VoteCounts = array2 }; } } public enum WinReason : byte { WerewolvesEradicated, VillagersEradicated, ExtractionCompleted, TimerExpired, ExtractionFailed, ValueCheckmate } public sealed class WinResult { public Team WinningTeam { get; } public WinReason Reason { get; } public WinResult(Team winningTeam, WinReason reason) { WinningTeam = winningTeam; Reason = reason; } } public static class WinJudge { public static WinResult Judge(IReadOnlyList players, bool extractionCompleted = false, bool timerExpired = false) { if (players == null) { throw new ArgumentNullException("players"); } int num = 0; int num2 = 0; foreach (WPlayer player in players) { if (player.Alive) { if (player.Role == Role.Werewolf || player.Role == Role.Bomber) { num++; } else if (RoleDistribution.TeamOf(player.Role) == Team.Villagers) { num2++; } } } if (num == 0) { return new WinResult(Team.Villagers, WinReason.WerewolvesEradicated); } if (num2 == 0) { return new WinResult(Team.Werewolves, WinReason.VillagersEradicated); } if (extractionCompleted) { return new WinResult(Team.Villagers, WinReason.ExtractionCompleted); } if (timerExpired) { return new WinResult(Team.Werewolves, WinReason.TimerExpired); } return null; } } public static class WLog { public const string Prefix = "[WW]"; public static Action Sink; public static void Event(string dir, int code, string target, object[] payload, bool secret = false, int[] targetActors = null) { if (Sink != null) { StringBuilder stringBuilder = new StringBuilder("[WW]").Append(" dir=").Append(dir).Append(" code=") .Append(code.ToString(CultureInfo.InvariantCulture)) .Append(" target=") .Append(target); if (targetActors != null) { stringBuilder.Append(" targetActors="); AppendValue(stringBuilder, targetActors); } stringBuilder.Append(" payload="); AppendValue(stringBuilder, payload ?? Array.Empty()); Emit(stringBuilder, secret); } } public static void Phase(GamePhase from, GamePhase to, string reason) { if (Sink != null) { Emit(new StringBuilder("[WW]").Append(" phase ").Append(from).Append("->") .Append(to) .Append(" reason=") .Append(reason), secret: false); } } public static void Line(string kind, bool secret, params (string Key, object Value)[] fields) { if (Sink == null) { return; } StringBuilder stringBuilder = new StringBuilder("[WW]").Append(' ').Append(kind); if (fields != null) { for (int i = 0; i < fields.Length; i++) { var (value, value2) = fields[i]; stringBuilder.Append(' ').Append(value).Append('='); AppendValue(stringBuilder, value2); } } Emit(stringBuilder, secret); } private static void Emit(StringBuilder sb, bool secret) { if (secret) { sb.Append(" secret=1"); } try { Sink?.Invoke(sb.ToString(), secret); } catch { } } private static void AppendValue(StringBuilder sb, object value) { if (value != null) { if (!(value is bool flag)) { if (!(value is string text)) { if (!(value is IEnumerable enumerable)) { if (value is IFormattable formattable) { sb.Append(formattable.ToString(null, CultureInfo.InvariantCulture)); } else { sb.Append(value); } return; } sb.Append('['); bool flag2 = true; foreach (object item in enumerable) { if (!flag2) { sb.Append(','); } flag2 = false; AppendValue(sb, item); } sb.Append(']'); } else if (text.Length == 0 || text.IndexOf(' ') >= 0 || text.IndexOf('=') >= 0 || text.IndexOf('"') >= 0) { sb.Append('"').Append(text.Replace("\"", "\\\"")).Append('"'); } else { sb.Append(text); } } else { sb.Append(flag ? "true" : "false"); } } else { sb.Append("null"); } } } public enum WolfPerkVisual : byte { Locked, Ready, Active } public readonly struct WolfStatusState { public static readonly WolfStatusState Hidden = new WolfStatusState(visible: false, WolfPerkVisual.Locked, WolfPerkVisual.Locked, WolfPerkVisual.Locked, -1, WolfPerkVisual.Locked, WolfPerkVisual.Locked, 0, 0, 0f); public bool Visible { get; } public WolfPerkVisual Toggle { get; } public WolfPerkVisual Stamina { get; } public WolfPerkVisual Jump { get; } public int JumpCharges { get; } public WolfPerkVisual EnemyIgnore { get; } public WolfPerkVisual Heal { get; } public int BeaconCharges { get; } public int BeaconCooldownSec { get; } public float BeaconGrayFraction { get; } public WolfStatusState(bool visible, WolfPerkVisual toggle, WolfPerkVisual stamina, WolfPerkVisual jump, int jumpCharges, WolfPerkVisual enemyIgnore, WolfPerkVisual heal, int beaconCharges, int beaconCooldownSec, float beaconGrayFraction) { Visible = visible; Toggle = toggle; Stamina = stamina; Jump = jump; JumpCharges = jumpCharges; EnemyIgnore = enemyIgnore; Heal = heal; BeaconCharges = beaconCharges; BeaconCooldownSec = beaconCooldownSec; BeaconGrayFraction = beaconGrayFraction; } } public sealed class WolfStatusModel { private long _trackedReadyUnixMs; private long _anchorUnixMs; public WolfStatusState Compute(RolesClientState roles, Role? effectiveRole, GamePhase phase, bool warpedInMeeting, long nowUnixMs, int extraJumpLimit = -1, int jumpRefillsUsed = 0, bool injectedJumpAvailable = false) { if ((phase != GamePhase.Play && phase != GamePhase.Meeting) || warpedInMeeting || roles == null || effectiveRole != Role.Werewolf || !roles.CanShowHud(effectiveRole)) { return WolfStatusState.Hidden; } float num = CooldownFraction(roles.BeaconReadyUnixMs, nowUnixMs); long num2 = roles.BeaconReadyUnixMs - nowUnixMs; int num3 = (int)((num2 > 0) ? ((num2 + 999) / 1000) : 0); float beaconGrayFraction = ((num3 > 0) ? num : ((roles.BeaconCharges <= 0) ? 1f : 0f)); bool wolfMode = roles.WolfMode; return new WolfStatusState(visible: true, (roles.UnlockedFlags != PerkFlags.None) ? ((!wolfMode) ? WolfPerkVisual.Ready : WolfPerkVisual.Active) : WolfPerkVisual.Locked, SlotVisual(roles.UnlockedFlags, PerkId.InfiniteStamina, wolfMode), SlotVisual(roles.UnlockedFlags, PerkId.InfiniteJump, wolfMode), JumpCharges(roles.UnlockedFlags, extraJumpLimit, jumpRefillsUsed, injectedJumpAvailable), SlotVisual(roles.UnlockedFlags, PerkId.EnemyIgnore, wolfMode), SlotVisual(roles.UnlockedFlags, PerkId.NaturalHeal, wolfMode), roles.BeaconCharges, num3, beaconGrayFraction); } public void Reset() { _trackedReadyUnixMs = 0L; _anchorUnixMs = 0L; } private float CooldownFraction(long readyUnixMs, long nowUnixMs) { if (readyUnixMs != _trackedReadyUnixMs) { _trackedReadyUnixMs = readyUnixMs; _anchorUnixMs = nowUnixMs; } if (readyUnixMs <= nowUnixMs) { return 0f; } long num = readyUnixMs - _anchorUnixMs; if (num <= 0) { return 0f; } float num2 = (float)(readyUnixMs - nowUnixMs) / (float)num; if (!(num2 > 1f)) { return num2; } return 1f; } private static WolfPerkVisual SlotVisual(PerkFlags unlocked, PerkId perk, bool wolfOn) { if (!PerkFlagsUtil.Has(unlocked, perk)) { return WolfPerkVisual.Locked; } if (!wolfOn) { return WolfPerkVisual.Ready; } return WolfPerkVisual.Active; } private static int JumpCharges(PerkFlags unlocked, int extraJumpLimit, int jumpRefillsUsed, bool injectedJumpAvailable) { if (!PerkFlagsUtil.Has(unlocked, PerkId.InfiniteJump)) { return -1; } if (extraJumpLimit < 0) { return -1; } int num = extraJumpLimit - jumpRefillsUsed + (injectedJumpAvailable ? 1 : 0); if (num > extraJumpLimit) { num = extraJumpLimit; } if (num <= 0) { return 0; } return num; } } public interface IWorldgenCatalog { IReadOnlyDictionary ItemMaxAmounts { get; } IReadOnlyList PlayableLevelNames { get; } } public sealed class WorldgenPlan { public int LevelsCompleted; public string ForcedLevelName; public IReadOnlyDictionary Items; public int PowerCrystals; public int ChargeTotal; public IReadOnlyDictionary Upgrades; public int OrbDropMax; } public static class WorldgenPlanner { private const int MinLevelNumber = 1; private const int MaxLevelNumber = 99; private const int MinEnergyPct = 0; private const int MaxEnergyPct = 100; private const int PctPerCrystal = 10; private const int MinOrbDropMax = 0; private const int MaxOrbDropMax = 25; public static WorldgenPlan BuildPlan(GameConfig config, IWorldgenCatalog catalog) { if (config == null) { throw new ArgumentNullException("config"); } if (catalog == null) { throw new ArgumentNullException("catalog"); } int num = Clamp(config.StartLevelNumber, 1, 99); int num2 = Clamp(config.StartEnergyPct, 0, 100); return new WorldgenPlan { LevelsCompleted = num - 1, ForcedLevelName = ResolveForcedLevelName(config.StartMapName, catalog.PlayableLevelNames), Items = BuildItems(config.StartItemsSpec, catalog.ItemMaxAmounts), ChargeTotal = num2, PowerCrystals = (num2 + 10 - 1) / 10, Upgrades = WorldgenSpec.Decode(config.StartUpgradesSpec), OrbDropMax = Clamp(config.OrbDropMax, 0, 25) }; } private static string ResolveForcedLevelName(string mapName, IReadOnlyList playableLevelNames) { if (string.IsNullOrWhiteSpace(mapName)) { return null; } string text = mapName.Trim(); for (int i = 0; i < playableLevelNames.Count; i++) { if (string.Equals(playableLevelNames[i], text, StringComparison.Ordinal)) { return text; } } return null; } private static IReadOnlyDictionary BuildItems(string itemsSpec, IReadOnlyDictionary itemMaxAmounts) { Dictionary dictionary = new Dictionary(); foreach (KeyValuePair item in WorldgenSpec.Decode(itemsSpec)) { if (itemMaxAmounts.TryGetValue(item.Key, out var value)) { int num = ((item.Value < value) ? item.Value : value); if (num > 0) { dictionary[item.Key] = num; } } } return dictionary; } private static int Clamp(int value, int min, int max) { if (value >= min) { if (value <= max) { return value; } return max; } return min; } } public static class WorldgenUpgrades { public static readonly IReadOnlyList Names = new string[13] { "CrouchRest", "DeathHeadBattery", "ExtraJump", "Health", "Launch", "MapPlayerCount", "Range", "Speed", "Stamina", "Strength", "Throw", "TumbleClimb", "TumbleWings" }; } public static class WorldgenUpgradeDeltas { public static IReadOnlyList<(string Name, int Delta)> Compute(IReadOnlyDictionary targets, Func currentStage) { if (currentStage == null) { throw new ArgumentNullException("currentStage"); } List<(string, int)> list = new List<(string, int)>(); if (targets == null || targets.Count == 0) { return list; } List list2 = new List(targets.Keys); list2.Sort(StringComparer.Ordinal); foreach (string item in list2) { int num = targets[item] - currentStage(item); if (num != 0) { list.Add((item, num)); } } return list; } } public static class WorldgenStartHealth { private const int BaseMaxHealth = 100; private const int HealthPerStage = 20; private const int MinHealth = 1; public static int Compute(int currentHp, int healthDelta, int targetStage) { int num = 100 + 20 * targetStage; int num2 = currentHp + 20 * healthDelta; if (num2 >= 1) { if (num2 <= num) { return num2; } return num; } return 1; } } public static class WorldgenSpec { private const char EntrySep = ','; private const char KvSep = ':'; private static readonly char[] ForbiddenNameChars = new char[5] { ',', ':', '|', ';', '=' }; public static string Encode(IReadOnlyDictionary map) { return Encode(map, null); } public static string Encode(IReadOnlyDictionary map, ICollection skippedNames) { if (map == null || map.Count == 0) { return ""; } List list = new List(map.Count); foreach (KeyValuePair item in map) { if (item.Value > 0) { string key = item.Key; if (string.IsNullOrWhiteSpace(key) || key.IndexOfAny(ForbiddenNameChars) >= 0) { skippedNames?.Add(key); } else { list.Add(key); } } } if (list.Count == 0) { return ""; } list.Sort(StringComparer.Ordinal); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < list.Count; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(list[i]); stringBuilder.Append(':'); stringBuilder.Append(map[list[i]].ToString(CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } public static IReadOnlyDictionary Decode(string spec) { Dictionary dictionary = new Dictionary(); if (string.IsNullOrWhiteSpace(spec)) { return dictionary; } string[] array = spec.Split(','); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(':'); if (array2.Length == 2) { string text = array2[0].Trim(); if (text.Length != 0 && int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0) { dictionary[text] = result; } } } return dictionary; } } public static class WorldgenSaveGuard { public static bool ShouldDelete(bool leaveGame, bool active, string markedSaveFileName, string currentSaveFileName) { if (!leaveGame) { return false; } if (!active) { return false; } if (string.IsNullOrEmpty(markedSaveFileName)) { return false; } if (string.IsNullOrEmpty(currentSaveFileName)) { return false; } return string.Equals(markedSaveFileName, currentSaveFileName, StringComparison.Ordinal); } } public enum DeathCause : byte { Vote, Other } public sealed class WPlayer { public int ActorNumber; public string Name; public string SteamId; public bool IsBot; public Role Role; public bool Alive = true; public DeathCause? DeathCause; } }