using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks.CompilerServices; using HarmonyLib; using LBOLMP.Entities; using LBOLMP.Net; using LBOLMP.Patches; using LBOLMP.Session; using LBOLMP.Session.Battle; using LBOLMP.Session.Messages; using LBOLMP.UI; using LBoL.Base; using LBoL.ConfigData; using LBoL.Core; using LBoL.Core.Adventures; using LBoL.Core.Battle; using LBoL.Core.Battle.BattleActions; using LBoL.Core.Cards; using LBoL.Core.Dialogs; using LBoL.Core.PlatformHandlers; using LBoL.Core.SaveData; using LBoL.Core.Stations; using LBoL.Core.StatusEffects; using LBoL.Core.Units; using LBoL.EntityLib.Adventures.FirstPlace; using LBoL.EntityLib.Cards.Misfortune; using LBoL.EntityLib.EnemyUnits.Character; using LBoL.EntityLib.Exhibits.Adventure; using LBoL.EntityLib.Stages.NormalStages; using LBoL.EntityLib.StatusEffects.Cirno; using LBoL.EntityLib.StatusEffects.Enemy; using LBoL.EntityLib.StatusEffects.Neutral.TwoColor; using LBoL.Presentation; using LBoL.Presentation.UI; using LBoL.Presentation.UI.ExtraWidgets; using LBoL.Presentation.UI.Panels; using LBoL.Presentation.UI.Widgets; using LBoL.Presentation.Units; using LBoLEntitySideloader; using LBoLEntitySideloader.Attributes; using LBoLEntitySideloader.Entities; using LBoLEntitySideloader.Resource; using Microsoft.CodeAnalysis; using Steamworks; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("LBoL.Base")] [assembly: IgnoresAccessChecksTo("LBoL.ConfigData")] [assembly: IgnoresAccessChecksTo("LBoL.Core")] [assembly: IgnoresAccessChecksTo("LBoL.EntityLib")] [assembly: IgnoresAccessChecksTo("LBoL.Presentation")] [assembly: IgnoresAccessChecksTo("Untitled.ConfigDataBuilder.Base")] [assembly: AssemblyCompany("LBOLMP")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.8.9.0")] [assembly: AssemblyInformationalVersion("0.8.9+e55d67b57f461bca56ebe2e38d779c1eede32587")] [assembly: AssemblyProduct("LBOLMP")] [assembly: AssemblyTitle("LBOLMP")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.8.9.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LBOLMP { public static class L10n { private const string Marker = "mp:"; private const char Separator = '|'; private const char Escape = '\\'; public static Locale Current { get { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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: Invalid comparison between Unknown and I4 //IL_0019: 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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 Locale currentLocale; try { currentLocale = Localization.CurrentLocale; } catch { return (Locale)0; } if ((int)currentLocale != 1 && (int)currentLocale != 2) { return (Locale)0; } return currentLocale; } } public static string Get(MpText key) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Format(Lookup(key, Current), null); } public static string Get(MpText key, params object[] args) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Format(Lookup(key, Current), args); } private static string Get(MpText key, Locale locale, object[] args) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Format(Lookup(key, locale), args); } public static string En(MpText key) { return Format(Lookup(key, (Locale)0), null); } public static string En(MpText key, params object[] args) { return Format(Lookup(key, (Locale)0), args); } public static string Encode(MpText key, params object[] args) { StringBuilder stringBuilder = new StringBuilder("mp:").Append(key.ToString()); if (args != null) { foreach (object obj in args) { stringBuilder.Append('|').Append(EscapeArg(Convert.ToString(obj, CultureInfo.InvariantCulture))); } } return stringBuilder.ToString(); } public static string Decode(string text) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Decode(text, Current); } public static string DecodeEn(string text) { return Decode(text, (Locale)0); } private static string Decode(string text, Locale locale) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(text) || !text.StartsWith("mp:", StringComparison.Ordinal)) { return text ?? string.Empty; } List list = SplitArgs(text.Substring("mp:".Length)); if (list.Count == 0 || !Enum.TryParse(list[0], out var result)) { return text; } object[] array = new object[list.Count - 1]; for (int i = 1; i < list.Count; i++) { array[i - 1] = list[i]; } return Get(result, locale, array); } private static string Lookup(MpText key, Locale locale) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 if (!MpStrings.Table.TryGetValue(key, out var value)) { return key.ToString(); } if ((int)locale == 2 && !string.IsNullOrWhiteSpace(value.ZhHant)) { return value.ZhHant; } if (((int)locale == 1 || (int)locale == 2) && !string.IsNullOrWhiteSpace(value.ZhHans)) { return value.ZhHans; } if (!string.IsNullOrEmpty(value.En)) { return value.En; } return key.ToString(); } private static string Format(string pattern, object[] args) { if (args == null || args.Length == 0) { return pattern; } try { return string.Format(CultureInfo.InvariantCulture, pattern, args); } catch (FormatException) { ManualLogSource log = MpPlugin.Log; if (log != null) { log.LogWarning((object)("Malformed localised text: '" + pattern + "'")); } return pattern; } } private static string EscapeArg(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } return value.Replace('\\'.ToString(), "\\\\").Replace('|'.ToString(), "\\|"); } private static List SplitArgs(string body) { List list = new List(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; foreach (char c in body) { if (flag) { stringBuilder.Append(c); flag = false; continue; } switch (c) { case '\\': flag = true; break; case '|': list.Add(stringBuilder.ToString()); stringBuilder.Length = 0; break; default: stringBuilder.Append(c); break; } } list.Add(stringBuilder.ToString()); return list; } internal static void Verify() { List list = new List(); foreach (MpText value in Enum.GetValues(typeof(MpText))) { if (!MpStrings.Table.ContainsKey(value)) { list.Add(value.ToString()); } } if (list.Count > 0) { MpPlugin.Log.LogWarning((object)string.Format("{0} text key(s) have no entry in MpStrings: {1}", list.Count, string.Join(", ", list))); } } } public enum MpText { LobbyWindowTitle, LobbyNotConnected, LobbyYourName, LobbyDirectConnection, LobbyPort, LobbyHostSession, LobbyHostAddress, LobbyJoinSession, LobbyPortNotANumber, LobbyOfflineHelp, LobbySteam, LobbySteamUnavailable, LobbyHostOverSteam, LobbySteamHelp, LobbyHosting, LobbyConnected, LobbyHostingDirectIp, LobbyHostingSteam, LobbyConnectedDirectIp, LobbyConnectedSteam, LobbyInviteFriends, LobbyTagHost, LobbyTagYou, LobbyChoosing, LobbyPlayerRow, LobbyLockedIn, LobbySeed, LobbyMapVoteHint, LobbyLeaveSession, LobbyBalanceSettings, SettingsWindowTitle, SettingsIntro, SettingsHostNote, SettingsNextRunNote, SettingsLockedForThisRun, SettingsDefault, SettingsResetAll, SettingsClose, SettingsNothingToShow, SettingEnemyHpScaleName, SettingEnemyHpScaleHelp, SettingEscalationName, SettingEscalationHelp, SettingReviveHpName, SettingReviveHpHelp, SettingResilienceName, SettingResilienceHelp, HudParty, HudPlayerRow, HudVoteNode, HudVoteStillChoosing, StateInLobby, StateReady, StateResuming, StateHp, StateDisconnected, BoardDefeated, BoardSittingOut, BoardWaitingForOne, BoardWaitingForOneToFinish, BoardWaitingForMany, BoardLostContact, BoardBlock, BoardShield, ActivitySpectating, ActivityDownSpectating, ActivityDone, ActivityDown, ActivityTurnOver, ActivityHand, EmoteNice, EmoteNegative, EmoteHurryUp, MapWaitingForOne, MapWaitingForMany, MapMovingToNode, MapPartySplit, MapPick, MapHeadingTo, RestartPartyMoving, InspectBanner, InspectZoneTitle, StatusHostFailed, StatusHostingOnPort, StatusHostSteamFailed, StatusOpeningSteamLobby, StatusHostingOverSteam, StatusConnecting, StatusConnectFailed, StatusAlreadyInSession, StatusConnectingSteam, StatusConnectSteamFailed, StatusConnectedAsPlayer, StatusRejected, StatusWaitingForPlayers, StatusWaitingForNames, StatusWaitingToResume, StatusRunStarted, StatusRunResumed, StatusBackInLobby, NoticeResumeStaggered, StatusPlayerLeft, StatusDisconnected, StatusSteamLobbyFailed, StatusSteamJoinFailed, ReasonProtocolMismatch, ReasonRunInProgress, ReasonStartSplit, ReasonResumeDifferentRuns, ReasonSessionFull, ReasonYouLeft, ReasonRemoteClosed, ReasonConnectionFailed, ReasonSteamClosed, ReasonReadFailed, ReasonWriteFailed, ReasonSendFailed, ReasonTimedOut, ErrorSteamUnavailable, ErrorSteamListenFailed, ErrorSteamConnectFailed } internal readonly struct MpPhrase { internal readonly string En; internal readonly string ZhHans; internal readonly string ZhHant; internal MpPhrase(string en, string zhHans, string zhHant) { En = en; ZhHans = zhHans; ZhHant = zhHant; } } internal static class MpStrings { internal static readonly Dictionary Table = new Dictionary { [MpText.LobbyWindowTitle] = P("LBOL MP v{0}", "LBOL MP v{0}", "LBOL MP v{0}"), [MpText.LobbyNotConnected] = P("Not connected", "未连接", "未連接"), [MpText.LobbyYourName] = P("Your name", "你的名字", "你的名字"), [MpText.LobbyDirectConnection] = P("Direct connection", "直接连接", "直接連接"), [MpText.LobbyPort] = P("Port", "端口", "端口"), [MpText.LobbyHostSession] = P("Host session", "创建房间", "建立房間"), [MpText.LobbyHostAddress] = P("Host address", "房主 IP 地址", "房主 IP 位址"), [MpText.LobbyJoinSession] = P("Join session", "加入房间", "加入房間"), [MpText.LobbyPortNotANumber] = P("Port is not a number", "端口必须是数字", "端口必須是數字"), [MpText.LobbyOfflineHelp] = P("Host should host a session, have everyone else join, then all of you pick a character on the Start Game screen. The run begins once everybody has confirmed.", "由房主建立房间,其他人加入,然后所有人在正常的开始游戏画面选择角色。所有人确认后开始游戏。", "由房主建立房間,其他人加入,然後所有人在正常的開始遊戲畫面選擇角色。所有人確認後開始遊戲。"), [MpText.LobbySteam] = P("Steam", "Steam", "Steam"), [MpText.LobbySteamUnavailable] = P("Steam is currently not available, only direct IP connections can be used.", "当前无法使用 Steam,只能使用直接 IP 连接。", "目前無法使用 Steam,只能使用直接 IP 連接。"), [MpText.LobbyHostOverSteam] = P("Host over Steam", "通过 Steam 创建房间", "通過 Steam 建立房間"), [MpText.LobbySteamHelp] = P("Once the session is hosted, invite friends from the Steam overlay. To join someone else, accept their invite or join via Steam.", "创建房间后,可从 Steam 介面邀请好友。请接受对方的邀请以加入房间,或通过 Steam 加入房间。", "建立房間後,可從 Steam 介面邀請好友。請接受對方的邀請以加入房間,或透過 Steam 加入房間。"), [MpText.LobbyHosting] = P("Hosting", "主持中", "主持中"), [MpText.LobbyConnected] = P("Connected", "已连接", "已連接"), [MpText.LobbyHostingDirectIp] = P("Hosting over direct IP, port {0}", "正在通过直接 IP 主持房间,端口 {0}", "正在透過直接 IP 主持房間,端口 {0}"), [MpText.LobbyHostingSteam] = P("Hosting over Steam", "正在通过 Steam 主持房间", "正在透過 Steam 主持房間"), [MpText.LobbyConnectedDirectIp] = P("Connected over direct IP to {0}:{1}", "已通过直接 IP 连接到 {0}:{1}", "已透過直接 IP 連接到 {0}:{1}"), [MpText.LobbyConnectedSteam] = P("Connected over Steam to {0}", "已通过 Steam 连接到房主 {0}", "已透過 Steam 連接到房主 {0}"), [MpText.LobbyInviteFriends] = P("Invite friends...", "邀请好友⋯⋯", "邀請好友⋯⋯"), [MpText.LobbyTagHost] = P(" [host]", " [房主]", " [房主]"), [MpText.LobbyTagYou] = P(" [you]", " [你]", " [你]"), [MpText.LobbyChoosing] = P("choosing...", "选择中⋯⋯", "選擇中⋯⋯"), [MpText.LobbyPlayerRow] = P("#{0} {1}{2} {3} {4}", "#{0} {1}{2} {3} {4}", "#{0} {1}{2} {3} {4}"), [MpText.LobbyLockedIn] = P("Locked in. Waiting for everyone else to confirm their character.", "已锁定。等待其他人确认角色。", "已鎖定。等待其他人確認角色。"), [MpText.LobbySeed] = P("Seed {0}", "种子 {0}", "種子 {0}"), [MpText.LobbyMapVoteHint] = P("Click a map node to vote for it. The party moves once everyone has voted for the same node.", "点击地图节点以投票。所有人投给同一个节点后,队伍才会前进。", "點擊地圖節點以投票。所有人投給同一個節點後,隊伍才會前進。"), [MpText.LobbyLeaveSession] = P("Leave session", "离开房间", "離開房間"), [MpText.LobbyBalanceSettings] = P("Balance settings...", "平衡性设定⋯⋯", "平衡性設定⋯⋯"), [MpText.SettingsWindowTitle] = P("Balance settings", "平衡性设定", "平衡性設定"), [MpText.SettingsIntro] = P("How much harder the game gets with more players.", "人数越多,游戏难度提升多少。", "人數越多,遊戲難度提升多少。"), [MpText.SettingsHostNote] = P("All of these are decided by the host. If you join someone else's session, theirs are used and yours are ignored.", "以下所有设定均由房主决定。加入别人的房间时仅使用房主的设定。", "以下所有設定均由房主決定。加入別人的房間時僅使用房主的設定。"), [MpText.SettingsNextRunNote] = P("Changes are saved straight away and apply from the next run onwards.", "修改会立即保存,并从下一局开始生效。", "修改會立即儲存,並從下一局開始生效。"), [MpText.SettingsLockedForThisRun] = P("A run is in progress, changes won't apply until the next run.", "当前有正在进行的游戏。本局沿用开始时的数值,此处的修改将在下一局生效。", "目前有正在進行的遊戲。本局沿用開始時的數值,此處的修改將在下一局生效。"), [MpText.SettingsDefault] = P("Default", "预设", "預設"), [MpText.SettingsResetAll] = P("Reset all to defaults", "全部恢复预设", "全部恢復預設"), [MpText.SettingsClose] = P("Close", "关闭", "關閉"), [MpText.SettingsNothingToShow] = P("No balance settings were found.", "未找到平衡性设定。", "未找到平衡性設定。"), [MpText.SettingEnemyHpScaleName] = P("Enemy health per extra player", "每位额外玩家的敌人生命值", "每位額外玩家的敵人生命值"), [MpText.SettingEnemyHpScaleHelp] = P("Extra max HP every enemy gets for each player beyond the first.", "除第一位玩家外,每多一位玩家,敌人最大生命值增加的比例。", "除第一位玩家外,每多一位玩家,敵人最大生命值增加的比例。"), [MpText.SettingEscalationName] = P("Act {0} escalation", "第 {0} 章额外加成", "第 {0} 章額外加成"), [MpText.SettingEscalationHelp] = P("Extra enemy max HP in this act, on top of the setting above. This stacks an additional time for each extra player, adding progressively more HP with more players. 0 turns it off for this act.", "本章中在上一项设定之外额外增加的敌人最大生命值,并且会叠加:每多一位玩家,加成都比前一位多一份。设为 0.1 时,第 2 位玩家增加 +10%,第 3 位再增加 +20%,第 4 位再增加 +30%。与上一项相加,而非相乘。设为 0 则本章不启用。", "本章中在上一項設定之外額外增加的敵人最大生命值,並且會疊加:每多一位玩家,加成都比前一位多一份。設為 0.1 時,第 2 位玩家增加 +10%,第 3 位再增加 +20%,第 4 位再增加 +30%。與上一項相加,而非相乘。設為 0 則本章不啟用。"), [MpText.SettingReviveHpName] = P("Revive health", "复活时的生命值", "復活時的生命值"), [MpText.SettingReviveHpHelp] = P("How much of their max health a defeated player comes back with when the party wins the fight. Between 0 and 1 (0%-100%). Players always revive with at least 1 HP.", "队伍获胜时,被击倒的玩家按最大生命值的多少比例复活。取值 0 到 1,且至少为 1 点生命。", "隊伍獲勝時,被擊倒的玩家按最大生命值的多少比例復活。取值 0 到 1,且至少為 1 點生命。"), [MpText.SettingResilienceName] = P("Enemies are Resilient", "敌人拥有「坚韧」", "敵人擁有「堅韌」"), [MpText.SettingResilienceHelp] = P("Give every enemy the Resilient status effect. For each extra player, enemies lose debuffs faster. Disable this to make debuffs decay as fast as in single player.", "让每个敌人获得「坚韧」状态。除第一位玩家外,每多一位玩家,敌人在回合结束时额外失去 1 层虚弱、易伤和锁定,并且获得的失去火力减少 1 点,但不低于 1 点。关闭后,减益效果与单人游戏时完全相同。", "讓每個敵人獲得「堅韌」狀態。除第一位玩家外,每多一位玩家,敵人在回合結束時額外失去 1 層虛弱、易傷和鎖定,並且獲得的失去火力減少 1 點,但不低於 1 點。關閉後,減益效果與單人遊戲時完全相同。"), [MpText.HudParty] = P("Party ({0})", "队伍 ({0})", "隊伍 ({0})"), [MpText.HudPlayerRow] = P("{0} {1}/{2}{3}", "{0} {1}/{2}{3}", "{0} {1}/{2}{3}"), [MpText.HudVoteNode] = P(" -> ({0},{1})", " -> ({0},{1})", " -> ({0},{1})"), [MpText.HudVoteStillChoosing] = P(" -> still choosing", " -> 仍在选择", " -> 仍在選擇"), [MpText.StateInLobby] = P("in lobby", "在房间中", "在房間中"), [MpText.StateReady] = P("ready", "已准备", "已準備"), [MpText.StateResuming] = P("ready to continue", "已准备继续", "已準備繼續"), [MpText.StateHp] = P("{0}/{1} HP", "{0}/{1} 生命", "{0}/{1} 生命"), [MpText.StateDisconnected] = P("disconnected", "已失去连接", "已失去連接"), [MpText.BoardDefeated] = P("You've been defeated. Wait for your partners to finish the combat.", "你已被击败。等待队友结束这场战斗。", "你已被擊敗。等待隊友結束這場戰鬥。"), [MpText.BoardSittingOut] = P("You're spectating this fight.", "你正在观战这场战斗。", "你正在觀戰這場戰鬥。"), [MpText.BoardWaitingForOne] = P("Waiting for {0} to finish their turn", "等待 {0} 结束回合", "等待 {0} 結束回合"), [MpText.BoardWaitingForOneToFinish] = P("Waiting for {0} to finish the fight", "等待 {0} 结束战斗", "等待 {0} 結束戰鬥"), [MpText.BoardWaitingForMany] = P("Waiting for {0}", "等待 {0}", "等待 {0}"), [MpText.BoardLostContact] = P("Connection with {0} lost. Continuing without them for now.", "连接已断开:{0}。 暂时先不等他们了。", "連接已中斷:{0}。 暫時先不等他們了。"), [MpText.BoardBlock] = P("BLK {0}", "格挡 {0}", "格擋 {0}"), [MpText.BoardShield] = P("SHD {0}", "护盾 {0}", "護盾 {0}"), [MpText.ActivitySpectating] = P("spectating", "观战中", "觀戰中"), [MpText.ActivityDownSpectating] = P("down, spectating", "已倒下,观战中", "已倒下,觀戰中"), [MpText.ActivityDone] = P("done", "已结束战斗", "已結束戰鬥"), [MpText.ActivityDown] = P("down", "已倒下", "已倒下"), [MpText.ActivityTurnOver] = P("turn over", "回合已结束", "回合已結束"), [MpText.ActivityHand] = P("hand {0}", "手牌 {0}", "手牌 {0}"), [MpText.EmoteNice] = P("Nice!", "漂亮!", "漂亮!"), [MpText.EmoteNegative] = P("...", "……", "……"), [MpText.EmoteHurryUp] = P("Any day now...", "快点吧……", "快點吧……"), [MpText.MapWaitingForOne] = P("Waiting for {0} to pick a node", "等待 {0} 选择节点", "等待 {0} 選擇節點"), [MpText.MapWaitingForMany] = P("Waiting for {0}", "等待 {0}", "等待 {0}"), [MpText.MapMovingToNode] = P("Moving to voted node", "正在前往投票选中的节点", "正在前往投票選中的節點"), [MpText.MapPartySplit] = P("Party is split: {0}", "队伍意见不一:{0}", "隊伍意見不一:{0}"), [MpText.MapPick] = P("{0} -> ({1},{2})", "{0} -> ({1},{2})", "{0} -> ({1},{2})"), [MpText.MapHeadingTo] = P("Heading to ({0}, {1})", "正在前往 ({0}, {1})", "正在前往 ({0}, {1})"), [MpText.RestartPartyMoving] = P("Can't restart while the party is moving to the next node. Try again once you arrive.", "队伍正在前往下一个节点,此时无法重来。抵达之后再试一次。", "隊伍正在前往下一個節點,此時無法重來。抵達之後再試一次。"), [MpText.InspectBanner] = P("Viewing {0}'s hand. Right-click or Esc to go back", "查看 {0} 的手牌中。按右键或 Esc 键以退回。", "查看 {0} 的手牌中。按右鍵或 Esc 鍵以退回。"), [MpText.InspectZoneTitle] = P("{0} - {1}", "{0} - {1}", "{0} - {1}"), [MpText.StatusHostFailed] = P("Could not host: {0}", "无法创建房间:{0}", "無法建立房間:{0}"), [MpText.StatusHostingOnPort] = P("Hosting on port {0}", "正在端口 {0} 上主持房间", "正在端口 {0} 上主持房間"), [MpText.StatusHostSteamFailed] = P("Could not host over Steam: {0}", "无法通过 Steam 创建房间:{0}", "無法透過 Steam 建立房間:{0}"), [MpText.StatusOpeningSteamLobby] = P("Opening a Steam lobby...", "正在创建 Steam 房间⋯⋯", "正在建立 Steam 房間⋯⋯"), [MpText.StatusHostingOverSteam] = P("Hosting over Steam. Invite friends from the overlay.", "正在通过 Steam 主持房间。可从 Steam 介面邀请好友。", "正在透過 Steam 主持房間。可從 Steam 介面邀請好友。"), [MpText.StatusConnecting] = P("Connecting...", "正在连接⋯⋯", "正在連接⋯⋯"), [MpText.StatusConnectFailed] = P("Could not connect: {0}", "无法连接:{0}", "無法連接:{0}"), [MpText.StatusAlreadyInSession] = P("Leave your current session before joining another", "加入其他房间前,请先离开当前房间", "加入其他房間前,請先離開當前房間"), [MpText.StatusConnectingSteam] = P("Connecting over Steam...", "正在通过 Steam 连接⋯⋯", "正在透過 Steam 連接⋯⋯"), [MpText.StatusConnectSteamFailed] = P("Could not connect over Steam: {0}", "无法通过 Steam 连接:{0}", "無法透過 Steam 連接:{0}"), [MpText.StatusConnectedAsPlayer] = P("Connected as player {0}", "已作为玩家 {0} 连接", "已作為玩家 {0} 連接"), [MpText.StatusRejected] = P("Rejected: {0}", "已被拒绝:{0}", "已被拒絕:{0}"), [MpText.StatusWaitingForPlayers] = P("Waiting for the other players...", "等待其他玩家⋯⋯", "等待其他玩家⋯⋯"), [MpText.StatusWaitingForNames] = P("Waiting for {0}...", "等待 {0}⋯⋯", "等待 {0}⋯⋯"), [MpText.StatusWaitingToResume] = P("Ready to continue. Waiting for the rest of the party...", "已准备继续。等待其他玩家⋯⋯", "已準備繼續。等待其他玩家⋯⋯"), [MpText.StatusRunStarted] = P("Run started (seed {0})", "游戏已开始(种子 {0})", "遊戲已開始(種子 {0})"), [MpText.StatusRunResumed] = P("Run continued (seed {0})", "游戏已继续(种子 {0})", "遊戲已繼續(種子 {0})"), [MpText.StatusBackInLobby] = P("Back in the lobby. Start a new run, or continue this one together.", "已回到房间。可以开始新游戏,或一起继续这局。", "已回到房間。可以開始新遊戲,或一起繼續這局。"), [MpText.NoticeResumeStaggered] = P("Not everyone saved at the same point. Whoever is behind will catch the party up.", "并非所有人都在同一处保存。落后的玩家会赶上大家。", "並非所有人都在同一處保存。落後的玩家會趕上大家。"), [MpText.StatusPlayerLeft] = P("{0} left: {1}", "{0} 已离开:{1}", "{0} 已離開:{1}"), [MpText.StatusDisconnected] = P("Disconnected: {0}", "连接已断开:{0}", "連接已中斷:{0}"), [MpText.StatusSteamLobbyFailed] = P("Cannot open a Steam lobby", "Steam 未能创建房间,无法邀请好友", "Steam 未能建立房間,無法邀請好友"), [MpText.StatusSteamJoinFailed] = P("Could not join that Steam lobby", "无法加入该 Steam 房间", "無法加入該 Steam 房間"), [MpText.ReasonProtocolMismatch] = P("Version mismatch: host runs protocol {0}, you run {1}, please ensure the mod is up to date", "版本不一致:房主使用协议 {0},你使用协议 {1}", "版本不一致:房主使用協定 {0},你使用協定 {1}"), [MpText.ReasonRunInProgress] = P("The run has already started, cannot join", "游戏已经开始了", "遊戲已經開始了"), [MpText.ReasonStartSplit] = P("Some of the party started a new run and others continued a saved one. Everyone has to do the same thing.", "有人开始了新游戏,有人却在继续存档。所有人必须做同样的选择。", "有人開始了新遊戲,有人卻在繼續存檔。所有人必須做同樣的選擇。"), [MpText.ReasonResumeDifferentRuns] = P("These saves are not the same run. Everyone has to continue the run you were playing together.", "这些存档不是同一局游戏。所有人都要继续你们一起玩的那一局。", "這些存檔不是同一局遊戲。所有人都要繼續你們一起玩的那一局。"), [MpText.ReasonSessionFull] = P("Session is full, cannot join", "房间已满", "房間已滿"), [MpText.ReasonYouLeft] = P("You left the session", "你离开了房间", "你離開了房間"), [MpText.ReasonRemoteClosed] = P("Remote closed the connection", "对方关闭了连接", "對方關閉了連接"), [MpText.ReasonConnectionFailed] = P("Connection failed", "连接失败", "連接失敗"), [MpText.ReasonSteamClosed] = P("Steam connection closed", "Steam 连接已关闭", "Steam 連接已關閉"), [MpText.ReasonReadFailed] = P("Read failed: {0}", "读取失败:{0}", "讀取失敗:{0}"), [MpText.ReasonWriteFailed] = P("Write failed: {0}", "写入失败:{0}", "寫入失敗:{0}"), [MpText.ReasonSendFailed] = P("Send failed: {0}", "发送失败:{0}", "傳送失敗:{0}"), [MpText.ReasonTimedOut] = P("Timed out connecting to {0}:{1}", "连接 {0}:{1} 超时", "連接 {0}:{1} 逾時"), [MpText.ErrorSteamUnavailable] = P("Steam is not available", "Steam 不可用", "Steam 無法使用"), [MpText.ErrorSteamListenFailed] = P("Steam refused to open a listen socket", "Steam 拒绝开启监听端口", "Steam 拒絕開啟監聽端口"), [MpText.ErrorSteamConnectFailed] = P("Steam refused to open a connection", "Steam 拒绝建立连接", "Steam 拒絕建立連接") }; private static MpPhrase P(string en, string zhHans, string zhHant) { return new MpPhrase(en, zhHans, zhHant); } } public static class MpInfo { public const string Guid = "rokk.lbol.multiplayer.LBOLMP"; public const string Name = "LBOL MP"; public const string Version = "0.8.9"; public const int ProtocolVersion = 34; public const int MaxPlayers = 4; } [BepInPlugin("rokk.lbol.multiplayer.LBOLMP", "LBOL MP", "0.8.9")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInProcess("LBoL.exe")] public sealed class MpPlugin : BaseUnityPlugin { private static readonly Harmony HarmonyInstance = new Harmony("rokk.lbol.multiplayer.LBOLMP"); public static ConfigEntry DefaultPort; public static ConfigEntry LastJoinAddress; public static ConfigEntry PlayerName; public static ConfigEntry LobbyHotkey; public static ConfigEntry DiagnosticsHotkey; public static ConfigEntry EnemyHpScalePerExtraPlayer; public static ConfigEntry[] EnemyHpEscalationByAct; public static ConfigEntry EnableEnemyResilience; public static ConfigEntry ReviveHpFraction; public static ConfigEntry VerboseLogging; public static ConfigEntry ForceCombatEvents; public static ConfigEntry ForceDoremyEvent; private bool _runInBackgroundForced; private bool _runInBackgroundBefore; public static MpPlugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } public static bool ShowDiagnostics { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; SetupConfig(); Log.LogInfo((object)"LBOL MP v0.8.9 starting up"); L10n.Verify(); try { EntityManager.RegisterSelf(); MessageRegistry.RegisterAll(Assembly.GetExecutingAssembly()); HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)"Harmony patches applied"); } catch (Exception arg) { Log.LogError((object)$"Failed to initialise LBOL MP: {arg}"); } MpSession.EnsureHandlers(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); } private void SetupConfig() { DefaultPort = ((BaseUnityPlugin)this).Config.Bind("Network", "DefaultPort", 7777, "Port used when hosting, and the default port offered when joining."); LastJoinAddress = ((BaseUnityPlugin)this).Config.Bind("Network", "LastJoinAddress", "127.0.0.1", "Remembered address of the last session you joined."); PlayerName = ((BaseUnityPlugin)this).Config.Bind("Network", "PlayerName", "Player", "The name other players see you as."); LobbyHotkey = ((BaseUnityPlugin)this).Config.Bind("Interface", "LobbyHotkey", (KeyCode)283, "Key that toggles the multiplayer lobby overlay."); DiagnosticsHotkey = ((BaseUnityPlugin)this).Config.Bind("Interface", "DiagnosticsHotkey", (KeyCode)284, "Key that toggles the combat sync diagnostics overlay."); EnemyHpScalePerExtraPlayer = ((BaseUnityPlugin)this).Config.Bind("Balance", "EnemyHpScalePerExtraPlayer", 1f, "Extra enemy max HP per additional player, as a fraction. 1 means a 100 HP enemy has 200 HP with two players. The host's setting is used."); float[] array = new float[4] { 0f, 0.1f, 0.15f, 0.2f }; EnemyHpEscalationByAct = new ConfigEntry[4]; for (int i = 1; i <= 4; i++) { EnemyHpEscalationByAct[i - 1] = ((BaseUnityPlugin)this).Config.Bind("Balance", $"EnemyHpScalePerExtraPlayerEscalationAct{i}", array[i - 1], $"Additional enemy HP in Act {i}. This stacks one more time for each player. If the value is 0.1, the 2nd player will add +10% HP, the 3rd player will add +20% HP, for +30% total HP. Stacks additively with EnemyHpScalePerExtraPlayer."); } EnableEnemyResilience = ((BaseUnityPlugin)this).Config.Bind("Balance", "EnableEnemyResilience", true, "Give every enemy the Resilient status effect in multiplayer. For each player past the first, an enemy loses 1 more Weak, Vulnerable and Lock On at the end of its turn, and gains 1 less Firepower Down (never less than 1). Turn this off to leave debuffs exactly as strong as they are in single player. The host's setting is used."); ReviveHpFraction = ((BaseUnityPlugin)this).Config.Bind("Balance", "ReviveHpFraction", 0.2f, "How much of their max health should a defeated player be revived with. Number between 0-1. You always revive with at least 1 HP. The host's setting is used."); ForceCombatEvents = ((BaseUnityPlugin)this).Config.Bind("Debug", "ForceCombatEvents", false, "DEBUG: Force Yachie or Miyoi events at event nodes."); ForceDoremyEvent = ((BaseUnityPlugin)this).Config.Bind("Debug", "ForceDoremyEvent", false, "DEBUG: Force Doremy event at event nodes."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("Debug", "VerboseLogging", false, "Log every network message. Very noisy, but useful when a desync happens."); } private void Update() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(DiagnosticsHotkey.Value)) { ShowDiagnostics = !ShowDiagnostics; if (ShowDiagnostics) { Log.LogInfo((object)("Combat sync state: " + MpBattleSync.DescribeTurnState())); } } SteamNet.EnsureCallbacks(); MpPortraits.Warm(); KeepRunningWhileOnline(); MpNet.Pump(); MpSession.Update(); MpBattleDriver.Update(); } private void KeepRunningWhileOnline() { bool isOnline = MpNet.IsOnline; if (isOnline == _runInBackgroundForced) { return; } if (isOnline) { _runInBackgroundBefore = Application.runInBackground; Application.runInBackground = true; if (!_runInBackgroundBefore) { Log.LogInfo((object)"Keeping the game running while unfocused for the duration of the session"); } } else { Application.runInBackground = _runInBackgroundBefore; } _runInBackgroundForced = isOnline; } private void OnApplicationQuit() { MpNet.Shutdown("Application quit"); SteamNet.LeaveLobby(); } private void OnDestroy() { MpNet.Shutdown("Plugin unloaded"); SteamNet.LeaveLobby(); Harmony harmonyInstance = HarmonyInstance; if (harmonyInstance != null) { harmonyInstance.UnpatchSelf(); } } } public static class MpSafe { public static void Run(string what, Action action) { try { action(); } catch (Exception arg) { MpPlugin.Log.LogError((object)$"[{what}] caught exception: {arg}"); } } public static T Run(string what, Func action, T fallback) { try { return action(); } catch (Exception arg) { MpPlugin.Log.LogError((object)$"[{what}] caught exception: {arg}"); return fallback; } } } } namespace LBOLMP.UI { public static class AllyCardPopup { private const float HoldSeconds = 1.15f; private const float FadeSeconds = 0.35f; private const float RiseSeconds = 0.18f; private const float RisePixels = 40f; private const float Scale = 0.25f; private const float HoldAlpha = 0.85f; private static readonly Dictionary Active = new Dictionary(); private static RectTransform _layer; public static void Show(int playerId, string cardId, bool upgraded) { MpSafe.Run("AllyCardPopup", delegate { //IL_00cc: 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) Dismiss(playerId); PlayBoard panel = UiManager.GetPanel(); CardWidget val = ((panel == null) ? null : panel.CardUi?.cardPrefab); if (!((Object)(object)val == (Object)null)) { RectTransform ownLayer = GetOwnLayer(); if (!((Object)(object)ownLayer == (Object)null)) { Card val2 = Library.TryCreateCard(cardId, upgraded, (int?)null); if (val2 == null) { MpPlugin.Log.LogWarning((object)("Unknown card over the wire: " + cardId)); } else { GameMaster instance = Singleton.Instance; ((GameEntity)val2).GameRun = ((instance != null) ? instance.CurrentGameRun : null); CardWidget val3 = Object.Instantiate(val, (Transform)(object)ownLayer); ((Object)((Component)val3).gameObject).name = "MpAllyCard: " + cardId; val3.Card = val2; val3.TooltipEnabled = false; RectTransform rectTransform = val3.RectTransform; ((Transform)rectTransform).localScale = Vector3.one * 0.25f; ((Transform)rectTransform).SetAsLastSibling(); Active[playerId] = ((Component)val3).gameObject; ((MonoBehaviour)MpPlugin.Instance).StartCoroutine(Run(playerId, val3, ownLayer)); } } } }); } private static IEnumerator Run(int playerId, CardWidget widget, RectTransform parent) { CanvasGroup group = widget.CanvasGroup; float elapsed = 0f; Vector2 val2 = default(Vector2); while ((Object)(object)widget != (Object)null && elapsed < 1.68f) { elapsed += Time.unscaledDeltaTime; float num = ((elapsed < 0.18f) ? (40f * (1f - Mathf.Pow(1f - elapsed / 0.18f, 3f))) : 40f); if (MpAllyUnits.TryGetHeadScreenPoint(playerId, out var position)) { Camera val = ResolveCamera(parent); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, position, val, ref val2)) { widget.RectTransform.anchoredPosition = val2 + new Vector2(0f, num); } } if ((Object)(object)group != (Object)null) { float num2 = 1.3299999f; group.alpha = ((elapsed <= num2) ? 0.85f : (0.85f * Mathf.Clamp01(1f - (elapsed - num2) / 0.35f))); } yield return null; } Dismiss(playerId); } private static RectTransform GetOwnLayer() { //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: Expected O, but got Unknown //IL_0023: 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_0052: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_layer != (Object)null) { return _layer; } GameObject val = new GameObject("MpAllyCardLayer"); Object.DontDestroyOnLoad((Object)val); Canvas obj = val.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 500; CanvasScaler obj2 = val.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; _layer = val.GetComponent(); return _layer; } private static Camera ResolveCamera(RectTransform parent) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) Canvas componentInParent = ((Component)parent).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return null; } if ((int)componentInParent.renderMode != 0) { return componentInParent.worldCamera; } return null; } public static void Dismiss(int playerId) { if (Active.TryGetValue(playerId, out var value)) { Active.Remove(playerId); if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } } public static void DismissAll() { foreach (int item in new List(Active.Keys)) { Dismiss(item); } } } internal sealed class BalanceSettingsWindow { private const int WindowId = 6914; private const string Section = "Balance"; internal bool Visible; private Rect _window = new Rect(120f, 90f, 560f, 520f); private Vector2 _scroll; private readonly Dictionary _typing = new Dictionary(); private GUIStyle _nameStyle; private GUIStyle _helpStyle; private GUIStyle _noteStyle; internal void Draw() { //IL_0016: 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_0038: Expected O, but got Unknown //IL_0033: 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) if (Visible) { EnsureStyles(); _window = GUI.Window(6914, _window, new WindowFunction(DrawWindow), L10n.Get(MpText.SettingsWindowTitle), MpGui.Window); } } private void EnsureStyles() { //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_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_0034: Expected O, but got Unknown //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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //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_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_0083: Expected O, but got Unknown if (_nameStyle == null) { _nameStyle = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1, wordWrap = true }; _helpStyle = new GUIStyle(GUI.skin.label) { fontSize = 12, wordWrap = true }; _noteStyle = new GUIStyle(GUI.skin.label) { fontSize = 12, fontStyle = (FontStyle)2, wordWrap = true }; } } private void DrawWindow(int id) { //IL_0093: 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_01aa: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(4f); GUILayout.Label(L10n.Get(MpText.SettingsIntro), _helpStyle, Array.Empty()); GUILayout.Space(2f); GUILayout.Label(L10n.Get(MpText.SettingsHostNote), _noteStyle, Array.Empty()); if (MpSession.IsInRun) { GUILayout.Label(L10n.Get(MpText.SettingsLockedForThisRun), _noteStyle, Array.Empty()); } GUILayout.Space(6f); List list = BalanceEntries(); if (list.Count == 0) { GUILayout.Label(L10n.Get(MpText.SettingsNothingToShow), _helpStyle, Array.Empty()); } else { _scroll = GUILayout.BeginScrollView(_scroll, Array.Empty()); foreach (ConfigEntryBase item in list) { DrawEntry(item); } GUILayout.EndScrollView(); } GUILayout.Space(4f); GUILayout.Label(L10n.Get(MpText.SettingsNextRunNote), _noteStyle, Array.Empty()); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(L10n.Get(MpText.SettingsResetAll), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) })) { foreach (ConfigEntryBase item2 in list) { Reset(item2); } } GUILayout.FlexibleSpace(); if (GUILayout.Button(L10n.Get(MpText.SettingsClose), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { Visible = false; } GUILayout.EndHorizontal(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f)); } private static List BalanceEntries() { MpPlugin instance = MpPlugin.Instance; ConfigFile val = ((instance != null) ? ((BaseUnityPlugin)instance).Config : null); if (val == null) { return new List(); } return (from pair in ((IEnumerable>)val).Where((KeyValuePair pair) => pair.Key.Section == "Balance").OrderBy, string>((KeyValuePair pair) => pair.Key.Key, StringComparer.Ordinal) select pair.Value).ToList(); } private void DrawEntry(ConfigEntryBase entry) { Describe(entry, out var name, out var help); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(name, _nameStyle, Array.Empty()); GUILayout.FlexibleSpace(); DrawEditor(entry); if (GUILayout.Button(L10n.Get(MpText.SettingsDefault), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(76f) })) { Reset(entry); } GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(help)) { GUILayout.Label(help, _helpStyle, Array.Empty()); } } private static void Describe(ConfigEntryBase entry, out string name, out string help) { if ((object)entry == MpPlugin.EnemyHpScalePerExtraPlayer) { name = L10n.Get(MpText.SettingEnemyHpScaleName); help = L10n.Get(MpText.SettingEnemyHpScaleHelp); return; } if ((object)entry == MpPlugin.ReviveHpFraction) { name = L10n.Get(MpText.SettingReviveHpName); help = L10n.Get(MpText.SettingReviveHpHelp); return; } if ((object)entry == MpPlugin.EnableEnemyResilience) { name = L10n.Get(MpText.SettingResilienceName); help = L10n.Get(MpText.SettingResilienceHelp); return; } ConfigEntry[] enemyHpEscalationByAct = MpPlugin.EnemyHpEscalationByAct; int num = 0; while (enemyHpEscalationByAct != null && num < enemyHpEscalationByAct.Length) { if ((object)entry == enemyHpEscalationByAct[num]) { name = L10n.Get(MpText.SettingEscalationName, num + 1); help = L10n.Get(MpText.SettingEscalationHelp); return; } num++; } name = entry.Definition.Key; ConfigDescription description = entry.Description; help = ((description != null) ? description.Description : null); } private void DrawEditor(ConfigEntryBase entry) { if (entry.SettingType == typeof(bool)) { bool flag = (bool)entry.BoxedValue; bool flag2 = GUILayout.Toggle(flag, GUIContent.none, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); if (flag2 != flag) { entry.BoxedValue = flag2; } return; } string key = entry.Definition.Key; if (!_typing.TryGetValue(key, out var value)) { value = Serialize(entry); } string text = GUILayout.TextField(value, 12, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); if (!(text == value)) { _typing[key] = text; Commit(entry, text); } } private static void Commit(ConfigEntryBase entry, string text) { MpSafe.Run("BalanceSettings.Commit", delegate { if (entry.SettingType == typeof(float)) { if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { entry.BoxedValue = result; } } else { if (!(entry.SettingType == typeof(int))) { try { entry.SetSerializedValue(text); return; } catch (Exception) { return; } } if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { entry.BoxedValue = result2; } } }); } private void Reset(ConfigEntryBase entry) { MpSafe.Run("BalanceSettings.Reset", delegate { entry.BoxedValue = entry.DefaultValue; _typing.Remove(entry.Definition.Key); }); } private static string Serialize(ConfigEntryBase entry) { if (entry.BoxedValue is float num) { return num.ToString("0.####", CultureInfo.InvariantCulture); } return Convert.ToString(entry.BoxedValue, CultureInfo.InvariantCulture) ?? string.Empty; } } public sealed class LobbyOverlay : MonoBehaviour { private const int WindowId = 6913; private static readonly Color HostColour = new Color(1f, 0.85f, 0.4f); private static readonly Color LocalColour = new Color(0.6f, 1f, 0.7f); private static readonly Color DeadColour = new Color(0.7f, 0.35f, 0.35f); private bool _visible; private Rect _window = new Rect(40f, 40f, 460f, 420f); private string _address = "127.0.0.1"; private string _port = "7777"; private string _name = "Player"; private GUIStyle _headerStyle; private GUIStyle _rowStyle; private GUIStyle _hudStyle; private readonly BalanceSettingsWindow _balance = new BalanceSettingsWindow(); private const float HudPadding = 8f; private const float HudMargin = 8f; private void Start() { _address = MpPlugin.LastJoinAddress.Value; _port = MpPlugin.DefaultPort.Value.ToString(); _name = MpPlugin.PlayerName.Value; } private void Update() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(MpPlugin.LobbyHotkey.Value)) { _visible = !_visible; } } private void EnsureStyles() { //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_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_0034: Expected O, but got Unknown //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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //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_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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown if (_headerStyle == null) { _headerStyle = new GUIStyle(GUI.skin.label) { fontSize = 15, fontStyle = (FontStyle)1, wordWrap = true }; _rowStyle = new GUIStyle(GUI.skin.label) { fontSize = 13, wordWrap = true }; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 13, alignment = (TextAnchor)0 }; val.normal.textColor = Color.white; _hudStyle = MpGui.SingleLine(val); } } private void OnGUI() { //IL_0028: 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_0057: Expected O, but got Unknown //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) EnsureStyles(); DrawCornerHud(); DrawVoteBanner(); DrawNotice(); if (_visible) { _window = GUI.Window(6913, _window, new WindowFunction(DrawWindow), L10n.Get(MpText.LobbyWindowTitle, "0.8.9"), MpGui.Window); _balance.Draw(); } } private void DrawVoteBanner() { //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_0038: 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_005c: 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_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_00ba: 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_00c6: Unknown result type (might be due to invalid IL or missing references) if (MpSession.IsActive && MpSession.IsInRun && MapSync.VoteInProgress && !MapSync.PartyAgrees) { string text = MapSync.DescribeVoteState(); Vector2 val = MpGui.Measure(_hudStyle, text); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - val.x) * 0.5f - 14f, 64f, val.x + 28f, val.y + 12f); GUI.color = new Color(0f, 0f, 0f, 0.7f); GUI.Box(val2, GUIContent.none); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref val2)).x + 14f, ((Rect)(ref val2)).y + 6f, val.x, val.y), text, _hudStyle); } } private void DrawNotice() { //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_0024: 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_0048: 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_0091: 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_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_00d2: Unknown result type (might be due to invalid IL or missing references) string current = MpNotice.Current; if (current.Length != 0) { Vector2 val = MpGui.Measure(_hudStyle, current); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - val.x) * 0.5f - 14f, 96f, val.x + 28f, val.y + 12f); GUI.color = new Color(0f, 0f, 0f, 0.8f); GUI.Box(val2, GUIContent.none); GUI.color = new Color(1f, 0.85f, 0.4f); GUI.Label(new Rect(((Rect)(ref val2)).x + 14f, ((Rect)(ref val2)).y + 6f, val.x, val.y), current, _hudStyle); GUI.color = Color.white; } } private void DrawCornerHud() { //IL_0071: 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_0138: 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_019a: Unknown result type (might be due to invalid IL or missing references) //IL_0226: 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_023c: 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_0262: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) if (!MpSession.IsActive) { return; } List list = MpSession.Players.Where((MpPlayer p) => !p.IsLocal).ToList(); if (list.Count == 0) { return; } List list2 = new List(); list2.Add(L10n.Get(MpText.HudParty, MpSession.ConnectedCount)); List list3 = list2; List list4 = new List { Color.white }; foreach (MpPlayer item in list) { string text = string.Empty; if (MapSync.CurrentVotes.TryGetValue(item.Id, out (int, int) value)) { text = L10n.Get(MpText.HudVoteNode, value.Item1, value.Item2); } else if (MapSync.VoteInProgress) { text = L10n.Get(MpText.HudVoteStillChoosing); } list3.Add(L10n.Get(MpText.HudPlayerRow, item.Name, item.Hp, item.MaxHp, text)); list4.Add((item.State == MpPlayerState.Disconnected) ? DeadColour : Color.white); } Vector2[] array = (Vector2[])(object)new Vector2[list3.Count]; float num = 0f; float num2 = 0f; for (int num3 = 0; num3 < list3.Count; num3++) { array[num3] = MpGui.Measure(_hudStyle, list3[num3]); num = Mathf.Max(num, array[num3].x); num2 += array[num3].y; } Rect val = default(Rect); ((Rect)(ref val))..ctor(Mathf.Max(8f, (float)Screen.width - num - 16f - 8f), 8f, num + 16f, num2 + 16f); GUI.color = new Color(0f, 0f, 0f, 0.55f); GUI.Box(val, GUIContent.none); GUI.color = Color.white; float num4 = ((Rect)(ref val)).y + 8f; for (int num5 = 0; num5 < list3.Count; num5++) { Color contentColor = GUI.contentColor; GUI.contentColor = list4[num5]; GUI.Label(new Rect(((Rect)(ref val)).x + 8f, num4, num, array[num5].y), list3[num5], _hudStyle); GUI.contentColor = contentColor; num4 += array[num5].y; } } private void DrawWindow(int id) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(4f); if (!MpNet.IsOnline) { DrawOfflineControls(); } else { DrawOnlineControls(); } GUILayout.Space(8f); if (GUILayout.Button(L10n.Get(MpText.LobbyBalanceSettings), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) })) { _balance.Visible = !_balance.Visible; } if (!string.IsNullOrEmpty(MpSession.StatusLine)) { GUILayout.Space(6f); GUILayout.Label(MpSession.StatusLine, _rowStyle, Array.Empty()); } GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f)); } private void DrawOfflineControls() { GUILayout.Label(L10n.Get(MpText.LobbyNotConnected), _headerStyle, Array.Empty()); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L10n.Get(MpText.LobbyYourName), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); string text = GUILayout.TextField(_name, 24, Array.Empty()); if (text != _name) { _name = text; MpPlugin.PlayerName.Value = text; } GUILayout.EndHorizontal(); DrawSteamControls(); GUILayout.Space(8f); GUILayout.Label(L10n.Get(MpText.LobbyDirectConnection), _headerStyle, Array.Empty()); GUILayout.Space(2f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L10n.Get(MpText.LobbyPort), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); _port = GUILayout.TextField(_port, 6, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); if (GUILayout.Button(L10n.Get(MpText.LobbyHostSession), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { if (int.TryParse(_port, out var result)) { MpPlugin.DefaultPort.Value = result; MpSession.Host(result); } else { MpSession.StatusLine = L10n.Get(MpText.LobbyPortNotANumber); } } GUILayout.EndHorizontal(); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L10n.Get(MpText.LobbyHostAddress), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); _address = GUILayout.TextField(_address, 64, Array.Empty()); GUILayout.EndHorizontal(); if (GUILayout.Button(L10n.Get(MpText.LobbyJoinSession), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { if (int.TryParse(_port, out var result2)) { MpPlugin.LastJoinAddress.Value = _address; MpPlugin.DefaultPort.Value = result2; MpSession.Join(_address, result2); } else { MpSession.StatusLine = L10n.Get(MpText.LobbyPortNotANumber); } } GUILayout.Space(10f); GUILayout.Label(L10n.Get(MpText.LobbyOfflineHelp), _rowStyle, Array.Empty()); } private void DrawSteamControls() { GUILayout.Space(8f); GUILayout.Label(L10n.Get(MpText.LobbySteam), _headerStyle, Array.Empty()); GUILayout.Space(2f); if (!SteamNet.IsAvailable) { GUILayout.Label(L10n.Get(MpText.LobbySteamUnavailable), _rowStyle, Array.Empty()); return; } if (GUILayout.Button(L10n.Get(MpText.LobbyHostOverSteam), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) })) { MpSession.HostSteam(); } GUILayout.Label(L10n.Get(MpText.LobbySteamHelp), _rowStyle, Array.Empty()); } private void DrawOnlineControls() { //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_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) //IL_00cb: 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) string text = L10n.Decode(MpNet.TransportName); if (string.IsNullOrEmpty(text)) { text = L10n.Get(MpNet.IsHost ? MpText.LobbyHosting : MpText.LobbyConnected); } GUILayout.Label(text, _headerStyle, Array.Empty()); GUILayout.Space(4f); if (MpNet.IsSteamSession && !MpSession.IsInRun && SteamNet.InLobby && GUILayout.Button(L10n.Get(MpText.LobbyInviteFriends), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) })) { SteamNet.OpenInviteDialog(); } foreach (MpPlayer player in MpSession.Players) { Color contentColor = GUI.contentColor; if (player.State == MpPlayerState.Disconnected) { GUI.contentColor = DeadColour; } else if (player.IsLocal) { GUI.contentColor = LocalColour; } else if (player.IsHost) { GUI.contentColor = HostColour; } string text2 = string.Empty; if (player.IsHost) { text2 += L10n.Get(MpText.LobbyTagHost); } if (player.IsLocal) { text2 += L10n.Get(MpText.LobbyTagYou); } string text3 = (string.IsNullOrEmpty(player.CharacterId) ? L10n.Get(MpText.LobbyChoosing) : player.CharacterId); GUILayout.Label(L10n.Get(MpText.LobbyPlayerRow, player.Id, player.Name, text2, text3, DescribeState(player)), _rowStyle, Array.Empty()); GUI.contentColor = contentColor; } GUILayout.Space(8f); if (MpSession.State == MpSessionState.WaitingForPlayers) { GUILayout.Label(L10n.Get(MpText.LobbyLockedIn), _rowStyle, Array.Empty()); GUILayout.Label(MpSession.DescribeRunWait(), _rowStyle, Array.Empty()); } if (MpSession.IsInRun) { GUILayout.Label(L10n.Get(MpText.LobbySeed, MpSession.RunSeed), _rowStyle, Array.Empty()); GUILayout.Label(L10n.Get(MpText.LobbyMapVoteHint), _rowStyle, Array.Empty()); } GUILayout.Space(8f); if (GUILayout.Button(L10n.Get(MpText.LobbyLeaveSession), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { MpSession.Leave(MpText.ReasonYouLeft); } } private static string DescribeState(MpPlayer player) { return player.State switch { MpPlayerState.Lobby => L10n.Get(MpText.StateInLobby), MpPlayerState.Ready => L10n.Get(MpText.StateReady), MpPlayerState.Resuming => L10n.Get(MpText.StateResuming), MpPlayerState.InRun => L10n.Get(MpText.StateHp, player.Hp, player.MaxHp), MpPlayerState.Disconnected => L10n.Get(MpText.StateDisconnected), _ => string.Empty, }; } } public static class MapVoteMarkers { private const string MarkerName = "MpVoteMarker"; private static readonly Dictionary Markers = new Dictionary(); private static readonly List Voted = new List(); public static void Update() { MpSafe.Run("MapVoteMarkers", Refresh); } public static void Clear() { MpSafe.Run("MapVoteMarkers.Clear", delegate { foreach (GameObject value in Markers.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } Markers.Clear(); }); } private static void Refresh() { if (!MpSession.IsActive || !MpSession.IsInRun) { HideAll(); return; } MapPanel val = TryGetPanel(); if ((Object)(object)val == (Object)null || MapSync.CurrentVotes.Count == 0) { HideAll(); return; } IReadOnlyDictionary currentVotes = MapSync.CurrentVotes; Voted.Clear(); Dictionary<(int, int), List> dictionary = new Dictionary<(int, int), List>(); foreach (MpPlayer player in MpSession.Players) { if (currentVotes.TryGetValue(player.Id, out var value)) { if (!dictionary.TryGetValue(value, out var value2)) { value2 = (dictionary[value] = new List()); } value2.Add(player.Id); Voted.Add(player.Id); } } foreach (KeyValuePair<(int, int), List> item in dictionary) { MapNodeWidget val2 = WidgetAt(val, item.Key.Item1, item.Key.Item2); if (!((Object)(object)val2 == (Object)null)) { for (int i = 0; i < item.Value.Count; i++) { Place(item.Value[i], val2, i, item.Value.Count); } } } foreach (KeyValuePair marker in Markers) { if (!Voted.Contains(marker.Key) && (Object)(object)marker.Value != (Object)null) { marker.Value.SetActive(false); } } } private static void Place(int playerId, MapNodeWidget widget, int index, int count) { //IL_006e: 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_00d1: 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_00e7: Expected O, but got Unknown //IL_0126: 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_0144: 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_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_01b8: 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_01c1: 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_01c9: 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_01e0: 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_0180: 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_0276: 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_027b: 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_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_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: 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_02a5: 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_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: 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_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) GameObject val = Ensure(playerId); if ((Object)(object)val == (Object)null) { return; } Sprite val2 = MpPortraits.For(MpSession.Get(playerId)?.CharacterId); if ((Object)(object)val2 == (Object)null) { val.SetActive(false); return; } Image component = val.GetComponent(); component.sprite = MpPortraits.Frame; ((Behaviour)component).enabled = (Object)(object)component.sprite != (Object)null; RawImage component2 = ((Component)val.transform.GetChild(0)).GetComponent(); Rect textureRect = val2.textureRect; component2.texture = (Texture)(object)val2.texture; component2.uvRect = new Rect(((Rect)(ref textureRect)).x / (float)((Texture)val2.texture).width, ((Rect)(ref textureRect)).y / (float)((Texture)val2.texture).height, ((Rect)(ref textureRect)).width / (float)((Texture)val2.texture).width, ((Rect)(ref textureRect)).height / (float)((Texture)val2.texture).height); RectTransform val3 = (RectTransform)val.transform; if ((Object)(object)((Transform)val3).parent != (Object)(object)((Component)widget).transform) { ((Transform)val3).SetParent(((Component)widget).transform, false); } ((Transform)val3).SetAsLastSibling(); Transform transform = ((Component)widget).transform; RectTransform val4 = (RectTransform)(object)((transform is RectTransform) ? transform : null); Rect rect; float num; if ((Object)(object)val4 != (Object)null) { rect = val4.rect; if (((Rect)(ref rect)).height > 1f) { rect = val4.rect; num = ((Rect)(ref rect)).height; goto IL_0152; } } num = 60f; goto IL_0152; IL_0189: float num3; float num2 = num3; float num5; float num4 = Mathf.Clamp(num5 * 0.85f, 38f, 84f); Vector2 val5 = default(Vector2); ((Vector2)(ref val5))..ctor(0.5f, 0.5f); val3.pivot = val5; Vector2 anchorMin = (val3.anchorMax = val5); val3.anchorMin = anchorMin; val3.sizeDelta = new Vector2(num4, num4); ((Transform)val3).localScale = Vector3.one; val3.anchoredPosition = new Vector2(0f - (num2 * 0.5f + num4 * 0.45f), ((float)(count - 1) * 0.5f - (float)index) * (num4 * ((count > 2) ? 0.62f : 0.8f))); float num6 = num4 * MpPortraits.HeadScale(); float num7 = ((((Rect)(ref textureRect)).height > 0f) ? (((Rect)(ref textureRect)).width / ((Rect)(ref textureRect)).height) : 1f); Vector2 sizeDelta = ((num7 > 1f) ? new Vector2(num6, num6 / num7) : new Vector2(num6 * num7, num6)); RectTransform val7 = (RectTransform)((Component)component2).transform; ((Vector2)(ref val5))..ctor(0.5f, 0.5f); val7.pivot = val5; anchorMin = (val7.anchorMax = val5); val7.anchorMin = anchorMin; val7.sizeDelta = sizeDelta; val7.anchoredPosition = Vector2.zero; ((Transform)val7).localScale = Vector3.one; val.SetActive(true); return; IL_0152: num5 = num; if ((Object)(object)val4 != (Object)null) { rect = val4.rect; if (((Rect)(ref rect)).width > 1f) { rect = val4.rect; num3 = ((Rect)(ref rect)).width; goto IL_0189; } } num3 = num5; goto IL_0189; } private static GameObject Ensure(int playerId) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //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) if (Markers.TryGetValue(playerId, out var value) && (Object)(object)value != (Object)null && value.transform.childCount > 0 && (Object)(object)((Component)value.transform.GetChild(0)).GetComponent() != (Object)null) { return value; } if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } GameObject val = new GameObject(string.Format("{0}_{1}", "MpVoteMarker", playerId), new Type[2] { typeof(RectTransform), typeof(Image) }); Image component = val.GetComponent(); component.preserveAspect = true; Configure((Graphic)(object)component); GameObject val2 = new GameObject("Head", new Type[2] { typeof(RectTransform), typeof(RawImage) }); val2.transform.SetParent(val.transform, false); Configure((Graphic)(object)val2.GetComponent()); Markers[playerId] = val; return val; } private static void Configure(Graphic graphic) { graphic.raycastTarget = false; } private static void HideAll() { foreach (GameObject value in Markers.Values) { if ((Object)(object)value != (Object)null && value.activeSelf) { value.SetActive(false); } } } private static MapNodeWidget WidgetAt(MapPanel panel, int x, int y) { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentMap : null); } GameMap val = (GameMap)obj; if (val == null || x < 0 || y < 0 || x >= val.Levels || y >= val.Width) { return null; } try { return panel.GetMapNodeWidget(x, y); } catch (IndexOutOfRangeException) { return null; } } private static TPanel TryGetPanel() where TPanel : UiPanelBase { try { return UiManager.GetPanel(); } catch (InvalidOperationException) { return default(TPanel); } } } public static class MpAllyUnits { private sealed class Ally { public int PlayerId; public string CharacterId; public PlayerUnit Unit; public UnitView View; public GameObject Root; public bool Loading; public bool Shooting; public bool Hidden; public int LastHp = int.MinValue; public int LastBlock = int.MinValue; public int LastShield = int.MinValue; } [StructLayout(LayoutKind.Auto)] [CompilerGenerated] private struct d__11 : IAsyncStateMachine { public int <>1__state; public AsyncUniTaskMethodBuilder <>t__builder; public MpPlayer player; public Ally ally; public int slot; private GameDirector 5__2; private UnitView 5__3; private Awaiter <>u__1; private void MoveNext() { //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_01ca: 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_0046: Expected O, but got Unknown //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) //IL_0083: 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_0094: 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_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) //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_01a2: 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) int num = <>1__state; try { try { Awaiter val5; if (num != 0) { 5__2 = Singleton.Instance; GameObject val = new GameObject($"MpAlly_{player.Id}_{ally.CharacterId}"); val.transform.SetParent(5__2.playerRoot, false); Vector2 val2 = Offsets[Mathf.Clamp(slot, 0, Offsets.Length - 1)]; val.transform.localPosition = new Vector3(val2.x, val2.y, 0f); val.transform.localScale = Vector3.one * 0.92f; ally.Root = val; GameObject val3 = Object.Instantiate(5__2.unitPrefab, val.transform); 5__3 = val3.GetComponent(); 5__3.Unit = (Unit)(object)ally.Unit; UnitStatusHud panel = UiManager.GetPanel(); if ((Object)(object)panel != (Object)null) { 5__3.SetStatusWidget(panel.CreateStatusWidget((Unit)(object)ally.Unit), 1f); 5__3.SetInfoWidget(panel.CreateInfoWidget((Unit)(object)ally.Unit), 1f); } 5__3.IsHidden = false; UniTask val4 = 5__3.LoadUnitModelAsync(ally.Unit.ModelName, true, (float?)null); val5 = ((UniTask)(ref val4)).GetAwaiter(); if (!((Awaiter)(ref val5)).IsCompleted) { num = (<>1__state = 0); <>u__1 = val5; ((AsyncUniTaskMethodBuilder)(ref <>t__builder)).AwaitUnsafeOnCompletedd__11>(ref val5, ref this); return; } } else { val5 = <>u__1; <>u__1 = default(Awaiter); num = (<>1__state = -1); } ((Awaiter)(ref val5)).GetResult(); ((Unit)ally.Unit).SetView((IUnitView)(object)5__3); 5__3.SetPlayerHpBarLength(((Unit)ally.Unit).MaxHp); ally.View = 5__3; ally.Loading = false; ally.Hidden = (Object)(object)5__2.PlayerUnitView != (Object)null && 5__2.PlayerUnitView.IsHidden; if (ally.Hidden) { 5__3.IsHidden = true; } else { 5__3.Show(true); 5__3.SetStatusVisible(true, true); } MpPlugin.Log.LogInfo((object)("Spawned ally unit for " + player.Name + " (" + ally.CharacterId + ")")); 5__2 = null; 5__3 = null; } catch (Exception arg) { MpPlugin.Log.LogError((object)$"Failed to spawn ally unit for player {player.Id}: {arg}"); ally.Loading = false; } } catch (Exception exception) { <>1__state = -2; ((AsyncUniTaskMethodBuilder)(ref <>t__builder)).SetException(exception); return; } <>1__state = -2; ((AsyncUniTaskMethodBuilder)(ref <>t__builder)).SetResult(); } void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { ((AsyncUniTaskMethodBuilder)(ref <>t__builder)).SetStateMachine(stateMachine); } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } } private static readonly Dictionary Allies = new Dictionary(); private const float AllyVolume = 0.5f; private static readonly Vector2[] Offsets = (Vector2[])(object)new Vector2[3] { new Vector2(-1.35f, -1.45f), new Vector2(-2.6f, 0.55f), new Vector2(-3.85f, -2.6f) }; private const float ShootTimeLimit = 8f; private static GunHitArgs _allyGunHit; private static GunHitArgs _displacedGunHit; public static PlayerUnit GetUnit(int playerId) { if (!Allies.TryGetValue(playerId, out var value)) { return null; } return value.Unit; } public static bool IsMirror(Unit unit) { if (unit == null) { return false; } foreach (Ally value in Allies.Values) { if ((object)value.Unit == unit) { return true; } } return false; } public static UnitView GetView(int playerId) { if (!Allies.TryGetValue(playerId, out var value) || value.Loading) { return null; } return value.View; } public static int PlayerFor(UnitView view) { if ((Object)(object)view == (Object)null) { return -1; } foreach (Ally value in Allies.Values) { if (value.View == view) { return value.PlayerId; } } return -1; } public static UnitView GetView(Unit unit) { if (unit == null) { return null; } foreach (Ally value in Allies.Values) { if ((object)value.Unit == unit) { return value.View; } } return null; } public static void Tick() { if (!MpSession.IsActive || !MpSession.IsInRun) { if (Allies.Count > 0) { DespawnAll(); } return; } GameDirector instance = Singleton.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.PlayerUnitView == (Object)null || (Object)(object)instance.playerRoot == (Object)null) { if (Allies.Count > 0) { DespawnAll(); } return; } foreach (int item in Allies.Keys.ToList()) { MpPlayer mpPlayer = MpSession.Get(item); if (mpPlayer == null || mpPlayer.State == MpPlayerState.Disconnected) { Despawn(item); } } int num = 0; foreach (MpPlayer connectedPlayer in MpSession.ConnectedPlayers) { if (!connectedPlayer.IsLocal) { if (!Allies.ContainsKey(connectedPlayer.Id) && !string.IsNullOrEmpty(connectedPlayer.CharacterId)) { Spawn(connectedPlayer, num); } num++; } } } private static void Spawn(MpPlayer player, int slot) { //IL_012f: Unknown result type (might be due to invalid IL or missing references) PlayerUnit val = MpSafe.Run("MpAllyUnits.Create", () => Library.TryCreatePlayerUnit(player.CharacterId), null); if (val == null) { MpPlugin.Log.LogWarning((object)("Unknown character id over the wire: " + player.CharacterId)); Allies[player.Id] = new Ally { PlayerId = player.Id, CharacterId = player.CharacterId }; return; } int num = ((player.MaxHp > 0) ? player.MaxHp : ((Unit)val).MaxHp); int num2 = ((player.Hp > 0) ? Mathf.Min(player.Hp, num) : num); ((Unit)val).SetMaxHp(num2, num); Ally ally = new Ally { PlayerId = player.Id, CharacterId = player.CharacterId, Unit = val, Loading = true, LastHp = num2 }; Allies[player.Id] = ally; UniTaskExtensions.Forget(LoadAllyAsync(ally, player, slot)); } [AsyncStateMachine(typeof(d__11))] private static UniTask LoadAllyAsync(Ally ally, MpPlayer player, int slot) { //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_0041: Unknown result type (might be due to invalid IL or missing references) d__11 d__ = default(d__11); d__.<>t__builder = AsyncUniTaskMethodBuilder.Create(); d__.ally = ally; d__.player = player; d__.slot = slot; d__.<>1__state = -1; ((AsyncUniTaskMethodBuilder)(ref d__.<>t__builder)).Start<d__11>(ref d__); return ((AsyncUniTaskMethodBuilder)(ref d__.<>t__builder)).Task; } private static void Despawn(int playerId) { if (!Allies.TryGetValue(playerId, out var ally)) { return; } MpSafe.Run("MpAllyUnits.Despawn", delegate { if ((Object)(object)ally.View != (Object)null) { Object.Destroy((Object)(object)((Component)ally.View).gameObject); } if ((Object)(object)ally.Root != (Object)null) { Object.Destroy((Object)(object)ally.Root); } }); Allies.Remove(playerId); } public static void DespawnAll() { foreach (int item in Allies.Keys.ToList()) { Despawn(item); } } public static void TickViews() { if (Allies.Count == 0) { return; } foreach (Ally value in Allies.Values) { UnitView view = value.View; if (!((Object)(object)view == (Object)null) && !value.Loading) { MpSafe.Run("MpAllyUnits.TickView", delegate { view.Tick(); }); } } } public static void SyncVitals(MpBattleSeat seat) { if (!Allies.TryGetValue(seat.PlayerId, out var ally) || ally.Unit == null) { return; } MpSafe.Run("MpAllyUnits.SyncVitals", delegate { //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0236: 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) PlayerUnit unit = ally.Unit; UnitView view = ally.View; if (seat.MaxHp > 0 && ((Unit)unit).MaxHp != seat.MaxHp) { ((Unit)unit).SetMaxHp(Mathf.Clamp(seat.Hp, 0, seat.MaxHp), seat.MaxHp); if (view != null) { view.SetPlayerHpBarLength(seat.MaxHp); } if (view != null) { view.OnMaxHpChanged(); } } int num = ((seat.MaxHp > 0) ? Mathf.Clamp(seat.Hp, 0, seat.MaxHp) : seat.Hp); int num2 = ((ally.LastHp != int.MinValue) ? (num - ally.LastHp) : 0); int num3 = Mathf.Max(0, seat.Block); int num4 = Mathf.Max(0, seat.Shield); bool num5 = num3 != ally.LastBlock || num4 != ally.LastShield; ((Unit)unit).Block = num3; ((Unit)unit).Shield = num4; ((Unit)unit).Hp = num; if (num2 < 0 && (Object)(object)view != (Object)null) { view.OnDamageReceived(DamageInfo.HpLose((float)(-num2), true)); } else if (num2 > 0 && (Object)(object)view != (Object)null) { view.OnHealingReceived(num2); } if (num5 && (Object)(object)view != (Object)null) { view.UpdateShieldColliders(); UnitStatusWidget statusWidget = view._statusWidget; if (statusWidget != null) { statusWidget.OnBlockShieldChanged(); } if (ally.LastShield != int.MinValue) { bool flag = false; if (num4 > ally.LastShield) { view.CreateLocalShieldEffect("GainShield", true); flag = true; } if (num3 > ally.LastBlock) { view.CreateLocalShieldEffect("GainBlock", false); flag = true; } if (flag) { AudioManager.PlaySfx("ShieldCast", 0.5f); } } } ally.LastHp = num; ally.LastBlock = num3; ally.LastShield = num4; SyncStatusEffects(ally, seat); if (num <= 0 && (int)((Unit)unit).Status == 0) { ((Unit)unit).Status = (UnitStatus)2; if (view != null) { view.DeathAnimation(); } } else if (num > 0 && (int)((Unit)unit).Status != 0) { Revive(ally); } }); } public static void Revive(int playerId) { if (Allies.TryGetValue(playerId, out var ally)) { MpSafe.Run("MpAllyUnits.Revive", delegate { Revive(ally); }); } } private static void Revive(Ally ally) { if (ally.Unit != null) { ((Unit)ally.Unit).Status = (UnitStatus)0; } UnitView view = ally.View; if (!((Object)(object)view == (Object)null)) { view._invincible = false; view.SpineIdle(false); } } private static void SyncStatusEffects(Ally ally, MpBattleSeat seat) { PlayerUnit unit = ally.Unit; Dictionary dictionary = new Dictionary(); foreach (string statusEffect in seat.StatusEffects) { string[] array = statusEffect.Split(':'); if (array.Length >= 3) { int.TryParse(array[1], out var result); int.TryParse(array[2], out var result2); dictionary[array[0]] = (result, result2, (array.Length > 3) ? array[3] : string.Empty); } } foreach (StatusEffect item in ((Unit)unit).StatusEffects.ToList()) { if (!dictionary.ContainsKey(((GameEntity)item).Id)) { ((Unit)unit)._statusEffects.Remove(item); item.Owner = null; UnitView view = ally.View; if (view != null) { view.OnRemoveStatusEffect(item); } } } foreach (KeyValuePair entry in dictionary) { StatusEffect val = ((IEnumerable)((Unit)unit).StatusEffects).FirstOrDefault((Func)((StatusEffect s) => ((GameEntity)s).Id == entry.Key)); if (val == null) { StatusEffect val2 = Library.TryCreateStatusEffect(entry.Key); if (val2 != null) { if (val2.HasLevel && entry.Value.Item1 >= 0) { val2.Level = entry.Value.Item1; } if (val2.HasDuration && entry.Value.Item2 >= 0) { val2.Duration = entry.Value.Item2; } if (!string.IsNullOrEmpty(entry.Value.Item3)) { val2.SourceCard = Library.TryCreateCard(entry.Value.Item3, false, (int?)null); } val2.Owner = (Unit)(object)unit; ((Unit)unit)._statusEffects.Add(val2); UnitView view2 = ally.View; if (view2 != null) { view2.OnAddStatusEffect(val2, (StatusEffectAddResult)0); } } } else { if (val.HasLevel && entry.Value.Item1 >= 0 && val.Level != entry.Value.Item1) { val.Level = entry.Value.Item1; } if (val.HasDuration && entry.Value.Item2 >= 0 && val.Duration != entry.Value.Item2) { val.Duration = entry.Value.Item2; } } } } public static void PlayDebut() { foreach (Ally value in Allies.Values) { UnitView view = value.View; if (!((Object)(object)view == (Object)null) && !value.Loading && !value.Hidden) { MpSafe.Run("MpAllyUnits.PlayDebut", delegate { view.DebutAnimation(); }); } } } public static void SetHidden(bool hidden, bool withStatus) { foreach (Ally value in Allies.Values) { value.Hidden = hidden; UnitView view = value.View; if ((Object)(object)view == (Object)null) { continue; } MpSafe.Run("MpAllyUnits.SetHidden", delegate { if (hidden) { view.IsHidden = true; } else { view.Show(withStatus); } }); } } public static void PlayAnimation(int playerId, string animationName) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(animationName) && Allies.TryGetValue(playerId, out var ally) && !((Object)(object)ally.View == (Object)null) && !ally.Loading && !ally.Shooting && (int)ally.View._status == 0) { MpSafe.Run("MpAllyUnits.PlayAnimation", delegate { ally.View.PlayAnimation(animationName); }); } } public static void PlayHit(int playerId, DamageInfo info) { //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) if (Allies.TryGetValue(playerId, out var ally) && !((Object)(object)ally.View == (Object)null) && !ally.Loading) { MpSafe.Run("MpAllyUnits.PlayHit", delegate { //IL_0013: Unknown result type (might be due to invalid IL or missing references) UnitView view = ally.View; view.UpdateShieldColliders(); view.ComingDamage = info; view.Hit(false, 1f, false); }); } } public static void AimAt(int playerId, int targetEnemyIndex) { if (Allies.TryGetValue(playerId, out var ally) && !((Object)(object)ally.View == (Object)null)) { MpSafe.Run("MpAllyUnits.AimAt", delegate { AimInternal(ally, targetEnemyIndex); }); } } private static UnitView AimInternal(Ally ally, int targetEnemyIndex) { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val == null || (Object)(object)ally.View == (Object)null) { return null; } EnemyUnit val2 = ((IEnumerable)val.EnemyGroup).FirstOrDefault((Func)((EnemyUnit e) => e.Index == targetEnemyIndex && ((Unit)e).IsAlive)) ?? val.FirstAliveEnemy; UnitView val3 = ((val2 != null) ? GameDirector.GetEnemy(val2) : null); if ((Object)(object)val3 == (Object)null) { return null; } ally.View.Target = val3; ally.View.Targets = new List { val3 }; return val3; } public static void PlayShoot(int playerId, string gunName, int targetEnemyIndex) { if (string.IsNullOrEmpty(gunName) || gunName == "Instant" || gunName == "Empty" || !Allies.TryGetValue(playerId, out var ally) || (Object)(object)ally.View == (Object)null || ally.Shooting) { return; } MpSafe.Run("MpAllyUnits.PlayShoot", delegate { UnitView val = AimInternal(ally, targetEnemyIndex); if (!((Object)(object)val == (Object)null)) { StageGunHit(val, gunName); ally.Shooting = true; ((MonoBehaviour)MpPlugin.Instance).StartCoroutine(ShootRoutine(ally, gunName)); } }); } private static IEnumerator ShootRoutine(Ally ally, string gunName) { if (MpSafe.Run("MpAllyUnits.StartShoot", delegate { ((MonoBehaviour)MpPlugin.Instance).StartCoroutine(ally.View.Shoot(gunName, (GunType)0)); return true; }, fallback: false)) { float waited = 0f; while (waited < 8f && (Object)(object)ally.View != (Object)null && (int)ally.View._status != 0) { waited += Time.unscaledDeltaTime; yield return null; } } MpSafe.Run("MpAllyUnits.EndShoot", delegate { ForceIdle(ally); }); ally.Shooting = false; } private static void ForceIdle(Ally ally) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) UnitView view = ally.View; if (!((Object)(object)view == (Object)null) && (int)view._status != 0) { MpPlugin.Log.LogWarning((object)("Ally shot never finished; forcing " + ally.CharacterId + " back to idle")); view._shootCounting = false; view.ShowEndActs(); } } private static void StageGunHit(UnitView targetView, string gunName) { //IL_0018: 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_0032: Expected O, but got Unknown _displacedGunHit = GameDirector._gunHitArgs; _allyGunHit = new GunHitArgs(false, (IList>)new List<(UnitView, DamageInfo)> { (targetView, DamageInfo.Attack(0f, false)) }, gunName); GameDirector._gunHitArgs = _allyGunHit; } internal static bool TryHandleAllyGunHit() { GunHitArgs gunHitArgs = GameDirector._gunHitArgs; if (gunHitArgs == null) { return true; } if (gunHitArgs != _allyGunHit) { return false; } GameDirector._gunHitArgs = _displacedGunHit; _allyGunHit = null; _displacedGunHit = null; foreach (var pair in gunHitArgs.Pairs) { if ((Object)(object)pair.Item1 != (Object)null) { pair.Item1.HitEnd(); } } return true; } public static bool TryGetHeadScreenPoint(int playerId, out Vector2 position) { //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_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_0080: 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_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) position = default(Vector2); if (!Allies.TryGetValue(playerId, out var value) || (Object)(object)value.View == (Object)null) { return false; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return false; } Transform val = (((Object)(object)value.View.ChatPoint != (Object)null) ? value.View.ChatPoint : ((Component)value.View).transform); Vector3 val2 = main.WorldToScreenPoint(val.position); if (val2.z < 0f) { return false; } position = new Vector2(val2.x, val2.y); return true; } } public static class MpEmotes { private readonly struct Emote { internal readonly MpText Line; internal readonly string Animation; internal readonly bool ClockTick; internal Emote(MpText line, string animation, bool clockTick) { Line = line; Animation = animation; ClockTick = clockTick; } } private static readonly Emote[] Emotes = new Emote[3] { new Emote(MpText.EmoteNice, null, clockTick: false), new Emote(MpText.EmoteNegative, null, clockTick: false), new Emote(MpText.EmoteHurryUp, "skill", clockTick: true) }; private static readonly KeyCode[] Keys; private static readonly KeyCode[] NumpadKeys; private const float BubbleSeconds = 2.5f; private const float Cooldown = 1.2f; private const float TickVolume = 0.5f; private static float _nextEmote; private static string _clockTick; public static bool Available { get { if (!MpSession.IsActive || !MpBattleSync.InBattle) { return false; } if (MpDownedPlayers.OutOfFight || MpBattleSync.AtEndOfBattleGate) { return true; } if (MpBattleSync.LocalTurnComplete) { return !MpBattleSync.AllSeatsCompleted(MpBattleSync.CurrentRound); } return false; } } private static string ClockTick { get { if (_clockTick != null) { return _clockTick; } string text = MpSafe.Run("MpEmotes.ClockTick", delegate { StatusEffectConfig obj = StatusEffectConfig.FromId("TimeAuraSe"); return (obj == null) ? null : obj.SFX; }, null); _clockTick = ((string.IsNullOrEmpty(text) || text == "Default") ? "Buff" : text); MpPlugin.Log.LogInfo((object)("Hurry-up emote will play the Time Pulse cue '" + _clockTick + "'")); return _clockTick; } } public static void Update() { if (!Available || Time.unscaledTime < _nextEmote) { return; } int i; for (i = 0; i < Emotes.Length; i++) { if (Input.GetKeyDown(Keys[i]) || Input.GetKeyDown(NumpadKeys[i])) { MpSafe.Run("MpEmotes.Send", delegate { Send(i); }); break; } } } private static void Send(int index) { _nextEmote = Time.unscaledTime + 1.2f; Emote emote = Emotes[index]; Speak(GameDirector.Player, emote); MpNet.Send(new RemoteEmoteMessage { Emote = index }); if (emote.Animation != null) { UnitView player = GameDirector.Player; if ((Object)(object)player != (Object)null) { player.PlayAnimation(emote.Animation); } } } public static void Play(int playerId, int index) { if (index >= 0 && index < Emotes.Length) { MpSafe.Run("MpEmotes.Play", delegate { Speak(MpAllyUnits.GetView(playerId), Emotes[index]); }); } } private static void Speak(UnitView view, Emote emote) { if (!((Object)(object)view == (Object)null)) { view.Chat(L10n.Get(emote.Line), 2.5f, (CloudType)2, 0f); if (emote.ClockTick) { AudioManager.PlaySfx(ClockTick, 0.5f); } } } static MpEmotes() { KeyCode[] array = new KeyCode[3]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); Keys = (KeyCode[])(object)array; KeyCode[] array2 = new KeyCode[3]; RuntimeHelpers.InitializeArray(array2, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); NumpadKeys = (KeyCode[])(object)array2; } } internal static class MpGui { private static readonly Color PanelFill = new Color(0.09f, 0.1f, 0.12f, 1f); private static readonly Color PanelEdge = new Color(0.42f, 0.44f, 0.5f, 1f); private static GUIStyle _windowStyle; private static Texture2D _windowTexture; internal static GUIStyle Window { get { //IL_002e: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0042: 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_0051: Expected O, but got Unknown //IL_0056: Expected O, but got Unknown //IL_0097: 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) if (_windowStyle != null && (Object)(object)_windowTexture != (Object)null) { return _windowStyle; } _windowTexture = PanelTexture(); _windowStyle = new GUIStyle(GUI.skin.window) { border = new RectOffset(1, 1, 1, 1), overflow = new RectOffset(0, 0, 0, 0) }; _windowStyle.normal.background = _windowTexture; _windowStyle.onNormal.background = _windowTexture; _windowStyle.normal.textColor = new Color(0.88f, 0.89f, 0.92f); _windowStyle.onNormal.textColor = Color.white; return _windowStyle; } } internal static GUIStyle SingleLine(GUIStyle style) { style.wordWrap = false; return style; } internal static Vector2 Measure(GUIStyle style, string text) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //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_0027: 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) Vector2 val = style.CalcSize(new GUIContent(text ?? string.Empty)); return new Vector2(Mathf.Ceil(val.x) + 2f, Mathf.Ceil(val.y)); } private static Texture2D PanelTexture() { //IL_0004: 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_0011: 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_0020: Expected O, but got Unknown //IL_0054: 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) Texture2D val = new Texture2D(8, 8, (TextureFormat)4, false) { hideFlags = (HideFlags)61, filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1 }; Color[] array = (Color[])(object)new Color[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { bool flag = j == 0 || i == 0 || j == 7 || i == 7; array[i * 8 + j] = (flag ? PanelEdge : PanelFill); } } val.SetPixels(array); val.Apply(); return val; } } public static class MpHandView { private sealed class Slot { public HandCard Hand; public string Key; public string Signature; } private sealed class CancelHandler : IInputActionHandler { public void OnCancel() { MpSafe.Run("MpHandView.Cancel", MpHandInspect.End); } public void OnRightClickCancel() { } public void OnToggleDrawZone() { MpInspectedPiles.ShowDraw(); } public void OnToggleDiscardZone() { MpInspectedPiles.ShowDiscard(); } public void OnToggleExileZone() { MpInspectedPiles.ShowExile(); } public void OnToggleBaseDeck() { MpInspectedPiles.ShowDeck(); } } private static readonly List Slots = new List(); private static HandCard _hovered; private static bool _active; private static int _shownRevision = -1; private static ManaGroup _shownMana; private static bool _manaSwapped; private static readonly CancelHandler Cancel = new CancelHandler(); private static bool _handlerPushed; public static bool Active => _active; public static void Tick() { MpSafe.Run("MpHandView", Refresh); } private static void Refresh() { PlayBoard val = TryGetPanel(); CardUi val2 = (((Object)(object)val != (Object)null) ? val.CardUi : null); HandlePointer(val2); if (!MpHandInspect.IsInspecting || !((Object)(object)val2 != (Object)null) || !MpBattleSync.InBattle) { if (_active) { Leave(val2); } return; } if (!_active) { Enter(); } HideOwnHand(val2); ShowCounts(val2); ShowMana(); if (_shownRevision != MpHandInspect.Revision) { _shownRevision = MpHandInspect.Revision; Sync(val2); } Layout(val2); } private static void Enter() { _active = true; _shownRevision = -1; _hovered = null; UiManager.PushActionHandler((IInputActionHandler)(object)Cancel); _handlerPushed = true; } private static void Leave(CardUi cardUi) { _active = false; _shownRevision = -1; Clear(); RestoreMana(); ShowOwnHand(cardUi); RestoreCounts(cardUi); if (_handlerPushed) { _handlerPushed = false; MpSafe.Run("MpHandView.PopHandler", RemoveHandler); } } private static void RemoveHandler() { Stack stack = UiManager.Instance?._actionHandlerStack; if (stack == null || stack.Count == 0) { return; } List list = new List(); bool flag = false; while (stack.Count > 0) { IInputActionHandler val = stack.Pop(); if ((object)val == Cancel) { flag = true; break; } list.Add(val); } for (int num = list.Count - 1; num >= 0; num--) { stack.Push(list[num]); } if (!flag) { MpPlugin.Log.LogWarning((object)"The hand view's input handler was gone before it closed"); } } private static void Clear() { foreach (Slot slot in Slots) { Discard(slot); } Slots.Clear(); _hovered = null; } private static void Sync(CardUi cardUi) { List list = new List(Slots); List list2 = new List(); foreach (Card item in MpHandInspect.Hand) { string key = Key(item); int num = list.FindIndex((Slot slot2) => (Object)(object)slot2.Hand != (Object)null && slot2.Key == key); Slot slot; if (num >= 0) { slot = list[num]; list.RemoveAt(num); string text = Signature(item); if (slot.Signature != text) { slot.Signature = text; slot.Hand.CardWidget.Card = item; slot.Hand.RefreshStatus(); } } else { slot = Create(cardUi, item); if (slot == null) { continue; } } list2.Add(slot); } foreach (Slot item2 in list) { Discard(item2); } Slots.Clear(); Slots.AddRange(list2); ReOrder(cardUi); } private static void ReOrder(CardUi cardUi) { RectTransform cache = cardUi.cardHandReorderCache; RectTransform parent = cardUi.cardHandParent; if ((Object)(object)cache == (Object)null || (Object)(object)parent == (Object)null) { return; } foreach (Slot slot in Slots) { if (!((Object)(object)slot.Hand == (Object)null)) { HandCard hand = slot.Hand; MpSafe.Run("MpHandView.ReOrder", delegate { hand.MoveToParentWhenReordering((Transform)(object)cache); hand.MoveToParentWhenReordering((Transform)(object)parent); }); } } } private static string Key(Card card) { if (card != null) { return ((GameEntity)card).Id + (card.IsUpgraded ? "+" : string.Empty); } return string.Empty; } private static string Signature(Card card) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected I8, but got Unknown if (card == null) { return string.Empty; } return Key(card) + "|" + ((object)card.Cost/*cast due to .constrained prefix*/).ToString() + "|" + ((object)card.BaseCost/*cast due to .constrained prefix*/).ToString() + "|" + ((object)card.AuraCost/*cast due to .constrained prefix*/).ToString() + "|" + (ulong)(long)card.Keywords + "|" + card.Loyalty + "|" + card.UpgradeCounter.GetValueOrDefault() + "|" + card.Summoned; } private static Slot Create(CardUi cardUi, Card card) { //IL_00a4: 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_00b9: 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_00f3: 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_0129: Unknown result type (might be due to invalid IL or missing references) CardWidget cardPrefab = cardUi.cardPrefab; HandCard handCardPrefab = cardUi.handCardPrefab; RectTransform cardHandParent = cardUi.cardHandParent; if ((Object)(object)cardPrefab == (Object)null || (Object)(object)handCardPrefab == (Object)null || (Object)(object)cardHandParent == (Object)null) { return null; } CardWidget val = Object.Instantiate(cardPrefab, (Transform)(object)cardHandParent); ((Object)((Component)val).gameObject).name = "MpInspected: " + ((GameEntity)card).Id; val.Card = card; val.ShowManaHand = true; HandCard val2 = Object.Instantiate(handCardPrefab, (Transform)(object)cardHandParent); ((Object)((Component)val2).gameObject).name = "MpInspectedHand: " + ((GameEntity)card).Id; val2.CardWidget = val; Transform transform = ((Component)val).transform; transform.SetParent(val2.cardRoot); transform.localPosition = Vector3.zero; transform.localScale = Vector3.one; transform.localRotation = Quaternion.identity; val2.NormalParent = (Transform)(object)cardHandParent; val2.HoveredParent = (Transform)(object)cardUi.cardHoveredParent; val2.ActiveHandParent = (Transform)(object)cardUi.cardHoveredParent; val2.SpecialReactingPosition = Vector3.zero; val2.SpecialReactingRotation = Quaternion.identity; val2.ShowShortcut = false; ((Component)val2).transform.localPosition = ((Transform)cardUi.cardDrawPoint).localPosition; ((Component)val2).transform.localScale = ((Transform)cardUi.cardDrawPoint).localScale; return new Slot { Hand = val2, Key = Key(card), Signature = Signature(card) }; } private static void Discard(Slot slot) { if ((Object)(object)slot?.Hand == (Object)null) { return; } if (slot.Hand == _hovered) { _hovered = null; } MpSafe.Run("MpHandView.Discard", delegate { CardWidget cardWidget = slot.Hand.CardWidget; if (cardWidget != null) { cardWidget.HideTooltip(); } }); Object.Destroy((Object)(object)((Component)slot.Hand).gameObject); } private static void Layout(CardUi cardUi) { //IL_003e: 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_005b: 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_007f: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < Slots.Count; i++) { HandCard hand = Slots[i].Hand; if (!((Object)(object)hand == (Object)null)) { Place(cardUi, i, Slots.Count, out var position, out var angle); hand.HandIndex = i; hand.NormalPosition = position; hand.NormalRotation = Quaternion.Euler(0f, 0f, angle); hand.HoveredPosition = new Vector3(position.x, (float)cardUi.hoveredY + cardUi.handOffset.y); hand.HoveredRotation = Quaternion.identity; } } } private static void Place(CardUi cardUi, int i, int count, out Vector3 position, out float angle) { //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_0083: 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_0093: 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) Rect rect = cardUi._rectTransform.rect; float num = ((Rect)(ref rect)).width / cardUi.curvatureRatio; float num2 = (float)count / 2f - 0.5f; float num3 = cardUi.deltaX; if (count >= 12) { num3 *= 0.84f; } else if (count == 11) { num3 *= 0.91f; } float num4 = ((float)i - num2) * num3; float num5 = (float.IsInfinity(num) ? 0f : (Mathf.Sqrt(Mathf.Max(0f, num * num - num4 * num4)) - num)); position = Vector2.op_Implicit(new Vector2(num4, num5) + cardUi.handOffset); angle = (0f - cardUi.deltaRotate) * ((float)i - num2); } private static void HideOwnHand(CardUi cardUi) { foreach (HandCard handWidget in cardUi._handWidgets) { if ((Object)(object)handWidget != (Object)null && ((Component)handWidget).gameObject.activeSelf) { ((Component)handWidget).gameObject.SetActive(false); } } } private static void ShowOwnHand(CardUi cardUi) { if ((Object)(object)cardUi == (Object)null) { return; } foreach (HandCard handWidget in cardUi._handWidgets) { if ((Object)(object)handWidget != (Object)null && !((Component)handWidget).gameObject.activeSelf) { ((Component)handWidget).gameObject.SetActive(true); } } } private static void ShowCounts(CardUi cardUi) { cardUi.DrawCount = MpHandInspect.Draw.Count; cardUi.DiscardCount = MpHandInspect.Discard.Count; cardUi.ExileCount = MpHandInspect.Exile.Count; } private static void RestoreCounts(CardUi cardUi) { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (!((Object)(object)cardUi == (Object)null) && val != null) { cardUi.DrawCount = val.DrawZone.Count; cardUi.DiscardCount = val.DiscardZone.Count; cardUi.ExileCount = val.ExileZone.Count; } } private static void ShowMana() { //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_0031: 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_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_001d: 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) BattleManaPanel val = TryGetPanel(); if (!((Object)(object)val == (Object)null)) { ManaGroup mana = MpHandInspect.Mana; if (!_manaSwapped || !(mana == _shownMana)) { _manaSwapped = true; _shownMana = mana; val.ResetAllManas(mana, ManaGroup.Empty, false); } } } private static void RestoreMana() { //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) if (_manaSwapped) { _manaSwapped = false; BattleManaPanel val = TryGetPanel(); GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val2 = (BattleController)obj; if ((Object)(object)val != (Object)null && val2 != null) { val.ResetAllManas(val2.BattleMana, ManaGroup.Empty, false); } } } private static void HandlePointer(CardUi cardUi) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Invalid comparison between Unknown and I4 if (_active) { Hover(cardUi); } if (!Input.GetMouseButtonDown(1) || IsVisible() || IsVisible()) { return; } if (_active) { CardWidget widget = (((Object)(object)_hovered != (Object)null) ? _hovered.CardWidget : null); if ((Object)(object)widget != (Object)null && widget.Card != null) { MpSafe.Run("MpHandView.Detail", delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown ((UiPanel)(object)UiManager.GetPanel()).Show(new CardDetailPayload(widget.RectTransform, widget.Card, false)); }); } else { MpHandInspect.End(); } } else { if ((Object)(object)cardUi == (Object)null || !MpBattleSync.InBattle || UiManager.IsBlockingInput) { return; } PlayBoard val = TryGetPanel(); if (!((Object)(object)val == (Object)null) && (int)val._status == 1) { int hoveredPlayer = MpHoveredUnit.HoveredPlayer; if (hoveredPlayer != -1) { MpHandInspect.Begin(hoveredPlayer); } } } } private static void Hover(CardUi cardUi) { //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_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_00ad: 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_00b9: 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_00c3: 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_00d6: 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_00e4: 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_00eb: 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_0100: 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) HandCard val = null; RectTransform cardHandParent = cardUi.cardHandParent; Vector2 val2 = default(Vector2); if ((Object)(object)cardHandParent != (Object)null && Slots.Count > 0 && RectTransformUtility.ScreenPointToLocalPointInRectangle(cardHandParent, Vector2.op_Implicit(Input.mousePosition), ResolveCamera(cardHandParent), ref val2)) { for (int num = Slots.Count - 1; num >= 0; num--) { HandCard hand = Slots[num].Hand; if (!((Object)(object)hand == (Object)null) && !((Object)(object)hand.CardWidget == (Object)null)) { Place(cardUi, num, Slots.Count, out var position, out var angle); Vector3 val3 = Quaternion.Euler(0f, 0f, 0f - angle) * Vector2.op_Implicit(val2 - Vector2.op_Implicit(position)); Rect rect = hand.CardWidget.RectTransform.rect; Vector2 val4 = ((Rect)(ref rect)).size * 0.35f; if (Mathf.Abs(val3.x) <= val4.x && Mathf.Abs(val3.y) <= val4.y) { val = hand; break; } } } } if (val == _hovered) { return; } if ((Object)(object)_hovered != (Object)null) { HandCard leaving = _hovered; MpSafe.Run("MpHandView.EndHover", delegate { leaving.EndHover(); }); } _hovered = val; if ((Object)(object)_hovered != (Object)null) { HandCard entering = _hovered; MpSafe.Run("MpHandView.StartHover", delegate { entering.StartHover(); }); } } private static Camera ResolveCamera(RectTransform rect) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) Canvas val = (((Object)(object)rect != (Object)null) ? ((Component)rect).GetComponentInParent() : null); if ((Object)(object)val == (Object)null) { return null; } if ((int)val.renderMode != 0) { return val.worldCamera; } return null; } private static bool IsVisible() where TPanel : UiPanelBase { TPanel val = TryGetPanel(); if ((Object)(object)val != (Object)null) { return ((UiBase)(object)val).IsVisible; } return false; } private static TPanel TryGetPanel() where TPanel : UiPanelBase { try { return UiManager.GetPanel(); } catch (InvalidOperationException) { return default(TPanel); } catch (NullReferenceException) { return default(TPanel); } } } public static class MpInspectedPiles { internal static string ShowingFor { get; private set; } = string.Empty; public static void ShowDraw() { Show(MpHandInspect.Draw, LocalizationExtensions.Localize("Game.DrawZoneOutOfOrder", true), (ShowCardZone)1, MpHandInspect.HideDrawOrder); } public static void ShowDiscard() { Show(MpHandInspect.Discard, LocalizationExtensions.Localize("Game.DiscardZone", true), (ShowCardZone)2, hideActualOrder: false); } public static void ShowExile() { Show(MpHandInspect.Exile, LocalizationExtensions.Localize("Game.ExileZone", true), (ShowCardZone)3, hideActualOrder: false); } public static void ShowDeck() { Show(MpHandInspect.Deck, LocalizationExtensions.Localize("Game.Deck", true), (ShowCardZone)4, hideActualOrder: false); } private static void Show(IReadOnlyList cards, string zoneName, ShowCardZone zone, bool hideActualOrder) { //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) MpSafe.Run("MpInspectedPiles", delegate { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) ShowingFor = MpSession.Get(MpHandInspect.Target)?.CharacterId ?? string.Empty; try { ShowCardsPanel panel = UiManager.GetPanel(); ShowCardsPayload val = new ShowCardsPayload(); val.Name = L10n.Get(MpText.InspectZoneTitle, MpHandInspect.TargetName, zoneName); val.Description = LocalizationExtensions.Localize("Cards.Show", true); val.Cards = new List(cards); val.InteractionType = (InteractionType)0; val.CardZone = zone; val.HideActualOrder = hideActualOrder; ((UiPanel)(object)panel).Show(val); } finally { ShowingFor = string.Empty; } }); } } internal static class MpNotice { private const float Seconds = 7f; private static string _text = string.Empty; private static float _until; internal static string Current { get { if (!(Time.unscaledTime < _until)) { return string.Empty; } return _text; } } internal static void Show(string text) { _text = text ?? string.Empty; _until = Time.unscaledTime + 7f; } internal static void Clear() { _text = string.Empty; _until = 0f; } } public static class MpPortraits { private const string FallbackKey = "null"; private const float DefaultHeadScale = 0.78f; private static readonly Dictionary Heads = new Dictionary(); private static readonly Dictionary Avatars = new Dictionary(); private static Sprite _frame; private static float _headScale; private static bool _warmed; private const float MinRingAspect = 0.85f; private const float MaxRingAspect = 1.18f; private const string ExpectedRingName = "空"; public static Sprite Frame => _frame; public static void Warm() { if (_warmed) { return; } MpSafe.Run("MpPortraits.Warm", delegate { //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_0124: 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) AssociationList val = Panel()?.portraitList; if (val != null) { foreach (KeyValuePair item in val) { if (!string.IsNullOrEmpty(item.Key) && (Object)(object)item.Value != (Object)null) { Heads[item.Key] = item.Value; } } if ((Object)(object)_frame == (Object)null) { _frame = Square(DiscoverRing()) ?? Square(Template()?.normalSprite); } if (Heads.Count > 0) { _warmed = true; ManualLogSource log = MpPlugin.Log; string[] obj = new string[5] { $"Cached {Heads.Count} collection portraits before the main menu UI is unloaded; ", "ring=", null, null, null }; object obj2; if (!((Object)(object)_frame == (Object)null)) { string name = ((Object)_frame).name; Rect rect = _frame.rect; object arg = ((Rect)(ref rect)).width; rect = _frame.rect; obj2 = $"'{name}' {arg:0}x{((Rect)(ref rect)).height:0}"; } else { obj2 = ""; } obj[2] = (string)obj2; obj[3] = ", "; obj[4] = $"headScale={HeadScale():0.00}"; log.LogInfo((object)string.Concat(obj)); } } }); } public static Sprite For(string characterId) { return Head(characterId) ?? ProfileHead(characterId) ?? Avatar(characterId); } public static float HeadScale() { if (!((Object)(object)_frame == (Object)null)) { return 0.78f; } return 1f; } private static Sprite DiscoverRing() { //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_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) Image val = Panel()?.profileHead; Transform val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).transform.parent); if ((Object)(object)val2 == (Object)null) { return null; } Sprite val3 = null; float num = 0f; Image[] componentsInChildren = ((Component)val2).GetComponentsInChildren(true); foreach (Image val4 in componentsInChildren) { if ((Object)(object)val4 == (Object)(object)val || (Object)(object)val4.sprite == (Object)null) { continue; } Rect rect = val4.sprite.rect; if (((Rect)(ref rect)).width < 1f || ((Rect)(ref rect)).height < 1f) { continue; } float num2 = ((Rect)(ref rect)).width / ((Rect)(ref rect)).height; if (!(num2 < 0.85f) && !(num2 > 1.18f)) { Rect rect2 = ((Graphic)val4).rectTransform.rect; float width = ((Rect)(ref rect2)).width; if (width > num) { val3 = val4.sprite; num = width; } } } if ((Object)(object)val3 != (Object)null && ((Object)val3).name != "空") { MpPlugin.Log.LogWarning((object)("Portrait ring is '" + ((Object)val3).name + "', not the expected '空' — the main menu's profile art may have changed; check how the markers look")); } return val3; } public static void Draw(Rect area, string characterId) { //IL_0070: 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) Sprite sprite = For(characterId); Sprite frame = Frame; if ((Object)(object)frame != (Object)null) { DrawSprite(area, frame); float num = HeadScale(); float num2 = Mathf.Min(((Rect)(ref area)).width, ((Rect)(ref area)).height) * num; ((Rect)(ref area))..ctor(((Rect)(ref area)).x + (((Rect)(ref area)).width - num2) * 0.5f, ((Rect)(ref area)).y + (((Rect)(ref area)).height - num2) * 0.5f, num2, num2); } DrawSprite(area, sprite); } public static void DrawSprite(Rect area, Sprite sprite) { //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_00f2: 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) if ((Object)(object)sprite == (Object)null || (Object)(object)sprite.texture == (Object)null) { return; } Texture2D texture = sprite.texture; Rect textureRect = sprite.textureRect; if (!(((Rect)(ref textureRect)).width <= 0f) && !(((Rect)(ref textureRect)).height <= 0f)) { Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref textureRect)).x / (float)((Texture)texture).width, ((Rect)(ref textureRect)).y / (float)((Texture)texture).height, ((Rect)(ref textureRect)).width / (float)((Texture)texture).width, ((Rect)(ref textureRect)).height / (float)((Texture)texture).height); float num = ((Rect)(ref textureRect)).width / ((Rect)(ref textureRect)).height; float num2 = ((Rect)(ref area)).width; float num3 = ((Rect)(ref area)).height; if (num2 / num3 > num) { num2 = num3 * num; } else { num3 = num2 / num; } GUI.DrawTextureWithTexCoords(new Rect(((Rect)(ref area)).x + (((Rect)(ref area)).width - num2) * 0.5f, ((Rect)(ref area)).y + (((Rect)(ref area)).height - num3) * 0.5f, num2, num3), (Texture)(object)texture, val); } } private static Sprite Head(string characterId) { if (string.IsNullOrEmpty(characterId)) { return null; } if (Heads.TryGetValue(characterId, out var value) && (Object)(object)value != (Object)null) { return value; } AssociationList val = Panel()?.portraitList; Sprite val2 = default(Sprite); if (val == null || !val.TryGetValue(characterId, ref val2) || (Object)(object)val2 == (Object)null) { return null; } Heads[characterId] = val2; return val2; } private static Sprite ProfileHead(string characterId) { ProfilePanel val = Panel(); if ((Object)(object)val == (Object)null) { return null; } try { return val.GetHeadSprite(string.IsNullOrEmpty(characterId) ? "null" : characterId); } catch (Exception ex) { MpPlugin.Log.LogWarning((object)("Could not get a profile head for '" + characterId + "': " + ex.Message)); return null; } } private static Sprite Avatar(string characterId) { if (string.IsNullOrEmpty(characterId)) { return null; } if (Avatars.TryGetValue(characterId, out var value)) { return value; } Sprite val = null; try { val = ResourcesHelper.LoadCharacterAvatarSprite(characterId); } catch (Exception ex) { MpPlugin.Log.LogWarning((object)("Could not load an avatar for " + characterId + ": " + ex.Message)); } Avatars[characterId] = val; return val; } private static Sprite Square(Sprite sprite) { //IL_000a: 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_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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //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_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_0087: 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) if (!((Object)(object)sprite == (Object)null)) { Rect rect = sprite.rect; if (!(((Rect)(ref rect)).width < 1f)) { rect = sprite.rect; if (!(((Rect)(ref rect)).height < 1f)) { rect = sprite.rect; float width = ((Rect)(ref rect)).width; rect = sprite.rect; float num = width / ((Rect)(ref rect)).height; if (num < 0.85f || num > 1.18f) { ManualLogSource log = MpPlugin.Log; string name = ((Object)sprite).name; rect = sprite.rect; object arg = ((Rect)(ref rect)).width; rect = sprite.rect; log.LogInfo((object)$"Ignoring '{name}' as a portrait ring: {arg:0}x{((Rect)(ref rect)).height:0} is not square"); return null; } return sprite; } } } return null; } private static CharacterToggleWidget Template() { Toggle val = Panel()?.characterToggleTemplate; if (!((Object)(object)val == (Object)null)) { return ((Component)val).GetComponent(); } return null; } private static TPanel Panel() where TPanel : UiPanelBase { try { return UiManager.GetPanel(); } catch (InvalidOperationException) { return default(TPanel); } } } public sealed class RemotePlayerBoard : MonoBehaviour { private const float PanelWidth = 190f; private const float PanelHeight = 62f; private const float TextLeftInset = 50f; private static RemotePlayerBoard _instance; private GUIStyle _nameStyle; private GUIStyle _smallStyle; private Texture2D _white; private void Awake() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) _instance = this; _white = new Texture2D(1, 1); _white.SetPixel(0, 0, Color.white); _white.Apply(); } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } private void EnsureStyles() { //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_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_003d: Expected O, but got Unknown //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_005a: 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_007e: Expected O, but got Unknown if (_nameStyle == null) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; val.normal.textColor = Color.white; _nameStyle = MpGui.SingleLine(val); GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 12 }; val2.normal.textColor = new Color(0.85f, 0.85f, 0.85f); _smallStyle = MpGui.SingleLine(val2); } } private void OnGUI() { if (MpBattleSync.InBattle && MpSession.IsActive) { EnsureStyles(); DrawSeatPanels(); DrawInspectBanner(); DrawDownedBanner(); DrawWaitingBanner(); DrawDiagnostics(); } } private void DrawInspectBanner() { //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_0033: 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_0057: 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_0086: 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_00c5: 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_00d1: 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) if (MpHandView.Active) { string text = L10n.Get(MpText.InspectBanner, MpHandInspect.TargetName); Vector2 val = MpGui.Measure(_nameStyle, text); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - val.x) * 0.5f - 16f, 24f, val.x + 32f, val.y + 14f); GUI.color = new Color(0f, 0f, 0f, 0.75f); GUI.DrawTexture(val2, (Texture)(object)_white); GUI.color = new Color(1f, 0.85f, 0.4f); GUI.Label(new Rect(((Rect)(ref val2)).x + 16f, ((Rect)(ref val2)).y + 7f, val.x, val.y), text, _nameStyle); GUI.color = Color.white; } } private void DrawDiagnostics() { //IL_01b5: 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_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) if (!MpPlugin.ShowDiagnostics) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; List list = new List { "waitingForInput : " + (((val != null) ? val.IsWaitingPlayerInput.ToString() : null) ?? "n/a"), $"localComplete : {MpBattleSync.LocalTurnComplete}", $"allComplete : {MpBattleSync.AllSeatsCompleted(MpBattleSync.CurrentRound)}", "round : " + (((val != null) ? val.RoundCounter.ToString() : null) ?? "n/a") }; foreach (MpBattleSeat allSeat in MpBattleSync.AllSeats) { list.Add($"#{allSeat.PlayerId} {allSeat.Name,-10} completed={allSeat.CompletedRound} " + $"alive={allSeat.Alive} done={allSeat.Finished} down={allSeat.Down} " + $"watching={allSeat.Spectating}"); } float num = 8f + (float)list.Count * 16f; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(24f, (float)Screen.height - num - 24f, 380f, num); GUI.color = new Color(0f, 0f, 0f, 0.75f); GUI.DrawTexture(val2, (Texture)(object)_white); GUI.color = Color.white; for (int i = 0; i < list.Count; i++) { GUI.Label(new Rect(((Rect)(ref val2)).x + 8f, ((Rect)(ref val2)).y + 4f + (float)i * 16f, ((Rect)(ref val2)).width - 16f, 16f), list[i], _smallStyle); } } private void DrawDownedBanner() { //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_0030: 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_005b: 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_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_00ba: 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_00c6: Unknown result type (might be due to invalid IL or missing references) if (MpDownedPlayers.OutOfFight) { string text = L10n.Get(MpDownedPlayers.LocalDown ? MpText.BoardDefeated : MpText.BoardSittingOut); Vector2 val = MpGui.Measure(_nameStyle, text); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - val.x) * 0.5f - 16f, (float)Screen.height * 0.7f, val.x + 32f, val.y + 14f); GUI.color = new Color(0.25f, 0f, 0.04f, 0.82f); GUI.DrawTexture(val2, (Texture)(object)_white); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref val2)).x + 16f, ((Rect)(ref val2)).y + 7f, val.x, val.y), text, _nameStyle); } } private void DrawWaitingBanner() { //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_0040: 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_006b: 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) //IL_00a6: 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) string text = MpSafe.Run("WaitingBanner", WaitingText, null); if (text != null) { Vector2 val = MpGui.Measure(_nameStyle, text); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - val.x) * 0.5f - 16f, (float)Screen.height * 0.78f, val.x + 32f, val.y + 14f); GUI.color = new Color(0f, 0f, 0f, 0.7f); GUI.DrawTexture(val2, (Texture)(object)_white); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref val2)).x + 16f, ((Rect)(ref val2)).y + 7f, val.x, val.y), text, _nameStyle); } } private static string WaitingText() { List list = MpBattleSync.SilentSeats.ToList(); if (list.Count > 0) { return L10n.Get(MpText.BoardLostContact, string.Join(", ", list)); } if (MpBattleSync.AtEndOfBattleGate) { List list2 = MpBattleSync.SeatsStillFighting.ToList(); if (list2.Count == 0) { return null; } if (list2.Count != 1) { return L10n.Get(MpText.BoardWaitingForMany, string.Join(", ", list2)); } return L10n.Get(MpText.BoardWaitingForOneToFinish, list2[0]); } if (MpDownedPlayers.OutOfFight) { return null; } if (!MpBattleSync.LocalTurnComplete || MpBattleSync.AllSeatsCompleted(MpBattleSync.CurrentRound)) { return null; } List list3 = MpBattleSync.SeatsStillPlaying.ToList(); if (list3.Count == 0) { return null; } if (list3.Count != 1) { return L10n.Get(MpText.BoardWaitingForMany, string.Join(", ", list3)); } return L10n.Get(MpText.BoardWaitingForOne, list3[0]); } private void DrawSeatPanels() { //IL_003c: 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) if (!MpPlugin.ShowDiagnostics) { return; } List list = MpBattleSync.RemoteSeats.ToList(); float num = 190f; foreach (MpBattleSeat item in list) { num = Mathf.Max(num, 50f + MpGui.Measure(_nameStyle, item.Name).x + 8f); } Rect rect = default(Rect); for (int i = 0; i < list.Count; i++) { ((Rect)(ref rect))..ctor(24f, 200f + (float)i * 70f, num, 62f); DrawSeatPanel(rect, list[i]); } } private void DrawSeatPanel(Rect rect, MpBattleSeat seat) { //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_002a: 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_009e: 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_00f3: 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_013b: 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_0189: 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_02ca: Unknown result type (might be due to invalid IL or missing references) GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.DrawTexture(rect, (Texture)(object)_white); GUI.color = Color.white; MpPortraits.Draw(new Rect(((Rect)(ref rect)).x + 4f, ((Rect)(ref rect)).y + 4f, 40f, 40f), seat.CharacterId); float num = ((Rect)(ref rect)).x + 50f; float num2 = ((Rect)(ref rect)).width - 50f - 4f; GUI.Label(new Rect(num, ((Rect)(ref rect)).y + 2f, num2, 18f), seat.Name, _nameStyle); Rect val = default(Rect); ((Rect)(ref val))..ctor(num, ((Rect)(ref rect)).y + 22f, num2 - 4f, 10f); GUI.color = new Color(0.15f, 0.15f, 0.15f, 0.9f); GUI.DrawTexture(val, (Texture)(object)_white); float num3 = ((seat.MaxHp > 0) ? Mathf.Clamp01((float)seat.Hp / (float)seat.MaxHp) : 0f); GUI.color = (seat.Alive ? new Color(0.75f, 0.25f, 0.3f) : new Color(0.35f, 0.35f, 0.35f)); GUI.DrawTexture(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y, ((Rect)(ref val)).width * num3, ((Rect)(ref val)).height), (Texture)(object)_white); GUI.color = Color.white; string text = $"{seat.Hp}/{seat.MaxHp}"; if (seat.Block > 0) { text = text + " " + L10n.Get(MpText.BoardBlock, seat.Block); } if (seat.Shield > 0) { text = text + " " + L10n.Get(MpText.BoardShield, seat.Shield); } GUI.Label(new Rect(num, ((Rect)(ref rect)).y + 34f, num2, 16f), text, _smallStyle); string text2 = (seat.Spectating ? L10n.Get(MpText.ActivitySpectating) : (seat.Down ? L10n.Get(MpText.ActivityDownSpectating) : (seat.Finished ? L10n.Get(MpText.ActivityDone) : ((!seat.Alive) ? L10n.Get(MpText.ActivityDown) : (seat.HasCompleted(MpBattleSync.CurrentRound) ? L10n.Get(MpText.ActivityTurnOver) : L10n.Get(MpText.ActivityHand, seat.HandCount)))))); GUI.Label(new Rect(num, ((Rect)(ref rect)).y + 46f, num2, 16f), text2, _smallStyle); } } } namespace LBOLMP.Session { public static class MapSync { private static readonly Dictionary Votes = new Dictionary(); private static readonly HashSet ReleasedBarriers = new HashSet(); private static readonly Dictionary> BarrierArrivals = new Dictionary>(); private static (int X, int Y)? _committed; private static int _decision; private static (int X, int Y)? _localVote; private static float _nextResend; private const float ResendInterval = 2f; public static IReadOnlyDictionary CurrentVotes => Votes; public static bool VoteInProgress => Votes.Count > 0; public static bool PartyAgrees { get { (int, int) node; return TryGetUnanimousNode(out node); } } public static string PendingAdventureType { get; private set; } = string.Empty; public static event Action BarrierReleased; public static void RegisterHandlers() { MpNet.On(OnVote); MpNet.On(OnCommit); MpNet.On(OnBossChosen); MpNet.On(OnBarrierArrive); MpNet.On(OnBarrierRelease); } public static void Reset() { Votes.Clear(); BarrierArrivals.Clear(); ReleasedBarriers.Clear(); _committed = null; _localVote = null; _decision = 0; MapVotingPatch.Reset(); SetBossSyncPatch.Reset(); SelectStationSyncPatch.Reset(); BossMapIconPatch.Reset(); MapVoteMarkers.Clear(); } public static void Update() { MapVotingPatch.Update(); BossMapIconPatch.Update(); MapVoteMarkers.Update(); ResendLocalVote(); } private static void ResendLocalVote() { if (_localVote.HasValue && !_committed.HasValue && MpSession.IsInRun && !PartyAgrees && !(Time.unscaledTime < _nextResend)) { _nextResend = Time.unscaledTime + 2f; SendVote(_localVote.Value.X, _localVote.Value.Y); } } public static void OnPlayerLeft(int playerId) { Votes.Remove(playerId); foreach (HashSet value in BarrierArrivals.Values) { value.Remove(playerId); } if (MpNet.IsHost) { TryCommit(); foreach (string item in BarrierArrivals.Keys.ToList()) { TryRelease(item); } } if (VoteInProgress) { MpSession.StatusLine = DescribeVoteState(); } } public static void CastVote(int x, int y) { _localVote = (x, y); _nextResend = Time.unscaledTime + 2f; SendVote(x, y); } private static void SendVote(int x, int y) { (int, int) tuple = CurrentNode(); MpNet.Send(new MapVoteMessage { StageIndex = CurrentStageIndex(), X = x, Y = y, FromX = tuple.Item1, FromY = tuple.Item2, Decision = _decision }); } private static (int X, int Y) CurrentNode() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; if (currentGameRun == null) { obj = null; } else { GameMap currentMap = currentGameRun.CurrentMap; obj = ((currentMap != null) ? currentMap.VisitingNode : null); } } MapNode val = (MapNode)obj; if (val != null) { return (X: val.X, Y: val.Y); } return (X: -1, Y: -1); } private static void OnVote(MapVoteMessage message) { if (message.Decision != _decision || message.StageIndex != CurrentStageIndex()) { return; } (int, int) tuple = CurrentNode(); if (message.FromX == tuple.Item1 && message.FromY == tuple.Item2) { Votes[message.SenderId] = (message.X, message.Y); MpSession.StatusLine = DescribeVoteState(); if (MpNet.IsHost) { TryCommit(); } } } private static void TryCommit() { if (!_committed.HasValue && TryGetUnanimousNode(out (int, int) node)) { MapCommitMessage obj = new MapCommitMessage { StageIndex = CurrentStageIndex() }; (obj.X, obj.Y) = node; DecideStationContents(obj); MpNet.Send(obj); } } private static bool TryGetUnanimousNode(out (int X, int Y) node) { node = default((int, int)); bool flag = false; foreach (MpPlayer respondingPlayer in MpSession.RespondingPlayers) { if (!Votes.TryGetValue(respondingPlayer.Id, out (int, int) value)) { return false; } if (!flag) { node = value; flag = true; continue; } (int, int) tuple = value; (int, int) tuple2 = node; if (tuple.Item1 == tuple2.Item1 && tuple.Item2 == tuple2.Item2) { continue; } return false; } return flag; } public static string DescribeVoteState() { List list = (from p in MpSession.RespondingPlayers where !Votes.ContainsKey(p.Id) select p.Name).ToList(); if (list.Count > 0) { if (list.Count != 1) { return L10n.Get(MpText.MapWaitingForMany, string.Join(", ", list)); } return L10n.Get(MpText.MapWaitingForOne, list[0]); } if (TryGetUnanimousNode(out (int, int) _)) { return L10n.Get(MpText.MapMovingToNode); } IEnumerable values = from p in MpSession.RespondingPlayers where Votes.ContainsKey(p.Id) select L10n.Get(MpText.MapPick, p.Name, Votes[p.Id].X, Votes[p.Id].Y); return L10n.Get(MpText.MapPartySplit, string.Join(", ", values)); } private static void DecideStationContents(MapCommitMessage commit) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); Stage val2 = ((val != null) ? val.CurrentStage : null); if (val2 == null) { return; } MapNode val3; try { val3 = val.CurrentMap.Nodes[commit.X, commit.Y]; } catch (IndexOutOfRangeException) { return; } if ((int)val3.StationType == 6) { Type type = AdventureSyncPatch.RollLocally(val2); if (type != null) { commit.AdventureType = type.Name; } } } private static void EndDecision() { _decision++; Votes.Clear(); _localVote = null; } private static void OnCommit(MapCommitMessage message) { EndDecision(); _committed = (message.X, message.Y); PendingAdventureType = message.AdventureType; MpSession.StatusLine = L10n.Get(MpText.MapHeadingTo, message.X, message.Y); MapVotingPatch.EnterCommittedNode(message.X, message.Y); } private static void OnBossChosen(BossChosenMessage message) { if (!MpNet.IsHost) { SetBossSyncPatch.ApplyHostChoice(message.StageIndex, message.BossId); } } public static void ClearVotes() { Votes.Clear(); _localVote = null; } public static void ClearCommit() { _committed = null; _localVote = null; Votes.Clear(); } private static int CurrentStageIndex() { GameMaster instance = Singleton.Instance; GameRunController obj = ((instance != null) ? instance.CurrentGameRun : null); int? obj2; if (obj == null) { obj2 = null; } else { Stage currentStage = obj.CurrentStage; obj2 = ((currentStage != null) ? new int?(currentStage.Index) : ((int?)null)); } return obj2 ?? (-1); } public static void Arrive(string barrierId) { MpNet.Send(new BarrierArriveMessage { BarrierId = barrierId }); } public static bool IsReleased(string barrierId) { return ReleasedBarriers.Contains(barrierId); } private static void OnBarrierArrive(BarrierArriveMessage message) { if (!BarrierArrivals.TryGetValue(message.BarrierId, out var value)) { value = new HashSet(); BarrierArrivals[message.BarrierId] = value; } value.Add(message.SenderId); if (MpNet.IsHost) { TryRelease(message.BarrierId); } } private static void TryRelease(string barrierId) { if (ReleasedBarriers.Contains(barrierId) || !BarrierArrivals.TryGetValue(barrierId, out var value)) { return; } foreach (MpPlayer connectedPlayer in MpSession.ConnectedPlayers) { if (!value.Contains(connectedPlayer.Id)) { return; } } MpNet.Send(new BarrierReleaseMessage { BarrierId = barrierId }); } private static void OnBarrierRelease(BarrierReleaseMessage message) { ReleasedBarriers.Add(message.BarrierId); BarrierArrivals.Remove(message.BarrierId); MapSync.BarrierReleased?.Invoke(message.BarrierId); } public static void Forget(string barrierId) { ReleasedBarriers.Remove(barrierId); BarrierArrivals.Remove(barrierId); } } internal static class MpBorderSensor { private static bool _owed; private static bool _granting; public static void RegisterHandlers() { MpNet.On(OnRemote); } public static void Reset() { _owed = false; _granting = false; } public static void Announce() { if (!_granting && MpSession.IsActive) { MpPlugin.Log.LogInfo((object)"Border Sensor obtained; taking the rest of the party to Act 4 too"); MpNet.Send(new BorderSensorMessage()); } } private static void OnRemote(BorderSensorMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { _owed = true; Tick(); } } public static void Tick() { if (!_owed) { return; } GameMaster instance = Singleton.Instance; GameRunController gameRun = ((instance != null) ? instance.CurrentGameRun : null); GameRunController obj = gameRun; if (((obj != null) ? obj.Player : null) == null) { return; } _owed = false; MpSafe.Run("MpBorderSensor.Grant", delegate { if (!gameRun.Player.HasExhibit()) { JingjieGanzhiyi val = Library.CreateExhibit(); _granting = true; try { gameRun.GainExhibitInstantly((Exhibit)(object)val, false, (VisualSourceData)null); } finally { _granting = false; } SystemBoard panel = UiManager.GetPanel(); if (panel != null) { panel.OnExhibitAdded((Exhibit)(object)val, 0f); } MpPlugin.Log.LogInfo((object)"A partner's Border Sensor carries this run into Act 4 as well"); } }); } } [Flags] internal enum MpCardFields : ushort { None = 0, Upgraded = 1, Keywords = 2, BaseCost = 4, TurnCostDelta = 8, AuraCost = 0x10, FreeCost = 0x20, Summoned = 0x40, Loyalty = 0x80, UpgradeCounter = 0x100 } public sealed class MpCardState { public string Id = string.Empty; public int Zone; internal MpCardFields Fields; public ulong Keywords; public ManaGroup BaseCost; public ManaGroup TurnCostDelta; public ManaGroup AuraCost; public int Loyalty; public int UpgradeCounter; } public static class MpCardMirror { private static readonly Dictionary References = new Dictionary(); private static readonly HashSet Unknown = new HashSet(); public static List Capture(IEnumerable cards) { List list = new List(); if (cards == null) { return list; } foreach (Card card in cards) { if (card != null && !string.IsNullOrEmpty(((GameEntity)card).Id)) { list.Add(Capture(card)); } } return list; } private static MpCardState Capture(Card card) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected I8, but got Unknown //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_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_0095: 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_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_00b8: 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_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_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_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Invalid comparison between Unknown and I4 MpCardState mpCardState = new MpCardState { Id = ((GameEntity)card).Id, Zone = (int)card.Zone }; MpCardFields mpCardFields = MpCardFields.None; if (card.IsUpgraded) { mpCardFields |= MpCardFields.Upgraded; } if (card.UpgradeCounter.GetValueOrDefault() > 0) { mpCardFields |= MpCardFields.UpgradeCounter; mpCardState.UpgradeCounter = card.UpgradeCounter.Value; } Card val = Reference(((GameEntity)card).Id, card.IsUpgraded); if (val == null || card.Keywords != val.Keywords) { mpCardFields |= MpCardFields.Keywords; mpCardState.Keywords = (ulong)(long)card.Keywords; } if (!card.IsXCost) { if (card.BaseCost != card.ConfigCost) { mpCardFields |= MpCardFields.BaseCost; mpCardState.BaseCost = card.BaseCost; } if (card.TurnCostDelta != ManaGroup.Empty) { mpCardFields |= MpCardFields.TurnCostDelta; mpCardState.TurnCostDelta = card.TurnCostDelta; } } if (card.AuraCost != ManaGroup.Empty) { mpCardFields |= MpCardFields.AuraCost; mpCardState.AuraCost = card.AuraCost; } if (card.FreeCost) { mpCardFields |= MpCardFields.FreeCost; } if (card.Summoned) { mpCardFields |= MpCardFields.Summoned; } if ((int)card.CardType == 5 && (val == null || card.Loyalty != val.Loyalty)) { mpCardFields |= MpCardFields.Loyalty; mpCardState.Loyalty = card.Loyalty; } mpCardState.Fields = mpCardFields; return mpCardState; } private static Card Reference(string id, bool upgraded) { string key = (upgraded ? (id + "+") : id); if (References.TryGetValue(key, out var value)) { return value; } Card val = MpSafe.Run("MpCardMirror.Reference", () => Library.TryCreateCard(id, upgraded, (int?)null), null); References[key] = val; return val; } public static List Rebuild(IReadOnlyList states) { List list = new List(); if (states == null) { return list; } GameMaster instance = Singleton.Instance; GameRunController gameRun = ((instance != null) ? instance.CurrentGameRun : null); GameRunController obj = gameRun; BattleController battle = ((obj != null) ? obj.Battle : null); foreach (MpCardState state in states) { Card val = MpSafe.Run("MpCardMirror.Rebuild", () => Rebuild(state, gameRun, battle), null); if (val != null) { list.Add(val); } } return list; } private static Card Rebuild(MpCardState state, GameRunController gameRun, BattleController battle) { //IL_00ee: 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_00d7: Unknown result type (might be due to invalid IL or missing references) bool flag = (state.Fields & MpCardFields.Upgraded) != 0; int? num = (((state.Fields & MpCardFields.UpgradeCounter) != MpCardFields.None) ? new int?(state.UpgradeCounter) : ((int?)null)); Card val = Library.TryCreateCard(state.Id, flag, num); if (val == null) { if (Unknown.Add(state.Id)) { MpPlugin.Log.LogWarning((object)("A player is holding '" + state.Id + "', which this install does not have; it will be missing from their hand here")); } return null; } if (battle != null) { val.SetBattle(battle); } else if (gameRun != null) { ((GameEntity)val).GameRun = gameRun; } val.Zone = (CardZone)state.Zone; if ((state.Fields & MpCardFields.Keywords) != MpCardFields.None) { val.Keywords = (Keyword)state.Keywords; } if (!val.IsXCost) { if ((state.Fields & MpCardFields.BaseCost) != MpCardFields.None) { val.BaseCost = state.BaseCost; } if ((state.Fields & MpCardFields.TurnCostDelta) != MpCardFields.None) { val.TurnCostDelta = state.TurnCostDelta; } } if ((state.Fields & MpCardFields.AuraCost) != MpCardFields.None) { val.AuraCost = state.AuraCost; } val.FreeCost = (state.Fields & MpCardFields.FreeCost) != 0; val.Summoned = (state.Fields & MpCardFields.Summoned) != 0; if ((state.Fields & MpCardFields.Loyalty) != MpCardFields.None) { val.Loyalty = state.Loyalty; } return val; } public static void Write(NetWriter w, IReadOnlyList states) { //IL_00d3: 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_0104: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Dictionary dictionary = new Dictionary(); for (int i = 0; i < states.Count; i++) { string text = states[i].Id ?? string.Empty; if (!dictionary.ContainsKey(text)) { dictionary[text] = list.Count; list.Add(text); } } w.StringList(list); w.Int(states.Count); for (int j = 0; j < states.Count; j++) { MpCardState mpCardState = states[j]; w.UShort((ushort)dictionary[mpCardState.Id ?? string.Empty]); w.Byte((byte)mpCardState.Zone); w.UShort((ushort)mpCardState.Fields); if ((mpCardState.Fields & MpCardFields.Keywords) != MpCardFields.None) { w.ULong(mpCardState.Keywords); } if ((mpCardState.Fields & MpCardFields.BaseCost) != MpCardFields.None) { WriteMana(w, mpCardState.BaseCost); } if ((mpCardState.Fields & MpCardFields.TurnCostDelta) != MpCardFields.None) { WriteMana(w, mpCardState.TurnCostDelta); } if ((mpCardState.Fields & MpCardFields.AuraCost) != MpCardFields.None) { WriteMana(w, mpCardState.AuraCost); } if ((mpCardState.Fields & MpCardFields.Loyalty) != MpCardFields.None) { w.Short((short)mpCardState.Loyalty); } if ((mpCardState.Fields & MpCardFields.UpgradeCounter) != MpCardFields.None) { w.Short((short)mpCardState.UpgradeCounter); } } } public static List Read(NetReader r) { //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_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) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) string[] array = r.StringArray(); int num = r.Int(); List list = new List(num); for (int i = 0; i < num; i++) { int num2 = r.UShort(); MpCardState mpCardState = new MpCardState { Id = ((num2 >= 0 && num2 < array.Length) ? array[num2] : string.Empty), Zone = r.Byte(), Fields = (MpCardFields)r.UShort() }; if ((mpCardState.Fields & MpCardFields.Keywords) != MpCardFields.None) { mpCardState.Keywords = r.ULong(); } if ((mpCardState.Fields & MpCardFields.BaseCost) != MpCardFields.None) { mpCardState.BaseCost = ReadMana(r); } if ((mpCardState.Fields & MpCardFields.TurnCostDelta) != MpCardFields.None) { mpCardState.TurnCostDelta = ReadMana(r); } if ((mpCardState.Fields & MpCardFields.AuraCost) != MpCardFields.None) { mpCardState.AuraCost = ReadMana(r); } if ((mpCardState.Fields & MpCardFields.Loyalty) != MpCardFields.None) { mpCardState.Loyalty = r.Short(); } if ((mpCardState.Fields & MpCardFields.UpgradeCounter) != MpCardFields.None) { mpCardState.UpgradeCounter = r.Short(); } list.Add(mpCardState); } return list; } public static void WriteMana(NetWriter w, ManaGroup mana) { w.Short((short)((ManaGroup)(ref mana)).Any); w.Short((short)((ManaGroup)(ref mana)).White); w.Short((short)((ManaGroup)(ref mana)).Blue); w.Short((short)((ManaGroup)(ref mana)).Black); w.Short((short)((ManaGroup)(ref mana)).Red); w.Short((short)((ManaGroup)(ref mana)).Green); w.Short((short)((ManaGroup)(ref mana)).Colorless); w.Short((short)((ManaGroup)(ref mana)).Philosophy); w.Short((short)((ManaGroup)(ref mana)).Hybrid); w.Short((short)((ManaGroup)(ref mana)).HybridColor); } public static ManaGroup ReadMana(NetReader r) { //IL_0002: 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) ManaGroup result = default(ManaGroup); ((ManaGroup)(ref result)).Any = r.Short(); ((ManaGroup)(ref result)).White = r.Short(); ((ManaGroup)(ref result)).Blue = r.Short(); ((ManaGroup)(ref result)).Black = r.Short(); ((ManaGroup)(ref result)).Red = r.Short(); ((ManaGroup)(ref result)).Green = r.Short(); ((ManaGroup)(ref result)).Colorless = r.Short(); ((ManaGroup)(ref result)).Philosophy = r.Short(); ((ManaGroup)(ref result)).Hybrid = r.Short(); ((ManaGroup)(ref result)).HybridColor = r.Short(); return result; } } public static class MpHandInspect { private sealed class Mirror { public readonly List Hand = new List(); public readonly List Draw = new List(); public readonly List Discard = new List(); public readonly List Exile = new List(); public readonly List Deck = new List(); public ManaGroup Mana; public bool HideDrawOrder; public int Revision; } private const float PublishInterval = 0.2f; private static readonly Dictionary Mirrors = new Dictionary(); private static readonly HashSet Watchers = new HashSet(); private static byte[] _lastCards; private static byte[] _lastDeck; private static float _nextPublish; private static readonly List Empty = new List(); public static int Target { get; private set; } = -1; public static bool IsInspecting => Target != -1; public static string TargetName => MpSession.Get(Target)?.Name ?? string.Empty; public static int Revision => Find(Target)?.Revision ?? 0; public static IReadOnlyList Hand => Find(Target)?.Hand ?? Empty; public static IReadOnlyList Draw => Find(Target)?.Draw ?? Empty; public static IReadOnlyList Discard => Find(Target)?.Discard ?? Empty; public static IReadOnlyList Exile => Find(Target)?.Exile ?? Empty; public static IReadOnlyList Deck => Find(Target)?.Deck ?? Empty; public static ManaGroup Mana => Find(Target)?.Mana ?? ManaGroup.Empty; public static bool HideDrawOrder => Find(Target)?.HideDrawOrder ?? true; private static bool Present(int playerId) { MpPlayer mpPlayer = MpSession.Get(playerId); if (mpPlayer != null) { return mpPlayer.State != MpPlayerState.Disconnected; } return false; } private static Mirror Find(int playerId) { if (!Mirrors.TryGetValue(playerId, out var value)) { return null; } return value; } public static void RegisterHandlers() { MpNet.On(OnInspect); MpNet.On(OnCards); MpNet.On(OnDeck); } public static void Begin(int playerId) { if (playerId != MpNet.LocalPlayerId && Present(playerId) && Target != playerId) { Target = playerId; if (!Mirrors.ContainsKey(playerId)) { Mirrors[playerId] = new Mirror(); } MpPlugin.Log.LogInfo((object)$"Watching player {playerId}'s hand"); MpNet.Send(new HandInspectMessage { TargetPlayerId = playerId }); } } public static void End() { if (IsInspecting) { MpPlugin.Log.LogInfo((object)$"Stopped watching player {Target}'s hand"); Target = -1; MpNet.Send(new HandInspectMessage { TargetPlayerId = -1 }); } } public static void Reset() { Target = -1; Mirrors.Clear(); Watchers.Clear(); _lastCards = null; _lastDeck = null; } public static void Update() { MpSafe.Run("MpHandInspect.Update", delegate { if (!MpSession.IsActive || !MpSession.IsInRun) { Reset(); } else { if (IsInspecting && (!MpBattleSync.InBattle || !Present(Target))) { End(); } if (Watchers.Count > 0) { foreach (int item in Watchers.Where((int id) => !Present(id)).ToList()) { Watchers.Remove(item); } } Publish(); } }); } private static void Publish() { //IL_0064: 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_0069: Unknown result type (might be due to invalid IL or missing references) if (Watchers.Count == 0) { _lastCards = null; _lastDeck = null; } else { if (Time.unscaledTime < _nextPublish) { return; } _nextPublish = Time.unscaledTime + 0.2f; GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); if (val != null) { BattleController battle = val.Battle; PlayerCardsMessage playerCardsMessage = new PlayerCardsMessage { Mana = ((battle != null) ? battle.BattleMana : ManaGroup.Empty), HideDrawOrder = (val.CanViewDrawZoneActualOrder <= 0), Cards = new List() }; if (battle != null) { playerCardsMessage.Cards.AddRange(MpCardMirror.Capture(battle.HandZone)); playerCardsMessage.Cards.AddRange(MpCardMirror.Capture(battle.DrawZone)); playerCardsMessage.Cards.AddRange(MpCardMirror.Capture(battle.DiscardZone)); playerCardsMessage.Cards.AddRange(MpCardMirror.Capture(battle.ExileZone)); } SendIfChanged(playerCardsMessage, ref _lastCards); SendIfChanged(new PlayerDeckMessage { Cards = MpCardMirror.Capture(val.BaseDeck) }, ref _lastDeck); } } } private static void SendIfChanged(NetMessage message, ref byte[] previous) { NetWriter netWriter = new NetWriter(); message.Write(netWriter); byte[] array = netWriter.ToArray(); if (previous == null || !Same(previous, array)) { previous = array; MpNet.Send(message); } } private static bool Same(byte[] a, byte[] b) { if (a.Length != b.Length) { return false; } for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) { return false; } } return true; } private static void OnInspect(HandInspectMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { bool flag = message.TargetPlayerId == MpNet.LocalPlayerId; if ((flag ? Watchers.Add(message.SenderId) : Watchers.Remove(message.SenderId)) && flag) { _lastCards = null; _lastDeck = null; _nextPublish = 0f; } } } private static void OnCards(PlayerCardsMessage message) { if (message.SenderId == MpNet.LocalPlayerId) { return; } MpSafe.Run("MpHandInspect.OnCards", delegate { //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_0080: 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_0086: 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_009e: Expected I4, but got Unknown Mirror mirror = MirrorFor(message.SenderId); mirror.Mana = message.Mana; mirror.HideDrawOrder = message.HideDrawOrder; mirror.Hand.Clear(); mirror.Draw.Clear(); mirror.Discard.Clear(); mirror.Exile.Clear(); foreach (Card item in MpCardMirror.Rebuild(message.Cards)) { CardZone zone = item.Zone; switch (zone - 1) { case 1: mirror.Hand.Add(item); break; case 0: mirror.Draw.Add(item); break; case 2: mirror.Discard.Add(item); break; case 3: mirror.Exile.Add(item); break; } } mirror.Revision++; }); } private static void OnDeck(PlayerDeckMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { MpSafe.Run("MpHandInspect.OnDeck", delegate { Mirror mirror = MirrorFor(message.SenderId); mirror.Deck.Clear(); mirror.Deck.AddRange(MpCardMirror.Rebuild(message.Cards)); mirror.Revision++; }); } } private static Mirror MirrorFor(int playerId) { if (!Mirrors.TryGetValue(playerId, out var value)) { value = new Mirror(); Mirrors[playerId] = value; } return value; } } internal static class MpPersonalRng { private static RandomGen _installed; private static RandomGen _supply; private static GameRunController _run; internal static ulong Salt { get { if (MpNet.LocalPlayerId > 0) { return (ulong)(-7046029254386353131L * (MpNet.LocalPlayerId + 1)); } return 0uL; } } internal static RandomGen Supply => _supply; internal static void Reset() { _installed = null; _supply = null; _run = null; } internal static void Tick() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown if (!MpNet.IsOnline) { return; } ulong salt = Salt; if (salt == 0L) { return; } GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); if (val == null) { Reset(); return; } RandomGen adventureRng = val.AdventureRng; if (adventureRng != _installed || val != _run) { ulong num = ((adventureRng != null) ? adventureRng.State : val.RootSeed); RandomGen installed = (val.AdventureRng = new RandomGen(Seed(num, salt, "adventure"))); _installed = installed; _supply = new RandomGen(Seed(num, salt, "supply")); if (val != _run) { _run = val; MpPlugin.Log.LogInfo((object)($"Event rewards personalised for player {MpNet.LocalPlayerId}, and kept that way " + "for the rest of the run")); } } } private static ulong Seed(ulong from, ulong salt, string streamName) { ulong num = 14695981039346656037uL; foreach (char c in streamName) { num ^= c; num *= 1099511628211L; } ulong num2 = from ^ salt ^ num; if (num2 != 0L) { return num2; } return 1uL; } } public enum MpPlayerState { Lobby, Ready, InRun, Disconnected, Resuming } public sealed class MpPlayer { public int Id; public string Name = "Player"; public MpPlayerState State; public string CharacterId = string.Empty; public int PlayerTypeIndex; public string InitExhibitId = string.Empty; public List StartingDeck = new List(); public int Difficulty = 1; public ulong ResumeSeed; public int ResumeStage = -1; public int ResumeX = -1; public int ResumeY = -1; public int Hp; public int MaxHp; public int Money; public int Power; public bool IsLocal => Id == MpNet.LocalPlayerId; public bool IsHost => Id == 0; public override string ToString() { return $"#{Id} {Name}"; } } internal static class MpRestart { private static bool _applying; private static (int Stage, int X, int Y)? _parked; private static SaveTiming _parkedTiming; private static float _parkedUntil; private const float ParkSeconds = 180f; internal static bool LocalDecides { get { if (MpSession.IsActive && MpSession.IsInRun) { return MpNet.IsHost; } return true; } } public static void RegisterHandlers() { MpNet.On(OnRemote); } internal static bool OnLocalRequest() { //IL_00aa: 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_00dc: Expected I4, but got Unknown if (_applying) { return true; } if (!MpSession.IsActive || !MpSession.IsInRun) { return true; } if (!MpNet.IsHost) { MpPlugin.Log.LogInfo((object)"Ignoring Restart Level: in multiplayer only the host restarts the level"); return false; } if (MapVotingPatch.MoveInFlight) { (int, int) pendingNode = MapVotingPatch.PendingNode; MpPlugin.Log.LogWarning((object)($"Refusing Restart Level: the party has moved on to ({pendingNode.Item1}, {pendingNode.Item2}) " + "and this client is not standing there yet")); MpNotice.Show(L10n.Get(MpText.RestartPartyMoving)); return false; } if (!CanRestartHere(out var timing)) { MpPlugin.Log.LogWarning((object)"Restart Level pressed, but this run has nothing to restart from; telling nobody"); return true; } (int, int, int) tuple = Here(); MpPlugin.Log.LogInfo((object)$"Restarting the level for the whole party (from {timing}, node ({tuple.Item2}, {tuple.Item3}))"); StationRestartMessage obj = new StationRestartMessage { Timing = (int)timing }; (obj.StageIndex, obj.X, obj.Y) = tuple; MpNet.Send(obj); Teardown(); return true; } private static void OnRemote(StationRestartMessage message) { if (message.SenderId == MpNet.LocalPlayerId) { return; } if (message.SenderId != 0) { MpPlugin.Log.LogWarning((object)$"Ignoring a restart from player {message.SenderId}: only the host restarts the level"); return; } MpSafe.Run("MpRestart.OnRemote", delegate { //IL_0082: Unknown result type (might be due to invalid IL or missing references) (int, int, int) tuple = (message.StageIndex, message.X, message.Y); (int, int, int) tuple2 = Here(); (int, int, int) tuple3 = tuple2; (int, int, int) tuple4 = tuple; if (tuple3.Item1 == tuple4.Item1 && tuple3.Item2 == tuple4.Item2 && tuple3.Item3 == tuple4.Item3) { Restart((SaveTiming)message.Timing); } else { _parked = tuple; _parkedTiming = (SaveTiming)message.Timing; _parkedUntil = Time.unscaledTime + 180f; MpPlugin.Log.LogInfo((object)($"The host restarted at node ({tuple.Item2}, {tuple.Item3}), but this client is still " + $"at ({tuple2.Item2}, {tuple2.Item3}); holding the restart until it gets there")); } }); } public static void Update() { if (!_parked.HasValue) { return; } MpSafe.Run("MpRestart.Update", delegate { //IL_006b: Unknown result type (might be due to invalid IL or missing references) (int, int, int) value = _parked.Value; if (!MpSession.IsActive || !MpSession.IsInRun) { _parked = null; } else { (int, int, int) tuple = Here(); (int, int, int) tuple2 = value; if (tuple.Item1 == tuple2.Item1 && tuple.Item2 == tuple2.Item2 && tuple.Item3 == tuple2.Item3 && CanRestartHere(out var _)) { _parked = null; Restart(_parkedTiming); } else if (Time.unscaledTime >= _parkedUntil) { _parked = null; (int, int, int) tuple3 = Here(); MpPlugin.Log.LogError((object)($"Gave up on the host's restart at node ({value.Item2}, {value.Item3}): this client " + $"is still at ({tuple3.Item2}, {tuple3.Item3}) and the party is now out of step")); } } }); } public static void Reset() { _parked = null; } private static (int Stage, int X, int Y) Here() { GameMaster instance = Singleton.Instance; GameRunController obj = ((instance != null) ? instance.CurrentGameRun : null); object obj2; if (obj == null) { obj2 = null; } else { GameMap currentMap = obj.CurrentMap; obj2 = ((currentMap != null) ? currentMap.VisitingNode : null); } MapNode val = (MapNode)obj2; int? obj3; if (obj == null) { obj3 = null; } else { Stage currentStage = obj.CurrentStage; obj3 = ((currentStage != null) ? new int?(currentStage.Index) : ((int?)null)); } return (Stage: obj3 ?? (-1), X: (val != null) ? val.X : (-1), Y: (val != null) ? val.Y : (-1)); } private static void Restart(SaveTiming ordered) { //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_0027: 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) if (!CanRestartHere(out var timing)) { MpPlugin.Log.LogWarning((object)"The host restarted the level, but this client has no save to restart from; staying put"); return; } if (timing != ordered) { MpPlugin.Log.LogWarning((object)($"The host restarted from {ordered}, but this client is at {timing}; " + "restarting from here instead")); } MpPlugin.Log.LogInfo((object)"The host restarted the level"); Teardown(); _applying = true; try { GameMaster.RequestReenterStation(); } finally { _applying = false; } } private static bool CanRestartHere(out SaveTiming timing) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected I4, but got Unknown timing = (SaveTiming)0; GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentStation : null); } if (obj == null) { return false; } GameRunSaveData gameRunSaveData = instance.GameRunSaveData; if (gameRunSaveData == null) { return false; } timing = (SaveTiming)(int)gameRunSaveData.Timing; if ((int)timing != 1 && (int)timing != 2 && (int)timing != 3) { return (int)timing == 4; } return true; } private static void Teardown() { MpSafe.Run("MpRestart.Teardown", delegate { EnemyDamageHook.UnhookAll(); PlayerDamageHook.Unhook(); MpPrivateEnemies.Reset(); MpBattleSync.LeaveBattle(); MpBattleSync.Reset(); MapSync.ClearCommit(); MapVotingPatch.Reset(); }); } } public enum MpSessionState { Offline, Lobby, WaitingForPlayers, InRun } public static class MpSession { private static readonly Dictionary PlayersById = new Dictionary(); private static float _nextStatusBroadcast; private static bool _handlersRegistered; private static float? _runEnemyHpScale; private static float[] _runEnemyHpEscalation; private static float? _runReviveHpFraction; private static bool? _runEnemyResilience; private static int? _runDifficulty; public const float UnresponsiveSeconds = 45f; public static MpSessionState State { get; private set; } = MpSessionState.Offline; public static ulong RunSeed { get; private set; } public static float EnemyHpScalePerExtraPlayer => _runEnemyHpScale ?? MpPlugin.EnemyHpScalePerExtraPlayer.Value; public static float ReviveHpFraction => _runReviveHpFraction ?? MpPlugin.ReviveHpFraction.Value; public static bool EnemyResilience => _runEnemyResilience ?? MpPlugin.EnableEnemyResilience.Value; public static int HostDifficulty { get; private set; } = 1; public static int RunDifficulty => _runDifficulty ?? HostDifficulty; public static string StatusLine { get; internal set; } = string.Empty; public static bool IsActive { get { if (MpNet.IsOnline) { return PlayersById.Count > 1; } return false; } } public static bool IsInRun => State == MpSessionState.InRun; public static IEnumerable Players => PlayersById.Values.OrderBy((MpPlayer p) => p.Id); public static IEnumerable ConnectedPlayers => from p in PlayersById.Values where p.State != MpPlayerState.Disconnected orderby p.Id select p; public static int ConnectedCount => ConnectedPlayers.Count(); public static IEnumerable RespondingPlayers => ConnectedPlayers.Where((MpPlayer p) => !IsUnresponsive(p.Id)); public static MpPlayer LocalPlayer => Get(MpNet.LocalPlayerId); public static List SeatOrder => ConnectedPlayers.ToList(); public static int LocalSeatIndex => SeatIndexOf(MpNet.LocalPlayerId); public static float EnemyHpEscalationForAct(int act) { if (act < 1 || act > 4) { return 0f; } if (_runEnemyHpEscalation == null) { return MpPlugin.EnemyHpEscalationByAct[act - 1].Value; } return _runEnemyHpEscalation[act - 1]; } public static bool IsUnresponsive(int playerId) { if (playerId != MpNet.LocalPlayerId) { return MpNet.SilenceFor(playerId) > 45f; } return false; } public static MpPlayer Get(int id) { if (!PlayersById.TryGetValue(id, out var value)) { return null; } return value; } public static int SeatIndexOf(int playerId) { List seatOrder = SeatOrder; for (int i = 0; i < seatOrder.Count; i++) { if (seatOrder[i].Id == playerId) { return i; } } return -1; } public static void EnsureHandlers() { if (!_handlersRegistered) { _handlersRegistered = true; MpNet.ClientConnected += OnClientConnected; MpNet.PeerDisconnected += OnPeerDisconnected; MpNet.Disconnected += OnDisconnectedFromHost; MpNet.ServerLinkReady += OnServerLinkReady; MpNet.ConnectFailed += OnConnectFailed; SteamNet.JoinRequested += OnSteamJoinRequested; SteamNet.LobbyReady += OnSteamLobbyReady; MpNet.On(OnJoinRequest); MpNet.On(OnJoinAccepted); MpNet.On(OnJoinRejected); MpNet.On(OnPlayerList); MpNet.On(OnPlayerReady); MpNet.On(OnResumeReady); MpNet.On(OnRunStart); MpNet.On(OnRunResume); MpNet.On(OnRunStartCancelled); MpNet.On(OnBackToLobby); MpNet.On(OnLobbyDifficulty); MpNet.On(OnPlayerStatus); MpNet.On(OnPlayerLeft); MapSync.RegisterHandlers(); MpHandInspect.RegisterHandlers(); MpBorderSensor.RegisterHandlers(); MpRestart.RegisterHandlers(); MpBattleSync.RegisterHandlers(); } } public static bool Host(int port) { EnsureHandlers(); if (!MpNet.StartHost(port)) { StatusLine = L10n.Get(MpText.StatusHostFailed, L10n.Decode(MpNet.LastError)); return false; } PlayersById.Clear(); PlayersById[0] = new MpPlayer { Id = 0, Name = MpPlugin.PlayerName.Value, State = MpPlayerState.Lobby }; State = MpSessionState.Lobby; StatusLine = L10n.Get(MpText.StatusHostingOnPort, port); return true; } public static bool HostSteam() { EnsureHandlers(); if (!MpNet.StartSteamHost()) { StatusLine = L10n.Get(MpText.StatusHostSteamFailed, L10n.Decode(MpNet.LastError)); return false; } PlayersById.Clear(); PlayersById[0] = new MpPlayer { Id = 0, Name = MpPlugin.PlayerName.Value, State = MpPlayerState.Lobby }; State = MpSessionState.Lobby; StatusLine = L10n.Get(MpText.StatusOpeningSteamLobby); SteamNet.CreateLobby(); return true; } private static void OnSteamLobbyReady() { if (MpNet.IsHost) { StatusLine = L10n.Get(MpText.StatusHostingOverSteam); } } public static bool Join(string address, int port) { EnsureHandlers(); PlayersById.Clear(); State = MpSessionState.Lobby; StatusLine = L10n.Get(MpText.StatusConnecting); if (!MpNet.StartClient(address, port)) { State = MpSessionState.Offline; StatusLine = L10n.Get(MpText.StatusConnectFailed, L10n.Decode(MpNet.LastError)); return false; } return true; } private static void OnSteamJoinRequested(CSteamID host) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (MpNet.IsOnline) { MpPlugin.Log.LogWarning((object)"Ignoring a Steam invite: already in a session"); StatusLine = L10n.Get(MpText.StatusAlreadyInSession); return; } EnsureHandlers(); PlayersById.Clear(); State = MpSessionState.Lobby; StatusLine = L10n.Get(MpText.StatusConnectingSteam); if (!MpNet.StartSteamClient(host)) { State = MpSessionState.Offline; StatusLine = L10n.Get(MpText.StatusConnectSteamFailed, L10n.Decode(MpNet.LastError)); } } private static void OnServerLinkReady() { MpNet.SendToHostDirect(new JoinRequestMessage { ProtocolVersion = 34, PlayerName = MpPlugin.PlayerName.Value }); } private static void OnConnectFailed(string reason) { MpPlugin.Log.LogWarning((object)L10n.En(MpText.StatusConnectFailed, L10n.DecodeEn(reason))); Leave(L10n.Get(MpText.StatusConnectFailed, L10n.Decode(reason)), L10n.En(MpText.StatusConnectFailed, L10n.DecodeEn(reason))); } public static void Leave(MpText reason) { Leave(L10n.Get(reason), L10n.En(reason)); } public static void Leave(string statusLine, string shutdownReason = null) { if (MpNet.IsOnline) { MpNet.Shutdown(shutdownReason ?? statusLine); } SteamNet.LeaveLobby(); PlayersById.Clear(); State = MpSessionState.Offline; RunSeed = 0uL; _runEnemyHpScale = null; _runEnemyHpEscalation = null; _runReviveHpFraction = null; _runEnemyResilience = null; _runDifficulty = null; HostDifficulty = 1; StatusLine = statusLine; StartGameInterceptPatch.Cancel(); RestoreGameInterceptPatch.Cancel(); MapSync.Reset(); MpRestart.Reset(); MpHandInspect.Reset(); MpBorderSensor.Reset(); MpPersonalRng.Reset(); MpBattleSync.Reset(); } public static void Update() { if (State == MpSessionState.Offline) { return; } if (Time.unscaledTime >= _nextStatusBroadcast) { _nextStatusBroadcast = Time.unscaledTime + 1f; BroadcastLocalStatus(); if (State == MpSessionState.WaitingForPlayers) { StatusLine = DescribeRunWait(); } } MapSync.Update(); MpRestart.Update(); MpHandInspect.Update(); MpBorderSensor.Tick(); MpPersonalRng.Tick(); } private static void OnClientConnected(NetConnection connection) { } private static void OnJoinRequest(JoinRequestMessage message) { if (!MpNet.IsHost) { return; } NetConnection currentSource = MpNet.CurrentSource; if (currentSource != null && !currentSource.HandshakeComplete) { if (message.ProtocolVersion != 34) { string reason = L10n.Encode(MpText.ReasonProtocolMismatch, 34, message.ProtocolVersion); MpNet.SendToConnection(currentSource, new JoinRejectedMessage { Reason = reason }); currentSource.Close(reason); return; } if (State != MpSessionState.Lobby) { string reason2 = L10n.Encode(MpText.ReasonRunInProgress); MpNet.SendToConnection(currentSource, new JoinRejectedMessage { Reason = reason2 }); currentSource.Close(reason2); return; } if (PlayersById.Count >= 4) { string reason3 = L10n.Encode(MpText.ReasonSessionFull); MpNet.SendToConnection(currentSource, new JoinRejectedMessage { Reason = reason3 }); currentSource.Close(reason3); return; } int num = (currentSource.PlayerId = MpNet.AllocatePlayerId()); currentSource.HandshakeComplete = true; PlayersById[num] = new MpPlayer { Id = num, Name = (string.IsNullOrWhiteSpace(message.PlayerName) ? ("Player " + num) : message.PlayerName), State = MpPlayerState.Lobby }; MpNet.SendToConnection(currentSource, new JoinAcceptedMessage { AssignedPlayerId = num }); BroadcastPlayerList(); MpNet.SendToConnection(currentSource, new LobbyDifficultyMessage { Difficulty = HostDifficulty }); MpPlugin.Log.LogInfo((object)$"{PlayersById[num]} joined from {currentSource.RemoteEndPoint}"); } } private static void OnJoinAccepted(JoinAcceptedMessage message) { MpNet.LocalPlayerId = message.AssignedPlayerId; StatusLine = L10n.Get(MpText.StatusConnectedAsPlayer, message.AssignedPlayerId); MpPlugin.Log.LogInfo((object)L10n.En(MpText.StatusConnectedAsPlayer, message.AssignedPlayerId)); } private static void OnJoinRejected(JoinRejectedMessage message) { MpPlugin.Log.LogWarning((object)L10n.En(MpText.StatusRejected, L10n.DecodeEn(message.Reason))); Leave(L10n.Get(MpText.StatusRejected, L10n.Decode(message.Reason)), L10n.En(MpText.StatusRejected, L10n.DecodeEn(message.Reason))); } private static void OnPlayerList(PlayerListMessage message) { if (MpNet.IsHost) { return; } HashSet seen = new HashSet(); foreach (MpPlayer player in message.Players) { seen.Add(player.Id); if (PlayersById.TryGetValue(player.Id, out var value)) { value.Name = player.Name; value.State = player.State; value.CharacterId = player.CharacterId; value.PlayerTypeIndex = player.PlayerTypeIndex; value.InitExhibitId = player.InitExhibitId; value.StartingDeck = player.StartingDeck; value.Hp = player.Hp; value.MaxHp = player.MaxHp; value.Money = player.Money; value.Power = player.Power; } else { PlayersById[player.Id] = player; } } foreach (int item in PlayersById.Keys.Where((int id) => !seen.Contains(id)).ToList()) { PlayersById.Remove(item); } } public static void BroadcastPlayerList() { if (!MpNet.IsHost) { return; } byte[] payload = MessageRegistry.Serialize(new PlayerListMessage { Players = Players.ToList() }); foreach (NetConnection connection in MpNet.Connections) { if (connection.HandshakeComplete) { connection.Send(payload); } } } public static void PublishHostDifficulty(int difficulty) { if (MpNet.IsOnline && MpNet.IsHost) { difficulty = ClampDifficulty(difficulty); if (difficulty != HostDifficulty) { HostDifficulty = difficulty; MpPlugin.Log.LogInfo((object)("Difficulty for the party is now " + DescribeDifficulty(difficulty))); MpNet.Send(new LobbyDifficultyMessage { Difficulty = difficulty }); } } } private static void OnLobbyDifficulty(LobbyDifficultyMessage message) { if (message.SenderId == 0 && !MpNet.IsHost) { HostDifficulty = ClampDifficulty(message.Difficulty); MpPlugin.Log.LogInfo((object)("The host set the difficulty to " + DescribeDifficulty(HostDifficulty))); MpSafe.Run("ApplyHostDifficulty", delegate { LobbyDifficultyPatch.ApplyHostChoice(HostDifficulty); }); } } private static int ClampDifficulty(int difficulty) { return Mathf.Clamp(difficulty, 0, 3); } public static string DescribeDifficulty(int difficulty) { return ClampDifficulty(difficulty) switch { 0 => "Easy", 1 => "Normal", 2 => "Hard", _ => "Lunatic", }; } public static void SubmitLocalReady(string characterId, int playerTypeIndex, string initExhibitId, List deck, int difficulty) { State = MpSessionState.WaitingForPlayers; StatusLine = L10n.Get(MpText.StatusWaitingForPlayers); MpNet.Send(new PlayerReadyMessage { CharacterId = characterId, PlayerTypeIndex = playerTypeIndex, InitExhibitId = initExhibitId, Deck = deck, Difficulty = difficulty }); } private static void OnPlayerReady(PlayerReadyMessage message) { MpPlayer mpPlayer = Get(message.SenderId); if (mpPlayer != null) { mpPlayer.CharacterId = message.CharacterId; mpPlayer.PlayerTypeIndex = message.PlayerTypeIndex; mpPlayer.InitExhibitId = message.InitExhibitId; mpPlayer.StartingDeck = message.Deck; mpPlayer.Difficulty = ClampDifficulty(message.Difficulty); mpPlayer.State = MpPlayerState.Ready; if (MpNet.IsHost) { BroadcastPlayerList(); TryBeginRun(); } } } public static void SubmitLocalResume(ulong seed, int stageIndex, int x, int y, int difficulty, string characterId) { State = MpSessionState.WaitingForPlayers; StatusLine = L10n.Get(MpText.StatusWaitingToResume); MpNet.Send(new ResumeReadyMessage { Seed = seed, StageIndex = stageIndex, X = x, Y = y, Difficulty = difficulty, CharacterId = (characterId ?? string.Empty) }); } private static void OnResumeReady(ResumeReadyMessage message) { MpPlayer mpPlayer = Get(message.SenderId); if (mpPlayer != null) { mpPlayer.ResumeSeed = message.Seed; mpPlayer.ResumeStage = message.StageIndex; mpPlayer.ResumeX = message.X; mpPlayer.ResumeY = message.Y; mpPlayer.Difficulty = ClampDifficulty(message.Difficulty); mpPlayer.CharacterId = message.CharacterId ?? string.Empty; mpPlayer.State = MpPlayerState.Resuming; if (MpNet.IsHost) { BroadcastPlayerList(); TryBeginRun(); } } } private static void TryBeginRun() { List list = ConnectedPlayers.ToList(); if (list.Count == 0) { return; } bool flag = false; bool flag2 = false; foreach (MpPlayer item in list) { if (item.State == MpPlayerState.Ready) { flag = true; continue; } if (item.State == MpPlayerState.Resuming) { flag2 = true; continue; } return; } if (flag && flag2) { CancelRunStart(L10n.Encode(MpText.ReasonStartSplit)); } else if (flag2) { BeginResume(list); } else { BeginNewRun(); } } private static void BeginNewRun() { ulong num = (ulong)(Environment.TickCount * 6364136223846793005L + 1442695040888963407L); num ^= (ulong)DateTime.UtcNow.Ticks; if (num == 0L) { num = 1uL; } int difficulty = Get(0)?.Difficulty ?? HostDifficulty; MpNet.Send(new RunStartMessage { Seed = num, Difficulty = difficulty, EnemyHpScalePerExtraPlayer = MpPlugin.EnemyHpScalePerExtraPlayer.Value, EnemyHpEscalationByAct = LocalEscalationSettings(), ReviveHpFraction = MpPlugin.ReviveHpFraction.Value, EnemyResilience = MpPlugin.EnableEnemyResilience.Value }); } private static void BeginResume(List party) { ulong seed = party[0].ResumeSeed; if (party.FirstOrDefault((MpPlayer p) => p.ResumeSeed != seed) != null) { MpPlugin.Log.LogWarning((object)("Refusing to continue: these are not saves of the same run — " + string.Join(", ", party.Select((MpPlayer p) => $"{p.Name} has seed {p.ResumeSeed}")))); CancelRunStart(L10n.Encode(MpText.ReasonResumeDifferentRuns)); return; } string arg = string.Join(", ", party.Select(DescribeSavedPosition)); MpPlugin.Log.LogInfo((object)$"Continuing run {seed}; saves are at {arg}"); MpPlayer first = party[0]; bool flag = party.Any((MpPlayer p) => p.ResumeStage != first.ResumeStage || p.ResumeX != first.ResumeX || p.ResumeY != first.ResumeY); if (flag) { MpPlugin.Log.LogWarning((object)"Not everybody saved at the same point in the run; the party will be out of step until whoever is behind catches up"); } int difficulty = Get(0)?.Difficulty ?? HostDifficulty; MpNet.Send(new RunResumeMessage { Seed = seed, Difficulty = difficulty, EnemyHpScalePerExtraPlayer = MpPlugin.EnemyHpScalePerExtraPlayer.Value, EnemyHpEscalationByAct = LocalEscalationSettings(), ReviveHpFraction = MpPlugin.ReviveHpFraction.Value, EnemyResilience = MpPlugin.EnableEnemyResilience.Value, Note = (flag ? L10n.Encode(MpText.NoticeResumeStaggered) : string.Empty) }); } private static string DescribeSavedPosition(MpPlayer player) { return $"{player.Name} at act {player.ResumeStage + 1}, node ({player.ResumeX}, {player.ResumeY})"; } public static string DescribeRunWait() { List list = (from p in ConnectedPlayers where p.State != MpPlayerState.Ready && p.State != MpPlayerState.Resuming select p.Name).ToList(); if (list.Count != 0) { return L10n.Get(MpText.StatusWaitingForNames, string.Join(", ", list)); } return L10n.Get(MpText.StatusWaitingForPlayers); } private static void CancelRunStart(string encodedReason) { MpPlugin.Log.LogWarning((object)("Not starting the run: " + L10n.DecodeEn(encodedReason))); foreach (MpPlayer value in PlayersById.Values) { if (value.State == MpPlayerState.Ready || value.State == MpPlayerState.Resuming) { value.State = MpPlayerState.Lobby; } } BroadcastPlayerList(); MpNet.Send(new RunStartCancelledMessage { Reason = encodedReason }); } private static void OnRunStartCancelled(RunStartCancelledMessage message) { MpNotice.Show(StatusLine = L10n.Decode(message.Reason)); MpPlugin.Log.LogInfo((object)L10n.DecodeEn(message.Reason)); if (State == MpSessionState.WaitingForPlayers) { StartGameInterceptPatch.Cancel(); RestoreGameInterceptPatch.Cancel(); State = MpSessionState.Lobby; } } private static float[] LocalEscalationSettings() { float[] array = new float[4]; for (int i = 0; i < array.Length; i++) { array[i] = MpPlugin.EnemyHpEscalationByAct[i].Value; } return array; } private static void OnRunStart(RunStartMessage message) { RunSeed = message.Seed; State = MpSessionState.InRun; StatusLine = L10n.Get(MpText.StatusRunStarted, message.Seed); AdoptRunRules(message.Difficulty, message.EnemyHpScalePerExtraPlayer, message.EnemyHpEscalationByAct, message.ReviveHpFraction, message.EnemyResilience); foreach (MpPlayer value in PlayersById.Values) { if (value.State == MpPlayerState.Ready) { value.State = MpPlayerState.InRun; } } ForgetLastRun(); StartGameInterceptPatch.BeginPendingRun(message.Seed); } private static void OnRunResume(RunResumeMessage message) { if (!RestoreGameInterceptPatch.HasPending) { MpPlugin.Log.LogWarning((object)"The party is continuing a run, but this client had no save held"); return; } RunSeed = message.Seed; State = MpSessionState.InRun; StatusLine = L10n.Get(MpText.StatusRunResumed, message.Seed); AdoptRunRules(message.Difficulty, message.EnemyHpScalePerExtraPlayer, message.EnemyHpEscalationByAct, message.ReviveHpFraction, message.EnemyResilience); foreach (MpPlayer value in PlayersById.Values) { if (value.State == MpPlayerState.Resuming) { value.State = MpPlayerState.InRun; } } if (!string.IsNullOrEmpty(message.Note)) { MpNotice.Show(L10n.Decode(message.Note)); } ForgetLastRun(); RestoreGameInterceptPatch.BeginPendingResume(message.Seed); } private static void ForgetLastRun() { MapSync.Reset(); MpRestart.Reset(); MpHandInspect.Reset(); MpBorderSensor.Reset(); MpPersonalRng.Reset(); } private static void AdoptRunRules(int difficulty, float enemyHpScale, float[] escalation, float reviveHpFraction, bool enemyResilience) { _runDifficulty = ClampDifficulty(difficulty); _runEnemyHpScale = enemyHpScale; _runEnemyHpEscalation = ((escalation != null && escalation.Length == 4) ? escalation : new float[4]); if (!MpNet.IsHost && !Mathf.Approximately(enemyHpScale, MpPlugin.EnemyHpScalePerExtraPlayer.Value)) { MpPlugin.Log.LogInfo((object)($"Using the host's enemy health scaling ({enemyHpScale:0.##} per extra player) " + $"instead of this machine's ({MpPlugin.EnemyHpScalePerExtraPlayer.Value:0.##})")); } _runReviveHpFraction = reviveHpFraction; if (!MpNet.IsHost && !Mathf.Approximately(reviveHpFraction, MpPlugin.ReviveHpFraction.Value)) { MpPlugin.Log.LogInfo((object)($"Using the host's revive fraction ({reviveHpFraction:0.##}) " + $"instead of this machine's ({MpPlugin.ReviveHpFraction.Value:0.##})")); } _runEnemyResilience = enemyResilience; if (!MpNet.IsHost && enemyResilience != MpPlugin.EnableEnemyResilience.Value) { MpPlugin.Log.LogInfo((object)("Using the host's Resilient setting (" + (enemyResilience ? "on" : "off") + ") instead of this machine's (" + (MpPlugin.EnableEnemyResilience.Value ? "on" : "off") + ")")); } if (MpNet.IsHost) { return; } for (int i = 1; i <= 4; i++) { float num = _runEnemyHpEscalation[i - 1]; float value = MpPlugin.EnemyHpEscalationByAct[i - 1].Value; if (!Mathf.Approximately(num, value)) { MpPlugin.Log.LogInfo((object)$"Using the host's Act {i} escalation ({num:0.##}) instead of this machine's ({value:0.##})"); } } } public static void BackToLobby() { if (State != MpSessionState.Offline) { bool num = State != MpSessionState.Lobby; State = MpSessionState.Lobby; RunSeed = 0uL; _runDifficulty = null; _runEnemyHpScale = null; _runEnemyHpEscalation = null; _runReviveHpFraction = null; _runEnemyResilience = null; StartGameInterceptPatch.Cancel(); RestoreGameInterceptPatch.Cancel(); EnemyDamageHook.UnhookAll(); PlayerDamageHook.Unhook(); MpPrivateEnemies.Reset(); MpBattleSync.LeaveBattle(); MpBattleSync.Reset(); ForgetLastRun(); if (num) { StatusLine = L10n.Get(MpText.StatusBackInLobby); MpNet.Send(new BackToLobbyMessage()); } } } private static void OnBackToLobby(BackToLobbyMessage message) { MpPlayer mpPlayer = Get(message.SenderId); if (mpPlayer != null && mpPlayer.State != MpPlayerState.Disconnected) { mpPlayer.State = MpPlayerState.Lobby; if (MpNet.IsHost) { BroadcastPlayerList(); } } } private static void BroadcastLocalStatus() { GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); if (((val != null) ? val.Player : null) != null) { MpPlayer localPlayer = LocalPlayer; if (localPlayer != null) { localPlayer.Hp = ((Unit)val.Player).Hp; localPlayer.MaxHp = ((Unit)val.Player).MaxHp; localPlayer.Money = val.Money; localPlayer.Power = val.Player.Power; } MpNet.Send(new PlayerStatusMessage { Hp = ((Unit)val.Player).Hp, MaxHp = ((Unit)val.Player).MaxHp, Money = val.Money, Power = val.Player.Power }); } } private static void OnPlayerStatus(PlayerStatusMessage message) { MpPlayer mpPlayer = Get(message.SenderId); if (mpPlayer != null) { mpPlayer.Hp = message.Hp; mpPlayer.MaxHp = message.MaxHp; mpPlayer.Money = message.Money; mpPlayer.Power = message.Power; } } private static void OnPeerDisconnected(int playerId, string reason) { if (MpNet.IsHost) { if (PlayersById.TryGetValue(playerId, out var value)) { value.State = MpPlayerState.Disconnected; } MpNet.Send(new PlayerLeftMessage { PlayerId = playerId, Reason = reason }); if (State == MpSessionState.Lobby) { PlayersById.Remove(playerId); } BroadcastPlayerList(); TryBeginRun(); } } private static void OnPlayerLeft(PlayerLeftMessage message) { if (PlayersById.TryGetValue(message.PlayerId, out var value)) { value.State = MpPlayerState.Disconnected; StatusLine = L10n.Get(MpText.StatusPlayerLeft, value.Name, L10n.Decode(message.Reason)); } MpBattleSync.OnPlayerLeft(message.PlayerId); MapSync.OnPlayerLeft(message.PlayerId); } private static void OnDisconnectedFromHost(string reason) { StatusLine = L10n.Get(MpText.StatusDisconnected, L10n.Decode(reason)); MpPlugin.Log.LogWarning((object)L10n.En(MpText.StatusDisconnected, L10n.DecodeEn(reason))); PlayersById.Clear(); State = MpSessionState.Offline; _runEnemyHpScale = null; _runEnemyHpEscalation = null; _runReviveHpFraction = null; _runEnemyResilience = null; _runDifficulty = null; HostDifficulty = 1; MapSync.Reset(); MpRestart.Reset(); MpHandInspect.Reset(); MpBorderSensor.Reset(); MpPersonalRng.Reset(); MpBattleSync.Reset(); } } internal static class RngFixInterop { private const string GrRngsTypeName = "RngFix.CustomRngs.GrRngs"; private const string ShopRngsTypeName = "RngFix.CustomRngs.ShopRngs"; private static readonly string[] PersonalFields = new string[6] { "rareExhibitQueueRng", "qingeUpgradeQueueRng", "cardUpgradeQueueRng", "extraCardRewardRng", "eliteCardRng", "bossCardRng" }; private static bool _resolved; private static MethodInfo _getOrCreate; private static FieldInfo _persRngsField; private static MethodInfo _shopRngsInit; public static bool Installed { get; private set; } private static void Resolve() { if (_resolved) { return; } _resolved = true; Type type = null; Type type2 = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (type == null) { type = assembly.GetType("RngFix.CustomRngs.GrRngs", throwOnError: false); } if (type2 == null) { type2 = assembly.GetType("RngFix.CustomRngs.ShopRngs", throwOnError: false); } if (type != null && type2 != null) { break; } } if (type == null) { MpPlugin.Log.LogInfo((object)"RngFix is not installed; the vanilla reward streams are the only ones to personalise"); return; } _getOrCreate = type.GetMethod("GetOrCreate", BindingFlags.Static | BindingFlags.Public); _persRngsField = type.GetField("persRngs", BindingFlags.Instance | BindingFlags.Public); _shopRngsInit = type2?.GetMethod("Init", BindingFlags.Static | BindingFlags.Public); if (_getOrCreate == null || _persRngsField == null) { MpPlugin.Log.LogWarning((object)"RngFix is installed but its RNG container does not look the way this expects; reward rolls may be identical for every player"); } else { Installed = true; } } public static void Personalise(GameRunController gameRun, ulong salt) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown Resolve(); if (!Installed || gameRun == null) { return; } object obj = _getOrCreate.Invoke(null, new object[1] { gameRun }); object value = _persRngsField.GetValue(obj); if (value == null) { return; } Type type = value.GetType(); int num = 0; string[] personalFields = PersonalFields; foreach (string text in personalFields) { FieldInfo field = type.GetField(text, BindingFlags.Instance | BindingFlags.Public); if (field == null || field.FieldType != typeof(RandomGen)) { MpPlugin.Log.LogWarning((object)("RngFix has no '" + text + "' stream any more; leaving it alone")); continue; } field.SetValue(value, (object?)new RandomGen(Mix(salt, text))); num++; } num += (ReseedShop(value, type, salt) ? 1 : 0); num += (ReseedSelfRngs(value, type, "exhibitSelfRngs", salt) ? 1 : 0); int num2 = PersonalFields.Length + 2; if (num == num2) { MpPlugin.Log.LogInfo((object)$"Personalised all {num2} of RngFix's reward streams for this player"); } else { MpPlugin.Log.LogWarning((object)($"Personalised only {num} of RngFix's {num2} reward streams; " + "the rest will be identical for every player")); } } private static bool ReseedShop(object persRngs, Type type, ulong salt) { FieldInfo field = type.GetField("shopRngs", BindingFlags.Instance | BindingFlags.Public); if (field == null || _shopRngsInit == null) { return false; } field.SetValue(persRngs, _shopRngsInit.Invoke(null, new object[1] { Mix(salt, "shopRngs") })); return true; } private static bool ReseedSelfRngs(object persRngs, Type type, string fieldName, ulong salt) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown object obj = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public)?.GetValue(persRngs); if (obj == null) { return false; } Type type2 = obj.GetType(); FieldInfo field = type2.GetField("rootRng", BindingFlags.Instance | BindingFlags.Public); if (field != null && field.FieldType == typeof(RandomGen)) { field.SetValue(obj, (object?)new RandomGen(Mix(salt, fieldName))); } if (!(type2.GetField("rngs", BindingFlags.Instance | BindingFlags.Public)?.GetValue(obj) is IDictionary dictionary)) { return false; } foreach (object item in dictionary.Keys.Cast().ToList()) { dictionary[item] = (object?)new RandomGen(Mix(salt, fieldName + ":" + item)); } return true; } private static ulong Mix(ulong salt, string streamName) { ulong num = 14695981039346656037uL; foreach (char c in streamName) { num ^= c; num *= 1099511628211L; } ulong num2 = salt ^ num; if (num2 != 0L) { return num2; } return 1uL; } } } namespace LBOLMP.Session.Messages { [NetMessage(30)] public sealed class BattleStartMessage : NetMessage { public ulong BattleSeed; public string EnemyGroupId; public int PlayerCount; public override void Write(NetWriter w) { w.ULong(BattleSeed); w.String(EnemyGroupId); w.Int(PlayerCount); } public override void Read(NetReader r) { BattleSeed = r.ULong(); EnemyGroupId = r.String(); PlayerCount = r.Int(); } } [NetMessage(31)] public sealed class TurnCompleteMessage : NetMessage { public int Round; public override void Write(NetWriter w) { w.Int(Round); } public override void Read(NetReader r) { Round = r.Int(); } } [NetMessage(32)] public sealed class EnemyDamageMessage : NetMessage { public int EnemyIndex; public float Amount; public int DamageType; public bool IsAccuracy; public string GunName; public override void Write(NetWriter w) { w.Int(EnemyIndex); w.Float(Amount); w.Int(DamageType); w.Bool(IsAccuracy); w.String(GunName); } public override void Read(NetReader r) { EnemyIndex = r.Int(); Amount = r.Float(); DamageType = r.Int(); IsAccuracy = r.Bool(); GunName = r.String(); } } [NetMessage(33)] public sealed class EnemyStatusMessage : NetMessage { public int EnemyIndex; public string StatusId; public int Level; public int Duration; public bool HasLevel; public bool HasDuration; public bool Removing; public override void Write(NetWriter w) { w.Int(EnemyIndex); w.String(StatusId); w.Int(Level); w.Int(Duration); w.Bool(HasLevel); w.Bool(HasDuration); w.Bool(Removing); } public override void Read(NetReader r) { EnemyIndex = r.Int(); StatusId = r.String(); Level = r.Int(); Duration = r.Int(); HasLevel = r.Bool(); HasDuration = r.Bool(); Removing = r.Bool(); } } [NetMessage(34)] public sealed class RemoteCardPlayMessage : NetMessage { public string CardId; public bool Upgraded; public int TargetEnemyIndex; public override void Write(NetWriter w) { w.String(CardId); w.Bool(Upgraded); w.Int(TargetEnemyIndex); } public override void Read(NetReader r) { CardId = r.String(); Upgraded = r.Bool(); TargetEnemyIndex = r.Int(); } } [NetMessage(35)] public sealed class BattleStatusMessage : NetMessage { public int Hp; public int MaxHp; public int Block; public int Shield; public int HandCount; public int DrawCount; public int DiscardCount; public int CompletedRound = -1; public bool Finished; public List StatusEffects = new List(); public override void Write(NetWriter w) { w.Int(Hp); w.Int(MaxHp); w.Int(Block); w.Int(Shield); w.Int(HandCount); w.Int(DrawCount); w.Int(DiscardCount); w.Int(CompletedRound); w.Bool(Finished); w.StringList(StatusEffects); } public override void Read(NetReader r) { Hp = r.Int(); MaxHp = r.Int(); Block = r.Int(); Shield = r.Int(); HandCount = r.Int(); DrawCount = r.Int(); DiscardCount = r.Int(); CompletedRound = r.Int(); Finished = r.Bool(); StatusEffects = new List(r.StringArray()); } } [NetMessage(36)] public sealed class BattleFinishedMessage : NetMessage { public bool Survived; public override void Write(NetWriter w) { w.Bool(Survived); } public override void Read(NetReader r) { Survived = r.Bool(); } } [NetMessage(37)] public sealed class PlayerDownMessage : NetMessage { public override void Write(NetWriter w) { } public override void Read(NetReader r) { } } [NetMessage(39)] public sealed class EnemyVitalsMessage : NetMessage { public List Vitals = new List(); public override void Write(NetWriter w) { w.IntList(Vitals); } public override void Read(NetReader r) { Vitals = new List(r.IntArray()); } } [NetMessage(40)] public sealed class EventBattleChoiceMessage : NetMessage { public bool Fighting; public string EnemyGroupId = string.Empty; public override void Write(NetWriter w) { w.Bool(Fighting); w.String(EnemyGroupId); } public override void Read(NetReader r) { Fighting = r.Bool(); EnemyGroupId = r.String(); } } [NetMessage(41)] public sealed class RemoteAnimationMessage : NetMessage { public string AnimationName = string.Empty; public override void Write(NetWriter w) { w.String(AnimationName); } public override void Read(NetReader r) { AnimationName = r.String(); } } [NetMessage(42)] public sealed class RemoteHitMessage : NetMessage { public float Damage; public float DamageBlocked; public float DamageShielded; public bool IsGrazed; public bool IsAccuracy; public int DamageType; public override void Write(NetWriter w) { w.Float(Damage); w.Float(DamageBlocked); w.Float(DamageShielded); w.Bool(IsGrazed); w.Bool(IsAccuracy); w.Int(DamageType); } public override void Read(NetReader r) { Damage = r.Float(); DamageBlocked = r.Float(); DamageShielded = r.Float(); IsGrazed = r.Bool(); IsAccuracy = r.Bool(); DamageType = r.Int(); } } [NetMessage(43)] public sealed class RemoteEmoteMessage : NetMessage { public int Emote; public override void Write(NetWriter w) { w.Int(Emote); } public override void Read(NetReader r) { Emote = r.Int(); } } [NetMessage(44)] public sealed class CuriosityFirepowerMessage : NetMessage { public int EnemyIndex; public int Firepower; public override void Write(NetWriter w) { w.Int(EnemyIndex); w.Int(Firepower); } public override void Read(NetReader r) { EnemyIndex = r.Int(); Firepower = r.Int(); } } [NetMessage(38)] public sealed class PlayerRevivedMessage : NetMessage { public int Hp; public override void Write(NetWriter w) { w.Int(Hp); } public override void Read(NetReader r) { Hp = r.Int(); } } [NetMessage(47)] public sealed class HandInspectMessage : NetMessage { public int TargetPlayerId; public override void Write(NetWriter w) { w.Int(TargetPlayerId); } public override void Read(NetReader r) { TargetPlayerId = r.Int(); } } [NetMessage(48)] public sealed class PlayerCardsMessage : NetMessage { public ManaGroup Mana; public bool HideDrawOrder; public List Cards = new List(); public override void Write(NetWriter w) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) MpCardMirror.WriteMana(w, Mana); w.Bool(HideDrawOrder); MpCardMirror.Write(w, Cards); } public override void Read(NetReader r) { //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) Mana = MpCardMirror.ReadMana(r); HideDrawOrder = r.Bool(); Cards = MpCardMirror.Read(r); } } [NetMessage(49)] public sealed class PlayerDeckMessage : NetMessage { public List Cards = new List(); public override void Write(NetWriter w) { MpCardMirror.Write(w, Cards); } public override void Read(NetReader r) { Cards = MpCardMirror.Read(r); } } [NetMessage(1, RelayedByHost = false)] public sealed class JoinRequestMessage : NetMessage { public int ProtocolVersion; public string PlayerName; public override void Write(NetWriter w) { w.Int(ProtocolVersion); w.String(PlayerName); } public override void Read(NetReader r) { ProtocolVersion = r.Int(); PlayerName = r.String(); } } [NetMessage(2, RelayedByHost = false)] public sealed class JoinAcceptedMessage : NetMessage { public int AssignedPlayerId; public override void Write(NetWriter w) { w.Int(AssignedPlayerId); } public override void Read(NetReader r) { AssignedPlayerId = r.Int(); } } [NetMessage(3, RelayedByHost = false)] public sealed class JoinRejectedMessage : NetMessage { public string Reason; public override void Write(NetWriter w) { w.String(Reason); } public override void Read(NetReader r) { Reason = r.String(); } } [NetMessage(4, RelayedByHost = false)] public sealed class PlayerListMessage : NetMessage { public List Players = new List(); public override void Write(NetWriter w) { w.Int(Players.Count); foreach (MpPlayer player in Players) { w.Int(player.Id); w.String(player.Name); w.Byte((byte)player.State); w.String(player.CharacterId); w.Int(player.PlayerTypeIndex); w.String(player.InitExhibitId); w.StringList(player.StartingDeck); w.Int(player.Hp); w.Int(player.MaxHp); w.Int(player.Money); w.Int(player.Power); } } public override void Read(NetReader r) { Players = new List(); int num = r.Int(); for (int i = 0; i < num; i++) { Players.Add(new MpPlayer { Id = r.Int(), Name = r.String(), State = (MpPlayerState)r.Byte(), CharacterId = r.String(), PlayerTypeIndex = r.Int(), InitExhibitId = r.String(), StartingDeck = new List(r.StringArray()), Hp = r.Int(), MaxHp = r.Int(), Money = r.Int(), Power = r.Int() }); } } } [NetMessage(10)] public sealed class PlayerReadyMessage : NetMessage { public string CharacterId; public int PlayerTypeIndex; public string InitExhibitId; public List Deck = new List(); public int Difficulty; public override void Write(NetWriter w) { w.String(CharacterId); w.Int(PlayerTypeIndex); w.String(InitExhibitId); w.StringList(Deck); w.Int(Difficulty); } public override void Read(NetReader r) { CharacterId = r.String(); PlayerTypeIndex = r.Int(); InitExhibitId = r.String(); Deck = new List(r.StringArray()); Difficulty = r.Int(); } } [NetMessage(11)] public sealed class RunStartMessage : NetMessage { public ulong Seed; public int Difficulty; public float EnemyHpScalePerExtraPlayer; public float[] EnemyHpEscalationByAct = new float[4]; public float ReviveHpFraction = 0.2f; public bool EnemyResilience = true; public override void Write(NetWriter w) { w.ULong(Seed); w.Int(Difficulty); w.Float(EnemyHpScalePerExtraPlayer); for (int i = 0; i < 4; i++) { w.Float(EnemyHpEscalationByAct[i]); } w.Float(ReviveHpFraction); w.Bool(EnemyResilience); } public override void Read(NetReader r) { Seed = r.ULong(); Difficulty = r.Int(); EnemyHpScalePerExtraPlayer = r.Float(); EnemyHpEscalationByAct = new float[4]; for (int i = 0; i < 4; i++) { EnemyHpEscalationByAct[i] = r.Float(); } ReviveHpFraction = r.Float(); EnemyResilience = r.Bool(); } } [NetMessage(15)] public sealed class ResumeReadyMessage : NetMessage { public ulong Seed; public int StageIndex; public int X; public int Y; public int Difficulty; public string CharacterId; public override void Write(NetWriter w) { w.ULong(Seed); w.Int(StageIndex); w.Int(X); w.Int(Y); w.Int(Difficulty); w.String(CharacterId); } public override void Read(NetReader r) { Seed = r.ULong(); StageIndex = r.Int(); X = r.Int(); Y = r.Int(); Difficulty = r.Int(); CharacterId = r.String(); } } [NetMessage(16)] public sealed class RunResumeMessage : NetMessage { public ulong Seed; public int Difficulty; public float EnemyHpScalePerExtraPlayer; public float[] EnemyHpEscalationByAct = new float[4]; public float ReviveHpFraction = 0.2f; public bool EnemyResilience = true; public string Note; public override void Write(NetWriter w) { w.ULong(Seed); w.Int(Difficulty); w.Float(EnemyHpScalePerExtraPlayer); for (int i = 0; i < 4; i++) { w.Float(EnemyHpEscalationByAct[i]); } w.Float(ReviveHpFraction); w.Bool(EnemyResilience); w.String(Note); } public override void Read(NetReader r) { Seed = r.ULong(); Difficulty = r.Int(); EnemyHpScalePerExtraPlayer = r.Float(); EnemyHpEscalationByAct = new float[4]; for (int i = 0; i < 4; i++) { EnemyHpEscalationByAct[i] = r.Float(); } ReviveHpFraction = r.Float(); EnemyResilience = r.Bool(); Note = r.String(); } } [NetMessage(17)] public sealed class BackToLobbyMessage : NetMessage { public override void Write(NetWriter w) { } public override void Read(NetReader r) { } } [NetMessage(18)] public sealed class RunStartCancelledMessage : NetMessage { public string Reason; public override void Write(NetWriter w) { w.String(Reason); } public override void Read(NetReader r) { Reason = r.String(); } } [NetMessage(14)] public sealed class LobbyDifficultyMessage : NetMessage { public int Difficulty; public override void Write(NetWriter w) { w.Int(Difficulty); } public override void Read(NetReader r) { Difficulty = r.Int(); } } [NetMessage(12)] public sealed class PlayerStatusMessage : NetMessage { public int Hp; public int MaxHp; public int Money; public int Power; public override void Write(NetWriter w) { w.Int(Hp); w.Int(MaxHp); w.Int(Money); w.Int(Power); } public override void Read(NetReader r) { Hp = r.Int(); MaxHp = r.Int(); Money = r.Int(); Power = r.Int(); } } [NetMessage(13)] public sealed class PlayerLeftMessage : NetMessage { public int PlayerId; public string Reason; public override void Write(NetWriter w) { w.Int(PlayerId); w.String(Reason); } public override void Read(NetReader r) { PlayerId = r.Int(); Reason = r.String(); } } [NetMessage(20)] public sealed class MapVoteMessage : NetMessage { public int StageIndex; public int X; public int Y; public int FromX = -1; public int FromY = -1; public int Decision; public override void Write(NetWriter w) { w.Int(StageIndex); w.Int(X); w.Int(Y); w.Int(FromX); w.Int(FromY); w.Int(Decision); } public override void Read(NetReader r) { StageIndex = r.Int(); X = r.Int(); Y = r.Int(); FromX = r.Int(); FromY = r.Int(); Decision = r.Int(); } } [NetMessage(21)] public sealed class MapCommitMessage : NetMessage { public int StageIndex; public int X; public int Y; public string AdventureType = string.Empty; public override void Write(NetWriter w) { w.Int(StageIndex); w.Int(X); w.Int(Y); w.String(AdventureType); } public override void Read(NetReader r) { StageIndex = r.Int(); X = r.Int(); Y = r.Int(); AdventureType = r.String(); } } [NetMessage(25)] public sealed class BossChosenMessage : NetMessage { public int StageIndex; public string BossId; public override void Write(NetWriter w) { w.Int(StageIndex); w.String(BossId); } public override void Read(NetReader r) { StageIndex = r.Int(); BossId = r.String(); } } [NetMessage(22)] public sealed class BarrierArriveMessage : NetMessage { public string BarrierId; public override void Write(NetWriter w) { w.String(BarrierId); } public override void Read(NetReader r) { BarrierId = r.String(); } } [NetMessage(23)] public sealed class BarrierReleaseMessage : NetMessage { public string BarrierId; public override void Write(NetWriter w) { w.String(BarrierId); } public override void Read(NetReader r) { BarrierId = r.String(); } } [NetMessage(24)] public sealed class NextStageMessage : NetMessage { public int StageIndex; public override void Write(NetWriter w) { w.Int(StageIndex); } public override void Read(NetReader r) { StageIndex = r.Int(); } } [NetMessage(45)] public sealed class BorderSensorMessage : NetMessage { public override void Write(NetWriter w) { } public override void Read(NetReader r) { } } [NetMessage(46)] public sealed class StationRestartMessage : NetMessage { public int Timing; public int StageIndex; public int X; public int Y; public override void Write(NetWriter w) { w.Int(Timing); w.Int(StageIndex); w.Int(X); w.Int(Y); } public override void Read(NetReader r) { Timing = r.Int(); StageIndex = r.Int(); X = r.Int(); Y = r.Int(); } } } namespace LBOLMP.Session.Battle { public sealed class MpBattleSeat { public int PlayerId; public string Name = string.Empty; public string CharacterId = string.Empty; public int Hp; public int MaxHp; public int Block; public int Shield; public int HandCount; public int DrawCount; public int DiscardCount; public int CompletedRound = -1; public bool Finished; public bool Alive = true; public bool Down; public bool Spectating; public List StatusEffects = new List(); public string LastCardId; public bool LastCardUpgraded; public float LastCardTime; public int LastCardTargetEnemyIndex; public bool IsOutOfPlay { get { if (Alive && !Finished && !Down) { return Spectating; } return true; } } public bool HasCompleted(int round) { if (!IsOutOfPlay) { return CompletedRound >= round; } return true; } } public static class MpBattleSync { private sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); public bool Equals(BattleAction x, BattleAction y) { return x == y; } public int GetHashCode(BattleAction obj) { return RuntimeHelpers.GetHashCode(obj); } } private static readonly Dictionary Seats = new Dictionary(); private static readonly HashSet Injected = new HashSet(ReferenceEqualityComparer.Instance); private static int _pendingInjections; private static float _nextStatusBroadcast; private static readonly HashSet _reportedSilent = new HashSet(); private const float GateFirstReportSeconds = 5f; private const float GateMaxReportSeconds = 30f; private static bool _atEndOfBattleGate; private static float _quietSince; private static float _nextSpentStatusSweep; private static readonly HashSet _seenAboveZero = new HashSet(); private static readonly HashSet _playerAppliedToEnemies = new HashSet(); private static float _nextEnemyVitals; private const float EnemyVitalsInterval = 1f; private static bool _reportedFinished; public static bool ShouldDeferPlayerInput { get { if (!InBattle) { return false; } if (_pendingInjections > 0) { return true; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val != null) { return val._debugActionQueue.Count > 0; } return false; } } public static bool ApplyingRemoteEffect { get; private set; } public static bool SpectatingOnly { get { if (!MpDownedPlayers.LocalDown) { return MpEventBattle.LocalSpectating; } return true; } } public static bool InBattle { get; private set; } public static ulong BattleSeed { get; private set; } public static int PlayerCountAtBattleStart { get; private set; } = 1; public static int CurrentRound { get { GameMaster instance = Singleton.Instance; int? obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; if (currentGameRun == null) { obj = null; } else { BattleController battle = currentGameRun.Battle; obj = ((battle != null) ? new int?(battle.RoundCounter) : ((int?)null)); } } return obj ?? (-1); } } public static bool LocalTurnComplete { get { MpBattleSeat seat = GetSeat(MpNet.LocalPlayerId); if (seat != null && InBattle) { return seat.CompletedRound >= CurrentRound; } return false; } } public static IEnumerable AllSeats => Seats.Values.OrderBy((MpBattleSeat s) => s.PlayerId); public static IEnumerable RemoteSeats => from s in Seats.Values where s.PlayerId != MpNet.LocalPlayerId orderby s.PlayerId select s; public static IEnumerable SilentSeats => from s in Seats.Values.Where(IsUnresponsive) orderby s.PlayerId select s.Name; public static IEnumerable SeatsStillPlaying { get { int round = CurrentRound; return from s in Seats.Values where !s.HasCompleted(round) && !IsUnresponsive(s) select s.Name; } } public static bool AtEndOfBattleGate { get { if (_atEndOfBattleGate) { return InBattle; } return false; } } public static bool AllSeatsFinished => Seats.Values.All((MpBattleSeat s) => s.Finished || IsUnresponsive(s)); public static IEnumerable SeatsStillFighting => from s in Seats.Values where !s.Finished && !IsUnresponsive(s) select s.Name; public static bool EndOfBattleGateOpen { get { if (!AllSeatsFinished && InBattle) { return !MpSession.IsActive; } return true; } } private static void SetWaitingHook(BattleController battle, bool subscribe) { if (battle == null) { return; } MpSafe.Run("SetWaitingHook", delegate { EventInfo eventInfo = typeof(BattleController).GetEvent("WaitingPlayerInput"); if (!(eventInfo == null)) { Action handler = OnBattleWaitingForInput; eventInfo.RemoveEventHandler(battle, handler); if (subscribe) { eventInfo.AddEventHandler(battle, handler); } } }); } private static void OnBattleWaitingForInput() { MpSafe.Run("OnBattleWaitingForInput", delegate { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val != null && val._debugActionQueue.Count == 0) { _pendingInjections = 0; } }); } private static T Inject(T action) where T : BattleAction { Injected.Add((BattleAction)(object)action); _pendingInjections++; return action; } public static bool ConsumeInjected(BattleAction action) { return Injected.Remove(action); } public static bool IsInjected(BattleAction action) { return Injected.Contains(action); } public static MpBattleSeat GetSeat(int playerId) { if (!Seats.TryGetValue(playerId, out var value)) { return null; } return value; } public static void RegisterHandlers() { MpNet.On(OnBattleStart); MpNet.On(OnTurnComplete); MpNet.On(OnEnemyDamage); MpNet.On(OnEnemyStatus); MpNet.On(OnRemoteCuriosity); MpNet.On(OnRemoteCardPlay); MpNet.On(OnRemoteAnimation); MpNet.On(OnRemoteHit); MpNet.On(OnRemoteEmote); MpNet.On(OnBattleStatus); MpNet.On(OnBattleFinished); MpNet.On(OnEnemyVitals); MpDownedPlayers.RegisterHandlers(); MpEventBattle.RegisterHandlers(); } public static void Reset() { Seats.Clear(); Injected.Clear(); _seenAboveZero.Clear(); _playerAppliedToEnemies.Clear(); _reportedSilent.Clear(); InBattle = false; _atEndOfBattleGate = false; _reportedFinished = false; BattleSeed = 0uL; PlayerCountAtBattleStart = 1; MpDownedPlayers.Reset(); MpEventBattle.Reset(); } public static void OnPlayerLeft(int playerId) { if (Seats.TryGetValue(playerId, out var value)) { value.Alive = false; value.Finished = true; value.CompletedRound = int.MaxValue; } } public static ulong StationSeed(GameRunController gameRun, string enemyGroupId) { object obj; if (gameRun == null) { obj = null; } else { GameMap currentMap = gameRun.CurrentMap; obj = ((currentMap != null) ? currentMap.VisitingNode : null); } MapNode val = (MapNode)obj; ulong runSeed = MpSession.RunSeed; ulong num = runSeed; int? obj2; if (gameRun == null) { obj2 = null; } else { Stage currentStage = gameRun.CurrentStage; obj2 = ((currentStage != null) ? new int?(currentStage.Index) : ((int?)null)); } int? num2 = obj2; runSeed = num ^ (ulong)(-7046029254386353131L * (num2.GetValueOrDefault() + 1)); runSeed ^= (ulong)(-4417276706812531889L * (((val != null) ? val.X : 0) + 1)); runSeed ^= (ulong)(1609587929392839161L * (((val != null) ? val.Y : 0) + 1)); runSeed ^= (ulong)(enemyGroupId?.GetHashCode() ?? 0); if (runSeed != 0L) { return runSeed; } return 1uL; } public static void BeginBattle(GameRunController gameRun, EnemyGroup enemyGroup) { if (!MpSession.IsActive) { return; } BattleSeed = StationSeed(gameRun, enemyGroup.Id); PlayerCountAtBattleStart = Math.Max(1, MpSession.ConnectedCount); InBattle = true; _atEndOfBattleGate = false; _reportedFinished = false; _pendingInjections = 0; SetWaitingHook(gameRun.Battle, subscribe: true); StartGameInterceptPatch.RepairUsOwner(gameRun.Player); MapSync.ClearVotes(); Seats.Clear(); bool active = MpEventBattle.Active; foreach (MpPlayer connectedPlayer in MpSession.ConnectedPlayers) { Seats[connectedPlayer.Id] = new MpBattleSeat { PlayerId = connectedPlayer.Id, Name = connectedPlayer.Name, CharacterId = connectedPlayer.CharacterId, Hp = connectedPlayer.Hp, MaxHp = connectedPlayer.MaxHp, Spectating = (active && !MpEventBattle.IsFighting(connectedPlayer.Id)) }; } MpPlugin.Log.LogInfo((object)$"Battle '{enemyGroup.Id}' starting, seed {BattleSeed}, {PlayerCountAtBattleStart} players"); } private static void OnBattleStart(BattleStartMessage message) { } public static void LeaveBattle() { GameMaster instance = Singleton.Instance; object battle; if (instance == null) { battle = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; battle = ((currentGameRun != null) ? currentGameRun.Battle : null); } SetWaitingHook((BattleController)battle, subscribe: false); InBattle = false; _atEndOfBattleGate = false; _reportedFinished = false; Seats.Clear(); Injected.Clear(); _seenAboveZero.Clear(); _playerAppliedToEnemies.Clear(); MpEventBattle.EndFight(); _pendingInjections = 0; } public static void SubmitLocalTurnComplete(int round) { MpBattleSeat seat = GetSeat(MpNet.LocalPlayerId); if (seat != null && seat.CompletedRound < round) { seat.CompletedRound = round; MpNet.Send(new TurnCompleteMessage { Round = round }); MpPlugin.Log.LogInfo((object)$"Player phase complete for round {round}; waiting at the enemy-turn gate"); } } private static void OnTurnComplete(TurnCompleteMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { MpBattleSeat seat = GetSeat(message.SenderId); if (seat != null && message.Round > seat.CompletedRound) { seat.CompletedRound = message.Round; } } } public static bool IsUnresponsive(MpBattleSeat seat) { if (seat != null) { return MpSession.IsUnresponsive(seat.PlayerId); } return false; } private static void AnnounceSilentSeats() { foreach (MpBattleSeat value in Seats.Values) { bool flag = IsUnresponsive(value); if (flag && _reportedSilent.Add(value.PlayerId)) { MpPlugin.Log.LogWarning((object)($"{value.Name} has not sent anything for {45f:F0}s; " + "the party will stop waiting for them. " + DescribeTurnState())); } else if (!flag && _reportedSilent.Remove(value.PlayerId)) { MpPlugin.Log.LogInfo((object)(value.Name + " is talking to us again")); } } } public static bool AllSeatsCompleted(int round) { if (!InBattle) { return true; } foreach (MpBattleSeat value in Seats.Values) { if (!value.HasCompleted(round) && !IsUnresponsive(value)) { return false; } } return true; } public static string DescribeTurnState() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; int currentRound = CurrentRound; List list = new List { "inBattle=" + InBattle, "localComplete=" + LocalTurnComplete, "waitingInput=" + (((val != null) ? val.IsWaitingPlayerInput.ToString() : null) ?? "n/a"), "round=" + currentRound, "allComplete=" + AllSeatsCompleted(currentRound) }; foreach (MpBattleSeat item in Seats.Values.OrderBy((MpBattleSeat s) => s.PlayerId)) { list.Add($"#{item.PlayerId}({item.Name}) completed={item.CompletedRound} " + $"alive={item.Alive} done={item.Finished} down={item.Down} " + $"silent={MpNet.SilenceFor(item.PlayerId):F0}s"); } return string.Join(", ", list); } public static IEnumerator WaitForEnemyTurn(BattleController battle) { if (!MpSession.IsActive || !InBattle || battle == null) { yield break; } int round = battle.RoundCounter; MpSafe.Run("SubmitTurnComplete", delegate { SubmitLocalTurnComplete(round); }); float waited = 0f; float reportInterval = 5f; float nextReport = reportInterval; while (!MpSafe.Run("TurnGate", () => battle.BattleShouldEnd || (AllSeatsCompleted(round) && battle._debugActionQueue.Count == 0), fallback: true)) { if (waited > nextReport) { reportInterval = Math.Min(reportInterval * 2f, 30f); nextReport = waited + reportInterval; MpPlugin.Log.LogInfo((object)("Still at the enemy-turn gate. " + DescribeTurnState())); } if (MpSafe.Run("TurnGateDrain", () => battle._debugActionQueue.Count > 0, fallback: false)) { yield return battle.ResolveDebugActions(); } waited += Time.unscaledDeltaTime; yield return null; } } public static IEnumerator WaitForEveryoneToFinish(BattleController battle) { if (!MpSession.IsActive || !InBattle || battle == null || !MpSafe.Run("EndGate", () => EnterEndOfBattleGate(battle), fallback: false)) { yield break; } _atEndOfBattleGate = true; try { float waited = 0f; float reportInterval = 5f; float nextReport = reportInterval; MpPlugin.Log.LogInfo((object)"Fight over here; waiting for the rest of the party to finish theirs"); while (!MpSafe.Run("EndGate", () => EndOfBattleGateOpen, fallback: true)) { if (waited > nextReport) { reportInterval = Math.Min(reportInterval * 2f, 30f); nextReport = waited + reportInterval; MpPlugin.Log.LogInfo((object)("Still waiting for the party to finish the fight. " + DescribeTurnState())); } waited += Time.unscaledDeltaTime; yield return null; } MpPlugin.Log.LogInfo((object)"Everyone has finished the fight; on to the rewards"); } finally { _atEndOfBattleGate = false; } } private static bool EnterEndOfBattleGate(BattleController battle) { ReportBattleFinished(battle.Player != null && ((Unit)battle.Player).IsAlive); return !EndOfBattleGateOpen; } public static void ReportEnemyDamage(EnemyUnit enemy, DamageInfo info, string gunName) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected I4, but got Unknown if (InBattle && !ApplyingRemoteEffect && MpSession.IsActive && !SpectatingOnly && !(((DamageInfo)(ref info)).Amount <= 0f)) { MpNet.Send(new EnemyDamageMessage { EnemyIndex = enemy.Index, Amount = ((DamageInfo)(ref info)).Amount, DamageType = (int)((DamageInfo)(ref info)).DamageType, IsAccuracy = ((DamageInfo)(ref info)).IsAccuracy, GunName = (string.IsNullOrEmpty(gunName) ? "Instant" : gunName) }); } } private static void OnEnemyDamage(EnemyDamageMessage message) { //IL_0097: 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_009c: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown if (message.SenderId == MpNet.LocalPlayerId) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; EnemyUnit val2 = FindEnemy(val, message.EnemyIndex); if (val2 != null && !MpPrivateEnemies.IsPrivate((Unit)(object)val2)) { Unit unit = (Unit)(object)MpAllyUnits.GetUnit(message.SenderId); if (unit == null) { MpPlugin.Log.LogWarning((object)$"No ally unit for player {message.SenderId}; skipping their hit"); return; } DamageInfo val3 = ((message.DamageType == 2) ? DamageInfo.Attack(message.Amount, message.IsAccuracy) : DamageInfo.HpLose(message.Amount, true)); DamageAction val4 = Inject(new DamageAction(unit, (Unit)(object)val2, val3, message.GunName, (GunType)0)); val.RequestDebugAction((BattleAction)(object)val4, "MP remote damage"); val.RequestDebugAction((BattleAction)(object)Inject(new StatisticalTotalDamageAction((IEnumerable)(object)new DamageAction[1] { val4 })), "MP remote damage stats"); MpAllyUnits.PlayShoot(message.SenderId, message.GunName, message.EnemyIndex); } } public static void ReportEnemyStatus(EnemyUnit enemy, StatusEffect effect, bool removing) { NotePlayerAppliedToEnemy((effect != null) ? ((GameEntity)effect).Id : null); if (InBattle && !ApplyingRemoteEffect && MpSession.IsActive && !SpectatingOnly) { MpNet.Send(new EnemyStatusMessage { EnemyIndex = enemy.Index, StatusId = ((GameEntity)effect).Id, HasLevel = effect.HasLevel, Level = (effect.HasLevel ? effect.Level : 0), HasDuration = effect.HasDuration, Duration = (effect.HasDuration ? effect.Duration : 0), Removing = removing }); } } private static void OnEnemyStatus(EnemyStatusMessage message) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown if (message.SenderId == MpNet.LocalPlayerId) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; EnemyUnit val2 = FindEnemy(val, message.EnemyIndex); if (val2 == null || MpPrivateEnemies.IsPrivate((Unit)(object)val2)) { return; } NotePlayerAppliedToEnemy(message.StatusId); ApplyingRemoteEffect = true; try { if (message.Removing) { StatusEffect val3 = ((IEnumerable)((Unit)val2).StatusEffects).FirstOrDefault((Func)((StatusEffect s) => ((GameEntity)s).Id == message.StatusId)); if (val3 != null) { val.RequestDebugAction((BattleAction)(object)Inject(new RemoveStatusEffectAction(val3, true, 0.1f)), "MP remote status remove"); } } else { StatusEffect val4 = Library.TryCreateStatusEffect(message.StatusId); if (val4 == null) { MpPlugin.Log.LogWarning((object)("Unknown status effect over the wire: " + message.StatusId)); } else { val.RequestDebugAction((BattleAction)(object)Inject(new ApplyStatusEffectAction(((object)val4).GetType(), (Unit)(object)val2, message.HasLevel ? new int?(message.Level) : ((int?)null), message.HasDuration ? new int?(message.Duration) : ((int?)null), (int?)null, (int?)null, 0f, true)), "MP remote status"); } } } catch (Exception ex) { MpPlugin.Log.LogError((object)("Failed to apply replicated enemy status: " + ex)); } finally { ApplyingRemoteEffect = false; } } public static void ReportCuriosity(EnemyUnit enemy, int firepower) { if (InBattle && !ApplyingRemoteEffect && MpSession.IsActive && !SpectatingOnly && firepower > 0) { MpNet.Send(new CuriosityFirepowerMessage { EnemyIndex = enemy.Index, Firepower = firepower }); } } private static void OnRemoteCuriosity(CuriosityFirepowerMessage message) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown if (message.SenderId == MpNet.LocalPlayerId) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; EnemyUnit enemy = FindEnemy(val, message.EnemyIndex); if (enemy == null || MpPrivateEnemies.IsPrivate((Unit)(object)enemy) || message.Firepower <= 0) { return; } val.RequestDebugAction((BattleAction)(object)Inject(new ApplyStatusEffectAction(typeof(Firepower), (Unit)(object)enemy, (int?)message.Firepower, (int?)null, (int?)null, (int?)null, 0f, true)), "MP ally ability card"); MpSafe.Run("CuriosityPulse", delegate { Curiosity statusEffect = ((Unit)enemy).GetStatusEffect(); if (statusEffect != null) { ((StatusEffect)statusEffect).NotifyActivating(); } }); } private static EnemyUnit FindEnemy(BattleController battle, int index) { if (battle == null) { return null; } return ((IEnumerable)battle.EnemyGroup).FirstOrDefault((Func)((EnemyUnit e) => e.Index == index)); } public static void ReportCardPlayed(string cardId, bool upgraded, int targetEnemyIndex) { if (InBattle && MpSession.IsActive) { MpNet.Send(new RemoteCardPlayMessage { CardId = cardId, Upgraded = upgraded, TargetEnemyIndex = targetEnemyIndex }); } } private static void OnRemoteCardPlay(RemoteCardPlayMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { MpBattleSeat seat = GetSeat(message.SenderId); if (seat != null) { seat.LastCardId = message.CardId; seat.LastCardUpgraded = message.Upgraded; seat.LastCardTargetEnemyIndex = message.TargetEnemyIndex; seat.LastCardTime = Time.unscaledTime; AllyCardPopup.Show(seat.PlayerId, message.CardId, message.Upgraded); MpAllyUnits.AimAt(seat.PlayerId, message.TargetEnemyIndex); } } } public static void ReportAnimation(string animationName) { if (InBattle && MpSession.IsActive && !string.IsNullOrEmpty(animationName)) { MpNet.Send(new RemoteAnimationMessage { AnimationName = animationName }); } } private static void OnRemoteAnimation(RemoteAnimationMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { MpAllyUnits.PlayAnimation(message.SenderId, message.AnimationName); } } public static void ReportHit(DamageInfo info) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected I4, but got Unknown if (InBattle && MpSession.IsActive) { MpNet.Send(new RemoteHitMessage { Damage = ((DamageInfo)(ref info)).Damage, DamageBlocked = ((DamageInfo)(ref info)).DamageBlocked, DamageShielded = ((DamageInfo)(ref info)).DamageShielded, IsGrazed = ((DamageInfo)(ref info)).IsGrazed, IsAccuracy = ((DamageInfo)(ref info)).IsAccuracy, DamageType = (int)((DamageInfo)(ref info)).DamageType }); } } private static void OnRemoteHit(RemoteHitMessage message) { //IL_0010: 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_0065: 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 (message.SenderId != MpNet.LocalPlayerId) { DamageInfo val = default(DamageInfo); ((DamageInfo)(ref val)).Damage = message.Damage; ((DamageInfo)(ref val)).DamageBlocked = message.DamageBlocked; ((DamageInfo)(ref val)).DamageShielded = message.DamageShielded; ((DamageInfo)(ref val)).IsGrazed = message.IsGrazed; ((DamageInfo)(ref val)).IsAccuracy = message.IsAccuracy; ((DamageInfo)(ref val)).DamageType = (DamageType)message.DamageType; DamageInfo info = val; MpAllyUnits.PlayHit(message.SenderId, info); } } private static void OnRemoteEmote(RemoteEmoteMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { MpEmotes.Play(message.SenderId, message.Emote); } } private static void TickInputDeferralWatchdog() { if (!InBattle || _pendingInjections <= 0) { _quietSince = 0f; return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val == null || !val.IsWaitingPlayerInput || val._debugActionQueue.Count != 0) { _quietSince = 0f; } else if (_quietSince == 0f) { _quietSince = Time.unscaledTime; } else if (Time.unscaledTime - _quietSince > 1.5f) { MpPlugin.Log.LogWarning((object)($"Clearing {_pendingInjections} stale replicated action(s); the battle has been idle. " + DescribeTurnState())); _pendingInjections = 0; _quietSince = 0f; } } internal static void NotePlayerAppliedToEnemy(string statusId) { if (!string.IsNullOrEmpty(statusId)) { _playerAppliedToEnemies.Add(statusId); } } private static void SweepSpentStatusEffects() { //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown if (!InBattle || Time.unscaledTime < _nextSpentStatusSweep) { return; } _nextSpentStatusSweep = Time.unscaledTime + 0.5f; GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val == null || !val.IsWaitingPlayerInput || val._debugActionQueue.Count > 0) { return; } foreach (EnemyUnit item in val.EnemyGroup) { if (!((Unit)item).IsAlive) { continue; } foreach (StatusEffect item2 in ((Unit)item).StatusEffects.ToList()) { if (item2.HasLevel && !item2.HasDuration && _playerAppliedToEnemies.Contains(((GameEntity)item2).Id)) { if (item2.Level > 0) { _seenAboveZero.Add(item2); } else if (_seenAboveZero.Contains(item2)) { MpPlugin.Log.LogWarning((object)("'" + ((GameEntity)item2).Id + "' left spent at level 0 on " + ((GameEntity)item).Id + "; removing it")); _seenAboveZero.Remove(item2); val.RequestDebugAction((BattleAction)new RemoveStatusEffectAction(item2, true, 0.1f), "MP spent status cleanup"); } } } } } private static bool BattleIsSettled(BattleController battle) { if (battle == null || battle._debugActionQueue.Count > 0) { return false; } if (!battle.IsWaitingPlayerInput && !SpectatingOnly) { return battle.BattleShouldEnd; } return true; } private static void BroadcastEnemyVitals() { if (!MpNet.IsHost || !InBattle || Time.unscaledTime < _nextEnemyVitals) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (!BattleIsSettled(val)) { return; } _nextEnemyVitals = Time.unscaledTime + 1f; List list = new List(); foreach (EnemyUnit item in val.EnemyGroup) { if (!MpPrivateEnemies.IsPrivate((Unit)(object)item)) { list.Add(item.Index); list.Add(((Unit)item).IsAlive ? ((Unit)item).Hp : 0); list.Add(((Unit)item).Block); list.Add(((Unit)item).Shield); } } if (list.Count > 0) { MpNet.Send(new EnemyVitalsMessage { Vitals = list }); } } private static void OnEnemyVitals(EnemyVitalsMessage message) { if (message.SenderId == MpNet.LocalPlayerId || !InBattle) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController battle = (BattleController)obj; if (!BattleIsSettled(battle)) { return; } for (int i = 0; i + 3 < message.Vitals.Count; i += 4) { EnemyUnit val = FindEnemy(battle, message.Vitals[i]); if (val != null && ((Unit)val).IsAlive && !MpPrivateEnemies.IsPrivate((Unit)(object)val)) { CorrectEnemy(battle, val, message.Vitals[i + 1], message.Vitals[i + 2], message.Vitals[i + 3]); } } } private static void CorrectEnemy(BattleController battle, EnemyUnit enemy, int hp, int block, int shield) { //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_006d: Expected O, but got Unknown //IL_0193: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Clamp(hp, 0, ((Unit)enemy).MaxHp); if (num <= 0) { MpPlugin.Log.LogInfo((object)(((GameEntity)enemy).Id + " is already down on the host; finishing it here")); battle.RequestDebugAction((BattleAction)(object)Inject(new DamageAction((Unit)null, (Unit)(object)enemy, DamageInfo.HpLose((float)((Unit)enemy).Hp, false), "Instant", (GunType)0)), "MP enemy correction"); return; } int num2 = num - ((Unit)enemy).Hp; bool flag = ((Unit)enemy).Block != block || ((Unit)enemy).Shield != shield; if (num2 == 0 && !flag) { return; } MpPlugin.Log.LogInfo((object)($"Correcting {((GameEntity)enemy).Id} to the host: hp {((Unit)enemy).Hp}->{num}, " + $"block {((Unit)enemy).Block}->{block}, shield {((Unit)enemy).Shield}->{shield}")); ((Unit)enemy).Hp = num; ((Unit)enemy).Block = Mathf.Max(0, block); ((Unit)enemy).Shield = Mathf.Max(0, shield); UnitView val = MpSafe.Run("EnemyCorrectionView", () => GameDirector.GetUnit((Unit)(object)enemy), null); if ((Object)(object)val == (Object)null) { return; } if (num2 < 0) { val.OnDamageReceived(DamageInfo.HpLose((float)(-num2), true)); } else if (num2 > 0) { val.OnHealingReceived(num2); } if (flag) { val.UpdateShieldColliders(); UnitStatusWidget statusWidget = val._statusWidget; if (statusWidget != null) { statusWidget.OnBlockShieldChanged(); } } } public static void Update() { if (!MpSession.IsActive) { return; } TickInputDeferralWatchdog(); AnnounceSilentSeats(); SweepSpentStatusEffects(); MpSafe.Run("BroadcastEnemyVitals", BroadcastEnemyVitals); if (Time.unscaledTime < _nextStatusBroadcast) { return; } _nextStatusBroadcast = Time.unscaledTime + 0.2f; GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); PlayerUnit val2 = ((val != null) ? val.Player : null); if (val2 == null) { return; } BattleController battle = val.Battle; List list = new List(); foreach (StatusEffect statusEffect in ((Unit)val2).StatusEffects) { int num = (statusEffect.HasLevel ? statusEffect.Level : (-1)); int num2 = (statusEffect.HasDuration ? statusEffect.Duration : (-1)); object[] obj = new object[4] { ((GameEntity)statusEffect).Id, num, num2, null }; Card sourceCard = statusEffect.SourceCard; obj[3] = ((sourceCard != null) ? ((GameEntity)sourceCard).Id : null) ?? string.Empty; list.Add(string.Format("{0}:{1}:{2}:{3}", obj)); } MpNet.Send(new BattleStatusMessage { Hp = ((Unit)val2).Hp, MaxHp = ((Unit)val2).MaxHp, Block = ((Unit)val2).Block, Shield = ((Unit)val2).Shield, HandCount = ((battle != null) ? battle.HandZone.Count : 0), DrawCount = ((battle != null) ? battle.DrawZone.Count : 0), DiscardCount = ((battle != null) ? battle.DiscardZone.Count : 0), CompletedRound = ((!InBattle) ? (-1) : (GetSeat(MpNet.LocalPlayerId)?.CompletedRound ?? (-1))), Finished = (InBattle && (GetSeat(MpNet.LocalPlayerId)?.Finished ?? false)), StatusEffects = list }); } private static void OnBattleStatus(BattleStatusMessage message) { if (message.SenderId == MpNet.LocalPlayerId) { return; } MpBattleSeat seat = GetSeat(message.SenderId); if (seat != null) { seat.Hp = message.Hp; seat.MaxHp = message.MaxHp; seat.Block = message.Block; seat.Shield = message.Shield; seat.HandCount = message.HandCount; seat.DrawCount = message.DrawCount; seat.DiscardCount = message.DiscardCount; seat.Alive = message.Hp > 0; if (message.CompletedRound > seat.CompletedRound) { seat.CompletedRound = message.CompletedRound; } if (message.Finished) { seat.Finished = true; } seat.StatusEffects = message.StatusEffects; MpAllyUnits.SyncVitals(seat); } } public static void ReportBattleFinished(bool survived) { if (MpSession.IsActive && !_reportedFinished) { _reportedFinished = true; MpBattleSeat seat = GetSeat(MpNet.LocalPlayerId); if (seat != null) { seat.Finished = true; seat.Alive = survived; seat.CompletedRound = int.MaxValue; } MpNet.Send(new BattleFinishedMessage { Survived = survived }); } } private static void OnBattleFinished(BattleFinishedMessage message) { MpBattleSeat seat = GetSeat(message.SenderId); if (seat != null) { seat.Finished = true; seat.Alive = message.Survived; seat.CompletedRound = int.MaxValue; } } public static ulong SeedForEnemyMove(int enemyIndex, int round) { ulong battleSeed = BattleSeed; battleSeed ^= (ulong)(-7046029254386353131L * (enemyIndex + 1)); battleSeed ^= (ulong)(-4658895280553007687L * (round + 1)); if (battleSeed != 0L) { return battleSeed; } return 1uL; } } public static class MpDownedPlayers { private static bool _allowRealDeath; private static GameEventHandler _dyingHandler; private static PlayerUnit _hookedPlayer; private const float GateFirstReportSeconds = 5f; private const float GateMaxReportSeconds = 30f; private static bool _effectsCleared; public static bool LocalDown { get; private set; } public static bool OutOfFight { get { if (!LocalDown) { return MpEventBattle.LocalSpectating; } return true; } } public static string[] DownedNames => (from s in MpBattleSync.AllSeats where s.Down select s.Name).ToArray(); public static void RegisterHandlers() { MpNet.On(OnPlayerDown); MpNet.On(OnPlayerRevived); } public static void Reset() { Unhook(); LocalDown = false; _allowRealDeath = false; _effectsCleared = false; } public static void Hook(BattleController battle) { Unhook(); LocalDown = false; _allowRealDeath = false; _effectsCleared = false; PlayerUnit val = ((battle != null) ? battle.Player : null); if (!MpSession.IsActive || val == null) { return; } _dyingHandler = delegate(DieEventArgs args) { MpSafe.Run("MpDownedPlayers.OnDying", delegate { OnDying(args); }); }; ((Unit)val).Dying.AddHandler(_dyingHandler, (GameEventPriority)int.MaxValue); _hookedPlayer = val; } public static void Unhook() { if (_hookedPlayer != null && _dyingHandler != null) { MpSafe.Run("MpDownedPlayers.Unhook", delegate { ((Unit)_hookedPlayer).Dying.RemoveHandler(_dyingHandler, (GameEventPriority)int.MaxValue); }); } _hookedPlayer = null; _dyingHandler = null; } private static void OnDying(DieEventArgs args) { if (_allowRealDeath || !MpSession.IsActive || !MpBattleSync.InBattle || args.Unit == null || !(args.Unit is PlayerUnit) || (object)args.Unit != _hookedPlayer || ((GameEventArgs)args).IsCanceled) { return; } if (!((GameEventArgs)args).CanCancel) { MpPlugin.Log.LogWarning((object)"Something insists this death cannot be cancelled; the run ends here"); return; } if (!AnyOtherSeatCanFight()) { MpPlugin.Log.LogInfo((object)"The whole party is down; this death stands"); AnnounceDown(); return; } ((GameEventArgs)args).CancelBy((GameEntity)(object)args.Unit); if (!LocalDown) { LocalDown = true; MpPlugin.Log.LogInfo((object)"Knocked out; spectating until the party settles this fight"); AnnounceDown(); } } private static void AnnounceDown() { MpBattleSeat seat = MpBattleSync.GetSeat(MpNet.LocalPlayerId); if (seat != null) { if (seat.Down) { return; } seat.Down = true; } MpNet.Send(new PlayerDownMessage()); } private static bool CanFight(MpBattleSeat seat) { if (seat.Down) { return false; } if (seat.Finished) { return seat.Alive; } return true; } private static bool AnyOtherSeatCanFight() { return MpBattleSync.AllSeats.Any((MpBattleSeat s) => s.PlayerId != MpNet.LocalPlayerId && CanFight(s)); } private static void OnPlayerDown(PlayerDownMessage message) { MpBattleSeat seat = MpBattleSync.GetSeat(message.SenderId); if (seat != null) { seat.Down = true; MpPlugin.Log.LogInfo((object)(seat.Name + " was knocked out")); } } private static void OnPlayerRevived(PlayerRevivedMessage message) { MpAllyUnits.Revive(message.SenderId); MpBattleSeat seat = MpBattleSync.GetSeat(message.SenderId); if (seat != null) { seat.Down = false; seat.Hp = message.Hp; seat.Alive = message.Hp > 0; MpPlugin.Log.LogInfo((object)$"{seat.Name} got back up on {message.Hp} HP"); } } public static IEnumerator WaitWhileDown(BattleController battle) { if (!MpSession.IsActive || !MpBattleSync.InBattle || battle == null || !OutOfFight) { yield break; } MpPlugin.Log.LogInfo((object)(LocalDown ? "Down, so taking no turn; watching the rest of the fight" : "Not in this fight, so taking no turn; watching it instead")); float waited = 0f; float reportInterval = 5f; float nextReport = reportInterval; while (!MpSafe.Run("DownedGate", () => ShouldStopWaiting(battle), fallback: true)) { if (waited > nextReport) { reportInterval = Mathf.Min(reportInterval * 2f, 30f); nextReport = waited + reportInterval; MpPlugin.Log.LogInfo((object)("Still down, still watching. " + MpBattleSync.DescribeTurnState())); } if (MpSafe.Run("DownedGateDrain", () => battle._debugActionQueue.Count > 0, fallback: false)) { yield return battle.ResolveDebugActions(); } waited += Time.unscaledDeltaTime; yield return null; } } private static bool ShouldStopWaiting(BattleController battle) { if (!battle.BattleShouldEnd && OutOfFight && MpBattleSync.InBattle) { return !MpSession.IsActive; } return true; } public static void Tick() { if (!MpSession.IsActive || !OutOfFight) { return; } MpSafe.Run("MpDownedPlayers.Tick", delegate { GameMaster instance = Singleton.Instance; object battle; if (instance == null) { battle = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; battle = ((currentGameRun != null) ? currentGameRun.Battle : null); } MpPrivateEnemies.Dismiss((BattleController)battle); if (LocalDown) { ClearStatusEffectsOnce(); if (EndRunIfPartyWiped()) { return; } } if (!EndFightIfEveryFighterIsDown()) { EndBattleIfEveryoneElseHasWon(); } }); } private static void ClearStatusEffectsOnce() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown if (_effectsCleared) { return; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; PlayerUnit val2 = ((val != null) ? val.Player : null); if (val2 == null) { return; } _effectsCleared = true; List list = ((Unit)val2).StatusEffects.ToList(); foreach (StatusEffect item in list) { val.RequestDebugAction((BattleAction)new RemoveStatusEffectAction(item, true, 0.1f), "MP downed cleanup"); } if (list.Count > 0) { MpPlugin.Log.LogInfo((object)$"Clearing {list.Count} status effect(s) off a downed player"); } } private static bool EndRunIfPartyWiped() { //IL_0087: 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_00a1: Expected O, but got Unknown GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val == null || !MpBattleSync.InBattle || _allowRealDeath) { return false; } if (val.BattleShouldEnd) { return false; } if (MpBattleSync.AllSeats.Any(CanFight)) { return false; } MpPlugin.Log.LogInfo((object)"The whole party is down; ending the run"); _allowRealDeath = true; val.RequestDebugAction((BattleAction)new DamageAction((Unit)null, (Unit)(object)val.Player, DamageInfo.HpLose(1f, false), "Instant", (GunType)0), "MP party wipe"); return true; } private static bool EndFightIfEveryFighterIsDown() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val == null || !MpBattleSync.InBattle || val.BattleShouldEnd) { return false; } if (!MpEventBattle.Active) { return false; } List source = MpBattleSync.AllSeats.ToList(); bool flag = source.Any((MpBattleSeat s) => s.Spectating); bool flag2 = source.Any((MpBattleSeat s) => !s.Spectating && CanFight(s)); if (!flag || flag2) { return false; } MpPlugin.Log.LogInfo((object)"Everyone who took this fight is down; ending it without ending the run"); if (MpEventBattle.IsFighting(MpNet.LocalPlayerId)) { MpEventBattle.AbortLocalEvent(); } val.InstantWin(); return true; } private static void EndBattleIfEveryoneElseHasWon() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.Battle : null); } BattleController val = (BattleController)obj; if (val != null && MpBattleSync.InBattle && !val.BattleShouldEnd) { List list = MpBattleSync.AllSeats.Where((MpBattleSeat s) => s.PlayerId != MpNet.LocalPlayerId).ToList(); if (list.Count != 0 && list.All((MpBattleSeat s) => s.Finished) && list.Any((MpBattleSeat s) => s.Alive)) { MpPlugin.Log.LogWarning((object)"The party has already won but enemies are still standing here; closing the fight out"); val.InstantWin(); } } } public static void ReviveIfWon(GameRunController gameRun) { if (!LocalDown) { return; } GameRunController obj = gameRun; PlayerUnit val = ((obj != null) ? obj.Player : null); if (val == null) { return; } if (!((Unit)val).IsAlive) { LocalDown = false; return; } LocalDown = false; MpBattleSeat seat = MpBattleSync.GetSeat(MpNet.LocalPlayerId); if (seat != null) { seat.Down = false; } if (((Unit)val).Hp > 0) { MpPlugin.Log.LogInfo((object)$"Already back up on {((Unit)val).Hp} HP; no revival needed"); MpNet.Send(new PlayerRevivedMessage { Hp = ((Unit)val).Hp }); return; } int num = Mathf.Max(1, Mathf.RoundToInt((float)((Unit)val).MaxHp * MpSession.ReviveHpFraction)); num = Mathf.Min(num, ((Unit)val).MaxHp); gameRun.SetHpAndMaxHp(num, ((Unit)val).MaxHp, true); MpSafe.Run("MpDownedPlayers.AddRegret", delegate { gameRun.AddDeckCard((Card)(object)Library.CreateCard(), true, (VisualSourceData)null); }); MpPlugin.Log.LogInfo((object)$"The party won; back up on {num} HP, and carrying a Regret for it"); MpNet.Send(new PlayerRevivedMessage { Hp = num }); } } public static class MpEventBattle { private struct Choice { public bool Fighting; public string EnemyGroupId; } private static readonly Dictionary Choices = new Dictionary(); private static bool _answered; private static bool _fightResolved; public static bool LocalEventAborted { get; private set; } public static bool ModRequestedBattle { get; private set; } public static bool LocalSpectating { get; private set; } public static bool AllAnswered => MpSession.ConnectedPlayers.All((MpPlayer p) => Choices.ContainsKey(p.Id)); public static bool AnyFighting => Choices.Values.Any((Choice c) => c.Fighting); public static int FighterCount => Mathf.Max(1, Choices.Values.Count((Choice c) => c.Fighting)); public static bool Active { get { if (_answered) { return AnyFighting; } return false; } } public static string EnemyGroupId => (from pair in Choices where pair.Value.Fighting && !string.IsNullOrEmpty(pair.Value.EnemyGroupId) orderby pair.Key select pair.Value.EnemyGroupId).FirstOrDefault() ?? string.Empty; public static IEnumerable StillChoosing => from p in MpSession.ConnectedPlayers where !Choices.ContainsKey(p.Id) select p.Name; public static void RegisterHandlers() { MpNet.On(OnChoice); } public static void Reset() { Choices.Clear(); _answered = false; _fightResolved = false; LocalSpectating = false; ModRequestedBattle = false; LocalEventAborted = false; } public static bool IsFighting(int playerId) { if (Choices.TryGetValue(playerId, out var value)) { return value.Fighting; } return false; } public static void Announce(bool fighting, string enemyGroupId) { if (!_answered && !_fightResolved) { _answered = true; Choice value = new Choice { Fighting = fighting, EnemyGroupId = (enemyGroupId ?? string.Empty) }; Choices[MpNet.LocalPlayerId] = value; MpPlugin.Log.LogInfo((object)(fighting ? ("Taking the event's fight against '" + enemyGroupId + "'; waiting for the rest of the party to choose") : "Declining the event's fight; waiting for the rest of the party to choose")); MpNet.Send(new EventBattleChoiceMessage { Fighting = fighting, EnemyGroupId = value.EnemyGroupId }); } } private static void OnChoice(EventBattleChoiceMessage message) { if (message.SenderId != MpNet.LocalPlayerId) { Choices[message.SenderId] = new Choice { Fighting = message.Fighting, EnemyGroupId = message.EnemyGroupId }; string text = MpSession.Players.FirstOrDefault((MpPlayer p) => p.Id == message.SenderId)?.Name ?? message.SenderId.ToString(); MpPlugin.Log.LogInfo((object)(message.Fighting ? (text + " is taking the event's fight") : (text + " is sitting the event's fight out"))); } } public static IEnumerator WaitForEveryone() { if (!MpSession.IsActive || _fightResolved) { yield break; } float waited = 0f; float reportInterval = 5f; float nextReport = reportInterval; while (!MpSafe.Run("EventBattleGate", () => AllAnswered || !MpSession.IsActive, fallback: true)) { if (waited > nextReport) { reportInterval = Mathf.Min(reportInterval * 2f, 30f); nextReport = waited + reportInterval; MpPlugin.Log.LogInfo((object)("Still waiting on the party's event choices. " + Describe())); } waited += Time.unscaledDeltaTime; yield return null; } MpPlugin.Log.LogInfo((object)(AnyFighting ? $"Everyone has chosen; {FighterCount} player(s) fighting '{EnemyGroupId}'" : "Everyone has chosen; nobody took the fight")); } public static string Describe() { return "waiting on: " + string.Join(", ", StillChoosing.DefaultIfEmpty("nobody")); } public static void SettleLocalRole() { LocalSpectating = AnyFighting && !IsFighting(MpNet.LocalPlayerId); if (LocalSpectating) { MpPlugin.Log.LogInfo((object)"Sitting this one out; watching the party fight"); } } public static void BeginModRequest() { ModRequestedBattle = true; } public static void EndModRequest() { ModRequestedBattle = false; } public static void ClearLocalRole() { LocalSpectating = false; } public static void EndFight() { Choices.Clear(); _answered = false; _fightResolved = true; } public static void AbortLocalEvent() { if (!LocalEventAborted) { LocalEventAborted = true; MpPlugin.Log.LogInfo((object)"The event's fight was lost; the rest of this event is forfeit"); } } public static void ClearEventAbort() { LocalEventAborted = false; } } public static class MpPrivateEnemies { private sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); public bool Equals(EnemyUnit x, EnemyUnit y) { return x == y; } public int GetHashCode(EnemyUnit obj) { return RuntimeHelpers.GetHashCode(obj); } } private static readonly HashSet Private = new HashSet(ReferenceEqualityComparer.Instance); private static bool _dismissed; public static void Reset() { Private.Clear(); _dismissed = false; } public static void OnSpawning(EnemyUnit spawner, EnemyUnit spawned) { if (spawned != null && (spawner is Siji || IsPrivate((Unit)(object)spawner))) { Private.Add(spawned); } } public static bool IsPrivate(Unit unit) { EnemyUnit val = (EnemyUnit)(object)((unit is EnemyUnit) ? unit : null); if (val != null) { return Private.Contains(val); } return false; } public static void Dismiss(BattleController battle) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown if (_dismissed || battle == null || Private.Count == 0) { return; } _dismissed = true; foreach (EnemyUnit item in ((IEnumerable)battle.EnemyGroup).ToList()) { if (IsPrivate((Unit)(object)item) && ((Unit)item).IsAlive && !((Unit)item).IsEscaped) { MpPlugin.Log.LogInfo((object)("Out of the fight; dismissing private enemy " + ((GameEntity)item).Id)); battle.RequestDebugAction((BattleAction)new EscapeAction((Unit)(object)item), "MP dismissing a private enemy"); } } } } } namespace LBOLMP.Patches { internal static class LocalPlayerView { internal static bool Is(UnitView view) { GameDirector instance = Singleton.Instance; if ((Object)(object)view != (Object)null && (Object)(object)instance != (Object)null) { return view == instance.PlayerUnitView; } return false; } } [HarmonyPatch(typeof(GameDirector), "PlayerDebutAnimation")] public static class AllyDebutPatch { [HarmonyPostfix] private static void Postfix() { MpAllyUnits.PlayDebut(); } } [HarmonyPatch(typeof(GameDirector), "HidePlayer")] public static class AllyHidePatch { [HarmonyPostfix] private static void Postfix() { MpAllyUnits.SetHidden(hidden: true, withStatus: false); } } [HarmonyPatch(typeof(GameDirector), "RevealPlayer")] public static class AllyRevealPatch { [HarmonyPostfix] private static void Postfix(bool withStatus) { MpAllyUnits.SetHidden(hidden: false, withStatus); } } [HarmonyPatch(typeof(UnitView), "PlayAnimation")] public static class AllyCardAnimationPatch { private static readonly HashSet NotACardBeingPlayed = new HashSet(StringComparer.OrdinalIgnoreCase) { "debut", "hit", "graze", "guard", "crash", "die" }; [HarmonyPostfix] private static void Postfix(UnitView __instance, string animationName) { MpSafe.Run("AllyCardAnimationPatch", delegate { if (!string.IsNullOrEmpty(animationName) && !NotACardBeingPlayed.Contains(animationName) && LocalPlayerView.Is(__instance)) { MpBattleSync.ReportAnimation(animationName); } }); } } [HarmonyPatch(typeof(UnitView), "DefendAnimation")] public static class AllyDefendAnimationPatch { [HarmonyPostfix] private static void Postfix(UnitView __instance) { MpSafe.Run("AllyDefendAnimationPatch", delegate { if (LocalPlayerView.Is(__instance)) { MpBattleSync.ReportAnimation("defend"); } }); } } [HarmonyPatch(typeof(EnemyGroupEntry), "Generate")] public static class EnemyHpRollPatch { [HarmonyPrefix] private static void Prefix(EnemyGroupEntry __instance, GameRunController gameRun, out RandomGen __state) { __state = null; RandomGen saved = null; MpSafe.Run("EnemyHpRollPatch", delegate { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (MpSession.IsActive && gameRun != null) { saved = gameRun.EnemyBattleRng; ulong num = MpBattleSync.StationSeed(gameRun, __instance.Id) ^ 0x7F4A7C159E3779B9L; gameRun.EnemyBattleRng = new RandomGen(num); } }); __state = saved; } [HarmonyPostfix] private static void Postfix(GameRunController gameRun, RandomGen __state) { if (__state != null && gameRun != null) { MpSafe.Run("EnemyHpRollPatch.Restore", delegate { gameRun.EnemyBattleRng = __state; }); } } } [HarmonyPatch(typeof(EnemyUnit), "EnterGameRun")] public static class EnemyHpScalingPatch { [HarmonyPostfix] private static void Postfix(EnemyUnit __instance) { MpSafe.Run("EnemyHpScalingPatch", delegate { if (MpEnemyScaling.ExtraFighters > 0 && !MpPrivateEnemies.IsPrivate((Unit)(object)__instance)) { int num = Mathf.Max(1, Mathf.RoundToInt((float)((Unit)__instance).MaxHp * MpEnemyScaling.MultiplierFor((Unit)(object)__instance))); ((Unit)__instance).SetMaxHp(num, num); } }); } } [HarmonyPatch(typeof(EnemyUnit), "UpdateTurnMoves")] public static class EnemyIntentSeedPatch { [HarmonyPrefix] private static void Prefix(EnemyUnit __instance) { MpSafe.Run("EnemyIntentSeedPatch", delegate { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown if (MpSession.IsActive && MpBattleSync.InBattle) { BattleController battle = ((Unit)__instance).Battle; GameRunController val = ((battle != null) ? battle.GameRun : null); if (val != null) { int roundCounter = ((Unit)__instance).Battle.RoundCounter; ulong num = MpBattleSync.SeedForEnemyMove(__instance.Index, roundCounter); val.EnemyMoveRng = new RandomGen(num); val.EnemyBattleRng = new RandomGen(num ^ 0x517CC1B727220A95L); } } }); } } [HarmonyPatch(typeof(GameDirector), "GetUnit")] public static class AllyViewLookupPatch { [HarmonyPostfix] private static void Postfix(Unit unit, ref UnitView __result) { if (!((Object)(object)__result != (Object)null) && unit != null) { __result = MpSafe.Run("AllyViewLookupPatch", () => MpAllyUnits.GetView(unit), null); } } } [HarmonyPatch(typeof(GameDirector), "MasterTick")] public static class AllyViewTickPatch { [HarmonyPostfix] private static void Postfix() { MpAllyUnits.TickViews(); } } [HarmonyPatch(typeof(GameDirector), "OnGunHit")] public static class AllyGunHitPatch { [HarmonyPrefix] private static bool Prefix() { return MpSafe.Run("AllyGunHitPatch", () => !MpAllyUnits.TryHandleAllyGunHit(), fallback: true); } } [HarmonyPatch(typeof(GameRunController))] public static class BattleLifecyclePatch { [HarmonyPostfix] [HarmonyPatch("EnterBattle")] private static void AfterEnterBattle(GameRunController __instance, EnemyGroup enemyGroup) { MpSafe.Run("EnterBattle", delegate { MpPrivateEnemies.Reset(); MpBattleSync.BeginBattle(__instance, enemyGroup); EnemyDamageHook.HookAll(__instance.Battle); PlayerDamageHook.Hook(__instance.Battle); MpDownedPlayers.Hook(__instance.Battle); }); } [HarmonyPrefix] [HarmonyPatch("LeaveBattle")] private static void BeforeLeaveBattle(GameRunController __instance) { MpSafe.Run("LeaveBattle", delegate { EnemyDamageHook.UnhookAll(); PlayerDamageHook.Unhook(); MpPrivateEnemies.Reset(); if (!MpBattleSync.InBattle) { MpDownedPlayers.Unhook(); } else { MpDownedPlayers.ReviveIfWon(__instance); MpDownedPlayers.Unhook(); MpBattleSync.ReportBattleFinished(__instance.Player != null && ((Unit)__instance.Player).IsAlive); MpBattleSync.LeaveBattle(); } }); } } [HarmonyPatch(typeof(BattleController), "EnemyTurnFlow")] public static class EnemyTurnBarrierPatch { [HarmonyPostfix] private static void Postfix(BattleController __instance, ref IEnumerator __result) { IEnumerator enumerator = __result; if (enumerator != null) { __result = Gated(__instance, enumerator); } } private static IEnumerator Gated(BattleController battle, IEnumerator enemyTurn) { yield return MpBattleSync.WaitForEnemyTurn(battle); yield return enemyTurn; } } [HarmonyPatch(typeof(BattleController), "Flow")] public static class BattleEndBarrierPatch { [HarmonyPostfix] private static void Postfix(BattleController __instance, ref IEnumerator __result) { IEnumerator enumerator = __result; if (enumerator != null) { __result = Gated(__instance, enumerator); } } private static IEnumerator Gated(BattleController battle, IEnumerator flow) { yield return flow; yield return MpBattleSync.WaitForEveryoneToFinish(battle); } } [HarmonyPatch(typeof(BattleController), "PlayerTurnFlow")] public static class DownedPlayerTurnPatch { [HarmonyPostfix] private static void Postfix(BattleController __instance, ref IEnumerator __result) { IEnumerator enumerator = __result; if (enumerator != null) { __result = Gated(__instance, enumerator); } } private static IEnumerator Gated(BattleController battle, IEnumerator playerTurn) { yield return MpDownedPlayers.WaitWhileDown(battle); yield return playerTurn; } } [HarmonyPatch(typeof(BattleController))] public static class CardUsePatch { [HarmonyPostfix] [HarmonyPatch("RequestUseCard")] private static void AfterUseCard(Card card, UnitSelector selector) { MpSafe.Run("AfterUseCard", delegate { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (MpSession.IsActive && MpBattleSync.InBattle && card != null) { int targetEnemyIndex = -1; if (selector != null && (int)selector.Type == 1) { EnemyUnit selectedEnemy = selector.SelectedEnemy; targetEnemyIndex = ((selectedEnemy != null) ? selectedEnemy.Index : (-1)); } MpBattleSync.ReportCardPlayed(((GameEntity)card).Id, card.IsUpgraded, targetEnemyIndex); } }); } } public static class EnemyDamageHook { private static readonly List<(EnemyUnit Enemy, GameEventHandler Handler)> Hooked = new List<(EnemyUnit, GameEventHandler)>(); public static void HookAll(BattleController battle) { UnhookAll(); if (!MpSession.IsActive || battle == null) { return; } foreach (EnemyUnit item in battle.EnemyGroup) { Hook(item, battle); } } public static void Hook(EnemyUnit enemy, BattleController battle) { if (!MpSession.IsActive || enemy == null || MpPrivateEnemies.IsPrivate((Unit)(object)enemy)) { return; } GameEventHandler val = delegate(DamageEventArgs args) { MpSafe.Run("EnemyDamageHook", delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((int)((GameEventArgs)args).Cause != 20 && (object)args.Source == battle.Player) { MpBattleSync.ReportEnemyDamage(enemy, args.DamageInfo, args.GunName); } }); }; ((Unit)enemy).DamageReceiving.AddHandler(val, (GameEventPriority)int.MinValue); Hooked.Add((enemy, val)); } public static void UnhookAll() { foreach (var entry in Hooked) { MpSafe.Run("EnemyDamageHook.Unhook", delegate { ((Unit)entry.Enemy).DamageReceiving.RemoveHandler(entry.Handler, (GameEventPriority)int.MinValue); }); } Hooked.Clear(); } } public static class PlayerDamageHook { private static PlayerUnit _player; private static GameEventHandler _handler; public static void Hook(BattleController battle) { Unhook(); if (!MpSession.IsActive || ((battle != null) ? battle.Player : null) == null) { return; } _player = battle.Player; _handler = delegate(DamageEventArgs args) { MpSafe.Run("PlayerDamageHook", delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0023: Unknown result type (might be due to invalid IL or missing references) if ((int)((GameEventArgs)args).Cause != 20 && !((GameEventArgs)args).IsCanceled) { MpBattleSync.ReportHit(args.DamageInfo); } }); }; ((Unit)_player).DamageReceived.AddHandler(_handler, (GameEventPriority)int.MaxValue); } public static void Unhook() { if (_player != null && _handler != null) { MpSafe.Run("PlayerDamageHook.Unhook", delegate { ((Unit)_player).DamageReceived.RemoveHandler(_handler, (GameEventPriority)int.MaxValue); }); } _player = null; _handler = null; } } [HarmonyPatch(typeof(BattleController), "Spawn", new Type[] { typeof(EnemyUnit), typeof(EnemyUnit), typeof(int), typeof(bool) })] public static class SpawnHookPatch { [HarmonyPrefix] private static void Prefix(EnemyUnit spawner, EnemyUnit enemyUnit) { MpSafe.Run("SpawnHookPatch.Mark", delegate { MpPrivateEnemies.OnSpawning(spawner, enemyUnit); }); } [HarmonyPostfix] private static void Postfix(BattleController __instance, EnemyUnit __result) { MpSafe.Run("SpawnHookPatch", delegate { EnemyDamageHook.Hook(__result, __instance); }); } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class StatusReplicationPatch { [HarmonyPostfix] private static void Postfix(ApplyStatusEffectAction __instance) { MpSafe.Run("StatusReplicationPatch", delegate { if (MpSession.IsActive && MpBattleSync.InBattle && !MpBattleSync.ConsumeInjected((BattleAction)(object)__instance)) { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; if (args != null && args.AddResult.HasValue) { Unit unit = args.Unit; EnemyUnit val = (EnemyUnit)(object)((unit is EnemyUnit) ? unit : null); if (val != null && !MpPrivateEnemies.IsPrivate((Unit)(object)val)) { BattleController battle = ((BattleAction)__instance).Battle; if (battle != null && IsLocalPlayerSource(((BattleAction)__instance).Source, battle)) { MpBattleSync.ReportEnemyStatus(val, args.Effect, removing: false); } } } } }); } private static bool IsLocalPlayerSource(GameEntity source, BattleController battle) { if (source != null) { if (!(source is EnemyUnit)) { PlayerUnit val = (PlayerUnit)(object)((source is PlayerUnit) ? source : null); if (val == null) { StatusEffect val2 = (StatusEffect)(object)((source is StatusEffect) ? source : null); if (val2 == null) { if (source is Card || source is Exhibit || source is UltimateSkill || source is Doll) { return true; } return false; } return (object)val2.Owner == battle.Player; } return val == battle.Player; } return false; } return false; } } public static class MpBattleDriver { public static void Update() { if (MpSession.IsActive) { MpBattleSync.Update(); MpDownedPlayers.Tick(); MpAllyUnits.Tick(); MpEmotes.Update(); MpHandView.Tick(); } } } [HarmonyPatch(typeof(JingjieGanzhiyi), "OnAdded")] internal static class BorderSensorAnnouncePatch { [HarmonyPostfix] private static void Postfix() { MpSafe.Run("BorderSensorAnnouncePatch", MpBorderSensor.Announce); } } [HarmonyPatch(typeof(GameRunController))] internal static class BorderSensorDuplicatePatch { [HarmonyPrefix] [HarmonyPatch("GainExhibitRunner")] private static bool BeforeRunner(GameRunController __instance, Exhibit exhibit, ref IEnumerator __result) { if (!AlreadyHeld(__instance, exhibit)) { return true; } __result = Enumerable.Empty().GetEnumerator(); return false; } [HarmonyPrefix] [HarmonyPatch("GainExhibitInstantly")] private static bool BeforeInstantly(GameRunController __instance, Exhibit exhibit) { return !AlreadyHeld(__instance, exhibit); } private static bool AlreadyHeld(GameRunController gameRun, Exhibit exhibit) { return MpSafe.Run("BorderSensorDuplicatePatch", delegate { if (MpSession.IsActive && exhibit is JingjieGanzhiyi) { GameRunController obj = gameRun; if (obj != null) { PlayerUnit player = obj.Player; if (((player != null) ? new bool?(player.HasExhibit()) : ((bool?)null)) == true) { MpPlugin.Log.LogInfo((object)"Skipping a second Border Sensor; a partner's has already arrived"); return true; } } } return false; }, fallback: false); } } internal static class MpStatusOrigin { internal static bool Replaying; } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class StatusOriginPatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance, out bool __state) { __state = MpStatusOrigin.Replaying; MpStatusOrigin.Replaying = MpSafe.Run("StatusOriginPatch", () => MpSession.IsActive && MpBattleSync.IsInjected((BattleAction)(object)__instance), fallback: false); } [HarmonyPostfix] private static void Postfix(bool __state) { MpStatusOrigin.Replaying = __state; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class ColdStackDamagePatch { [HarmonyPostfix] private static void Postfix(ref int __result) { if (MpStatusOrigin.Replaying) { __result = 0; } } } [HarmonyPatch(typeof(Cold), "Stack")] public static class ColdStackRelayPatch { [HarmonyPostfix] private static void Postfix(Cold __instance, bool __result) { MpSafe.Run("ColdStackRelayPatch", delegate { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (__result && !MpStatusOrigin.Replaying) { Unit owner = ((StatusEffect)__instance).Owner; EnemyUnit val = (EnemyUnit)(object)((owner is EnemyUnit) ? owner : null); if (val != null && !MpPrivateEnemies.IsPrivate((Unit)(object)val)) { int stackDamage = __instance.StackDamage; if (stackDamage > 0) { MpBattleSync.ReportEnemyDamage(val, DamageInfo.HpLose((float)stackDamage, false), "Cold2"); } } } }); } } [HarmonyPatch(typeof(VnPanel), "ShowOptions")] public static class DoremyPortalOptionPatch { private const string TunnelOptionLineId = "line:0891a52"; private static readonly FieldRef AvailableRef = SafeFieldRef("k__BackingField"); private static readonly FieldRef LineIdRef = SafeFieldRef("_lineId"); private static FieldRef SafeFieldRef(string name) { try { return AccessTools.FieldRefAccess(name); } catch (Exception ex) { MpPlugin.Log.LogError((object)("Could not reach DialogOption." + name + ": " + ex.Message)); return null; } } [HarmonyPrefix] private static void Prefix(DialogOption[] options) { MpSafe.Run("DoremyPortalOption", delegate { if (options != null && options.Length != 0 && AvailableRef != null && LineIdRef != null && MpSession.IsActive && MpSession.IsInRun && InDoremyPortal()) { string[] array = new string[options.Length]; for (int i = 0; i < options.Length; i++) { array[i] = LineIdRef.Invoke(options[i]); } int num = IndexOfTunnel(array); if (num >= 0 && AvailableRef.Invoke(options[num])) { AvailableRef.Invoke(options[num]) = false; MpPlugin.Log.LogInfo((object)"Hiding Doremy's tunnel: skipping to the boss would strand the rest of the party"); } } }); } internal static int IndexOfTunnel(string[] lineIds) { for (int i = 0; i < lineIds.Length; i++) { if (lineIds[i] == "line:0891a52") { return i; } } if (lineIds.Length > 1) { MpPlugin.Log.LogWarning((object)(string.Format("Could not find Doremy's tunnel by line id ({0}) in a {1}-option ", "line:0891a52", lineIds.Length) + "prompt; leaving every option alone. The game's dialogue may have changed, and the line id above is what wants updating.")); } return -1; } internal static bool InDoremyPortal() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentStation : null); } object obj2 = ((obj is AdventureStation) ? obj : null); return ((obj2 == null) ? null : ((object)((AdventureStation)obj2).Adventure)?.GetType().Name) == "DoremyPortal"; } } [HarmonyPatch(typeof(DoremyPortal), "TeleportBoss")] public static class DoremyPortalTeleportPatch { [HarmonyPrefix] private static bool Prefix() { return MpSafe.Run("DoremyPortalTeleport", delegate { if (!MpSession.IsActive || !MpSession.IsInRun) { return true; } MpPlugin.Log.LogWarning((object)"Refusing to skip to the boss: the rest of the party is still on the map"); return false; }, fallback: true); } } internal static class MpEnemyScaling { internal static int ExtraFighters { get { if (!MpSession.IsActive) { return 0; } int num = (MpEventBattle.Active ? MpEventBattle.FighterCount : MpSession.ConnectedCount); return Mathf.Max(0, num - 1); } } internal static int ActOf(Unit unit) { object obj = ((unit != null) ? ((GameEntity)unit).GameRun : null); if (obj == null) { GameMaster instance = Singleton.Instance; obj = ((instance != null) ? instance.CurrentGameRun : null); } int? obj2; if (obj == null) { obj2 = null; } else { Stage currentStage = ((GameRunController)obj).CurrentStage; obj2 = ((currentStage != null) ? new int?(currentStage.Level) : ((int?)null)); } return Mathf.Clamp(obj2 ?? 1, 1, 4); } internal static float BonusFor(Unit unit) { int extraFighters = ExtraFighters; if (extraFighters <= 0) { return 0f; } int num = extraFighters * (extraFighters + 1) / 2; return MpSession.EnemyHpScalePerExtraPlayer * (float)extraFighters + MpSession.EnemyHpEscalationForAct(ActOf(unit)) * (float)num; } internal static float MultiplierFor(Unit unit) { return 1f + BonusFor(unit); } internal static float HalfMultiplierFor(Unit unit) { return 1f + BonusFor(unit) * 0.5f; } } [HarmonyPatch(typeof(LimitedDamage), "OnAdded")] public static class EnemyDamageCapScalingPatch { [HarmonyPrefix] private static void Prefix(LimitedDamage __instance, Unit unit) { MpSafe.Run("EnemyDamageCapScalingPatch", delegate { if (unit is EnemyUnit && MpEnemyScaling.ExtraFighters > 0 && !MpPrivateEnemies.IsPrivate(unit)) { ((StatusEffect)__instance).Limit = Mathf.Max(1, Mathf.RoundToInt((float)((StatusEffect)__instance).Limit * MpEnemyScaling.MultiplierFor(unit))); } }); } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class EnemyGrazeScalingPatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance) { MpSafe.Run("EnemyGrazeScalingPatch", delegate { int extraFighters = MpEnemyScaling.ExtraFighters; if (extraFighters > 0) { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; StatusEffect val = ((args != null) ? args.Effect : null); if (val != null && args.Unit is EnemyUnit && val.HasLevel && !MpPrivateEnemies.IsPrivate(args.Unit) && !MpBattleSync.IsInjected((BattleAction)(object)__instance)) { if (val is Graze) { if (((BattleAction)__instance).Source is WindGirl) { return; } } else if (!(val is WindGirl)) { return; } int num = val.Level + extraFighters; val.SetInitLevel(num); args.Level = num; } } }); } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class EnemySleepScalingPatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance) { MpSafe.Run("EnemySleepScalingPatch", delegate { if (MpEnemyScaling.ExtraFighters > 0) { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; StatusEffect val = ((args != null) ? args.Effect : null); if (val is Sleep && args.Unit is EnemyUnit && val.HasLevel && !MpPrivateEnemies.IsPrivate(args.Unit) && !MpBattleSync.IsInjected((BattleAction)(object)__instance)) { int num = Mathf.Max(1, Mathf.RoundToInt((float)val.Level * MpEnemyScaling.HalfMultiplierFor(args.Unit))); val.SetInitLevel(num); args.Level = num; } } }); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class EnemyHealScalingPatch { [HarmonyPostfix] private static void Postfix(HealAction __instance) { MpSafe.Run("EnemyHealScalingPatch", delegate { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 if (MpEnemyScaling.ExtraFighters > 0) { HealEventArgs args = ((EventBattleAction)(object)__instance).Args; if (args != null && (int)args.HealType != 1 && args.Source is EnemyUnit) { Unit target = args.Target; EnemyUnit val = (EnemyUnit)(object)((target is EnemyUnit) ? target : null); if (val != null && !MpPrivateEnemies.IsPrivate((Unit)(object)val)) { args.Amount = Mathf.Max(1f, Mathf.Round(args.Amount * MpEnemyScaling.MultiplierFor((Unit)(object)val))); } } } }); } } [HarmonyPatch(typeof(FlatPeach), "OnDamageReceived")] public static class FlatPeachHealScalingExemption { [HarmonyPostfix] private static void Postfix(FlatPeach __instance, ref IEnumerable __result) { IEnumerable enumerable = __result; if (enumerable != null) { __result = Unscaled(__instance, enumerable); } } private static IEnumerable Unscaled(FlatPeach peach, IEnumerable actions) { foreach (BattleAction action in actions) { MpSafe.Run("FlatPeachHealScalingExemption", delegate { Restore(peach, action); }); yield return action; } } private static void Restore(FlatPeach peach, BattleAction action) { HealAction val = (HealAction)(object)((action is HealAction) ? action : null); if (((EventBattleAction)(object)val)?.Args != null && ((EventBattleAction)(object)val).Args.Target == ((StatusEffect)peach).Owner) { ((EventBattleAction)(object)val).Args.Amount = ((StatusEffect)peach).Level; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class SeijaBarrierScalingPatch { [HarmonyPostfix] private static void Postfix(CastBlockShieldAction __instance) { MpSafe.Run("SeijaBarrierScalingPatch", delegate { if (MpEnemyScaling.ExtraFighters > 0) { BlockShieldEventArgs args = ((EventBattleAction)(object)__instance).Args; if (args != null && args.HasShield && !args.HasBlock) { Unit target = args.Target; Seija val = (Seija)(object)((target is Seija) ? target : null); if (val != null && args.Source == args.Target && !MpPrivateEnemies.IsPrivate((Unit)(object)val)) { args.Shield = Mathf.Max(1f, Mathf.Round(args.Shield * MpEnemyScaling.MultiplierFor((Unit)(object)val))); } } } }); } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class DroneBlockScalingPatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance) { MpSafe.Run("DroneBlockScalingPatch", delegate { if (MpEnemyScaling.ExtraFighters > 0) { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; StatusEffect val = ((args != null) ? args.Effect : null); if (val is DroneBlock && args.Unit is EnemyUnit && val.HasLevel && !MpPrivateEnemies.IsPrivate(args.Unit) && !MpBattleSync.IsInjected((BattleAction)(object)__instance)) { int num = Mathf.Max(1, Mathf.RoundToInt((float)val.Level * MpEnemyScaling.HalfMultiplierFor(args.Unit))); val.SetInitLevel(num); args.Level = num; } } }); } } internal static class MpTianziSpell { internal const int VanillaCost = 100; internal static int Cost(EnemyUnit tianzi) { if (MpEnemyScaling.ExtraFighters <= 0 || MpPrivateEnemies.IsPrivate((Unit)(object)tianzi)) { return 100; } return Mathf.Max(100, Mathf.RoundToInt(100f * MpEnemyScaling.MultiplierFor((Unit)(object)tianzi))); } } [HarmonyPatch(typeof(Tianzi), "UpdateMoveCounters")] public static class TianziSpellThresholdPatch { [HarmonyPrefix] private static bool Prefix(Tianzi __instance) { int num = MpSafe.Run("TianziSpellThresholdPatch", () => MpTianziSpell.Cost((EnemyUnit)(object)__instance), 100); if (num <= 100) { return true; } Tianzi obj = __instance; int countDown = ((EnemyUnit)obj).CountDown; ((EnemyUnit)obj).CountDown = countDown - 1; Tianzi obj2 = __instance; countDown = obj2.DebuffCountDown; obj2.DebuffCountDown = countDown - 1; if (((EnemyUnit)__instance).CountDown <= 0) { __instance.Next = (MoveType)2; ((EnemyUnit)__instance).CountDown = 5; return false; } EnemyEnergy statusEffect = ((Unit)__instance).GetStatusEffect(); if (statusEffect != null && ((StatusEffect)statusEffect).Level >= num) { __instance.Next = (MoveType)3; return false; } if (__instance.DebuffCountDown <= 0) { __instance.Next = (MoveType)1; __instance.DebuffCountDown = 4; return false; } __instance.Next = (MoveType)0; return false; } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class TianziSpellPaymentPatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance) { MpSafe.Run("TianziSpellPaymentPatch", delegate { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; StatusEffect val = ((args != null) ? args.Effect : null); if (val is EnemyEnergyNegative) { Unit unit = args.Unit; Tianzi val2 = (Tianzi)(object)((unit is Tianzi) ? unit : null); if (val2 != null && val.HasLevel && !MpBattleSync.IsInjected((BattleAction)(object)__instance) && val.Level == 100) { int num = MpTianziSpell.Cost((EnemyUnit)(object)val2); if (num > 100) { val.SetInitLevel(num); args.Level = num; } } } }); } } internal static class MpLoveGirl { internal const int ShippedRate = 20; private const int RateDropPerExtraPlayer = 5; internal static int RatePerStack => Mathf.Max(1, 20 - 5 * MpEnemyScaling.ExtraFighters); internal static int Stacks(int shipped) { return shipped * (1 + MpEnemyScaling.ExtraFighters); } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class LoveGirlRegretScalingPatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance) { MpSafe.Run("LoveGirlRegretScalingPatch", delegate { if (MpEnemyScaling.ExtraFighters > 0) { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; StatusEffect val = ((args != null) ? args.Effect : null); if (val is LoveGirlDamageReduce) { Unit unit = args.Unit; EnemyUnit val2 = (EnemyUnit)(object)((unit is EnemyUnit) ? unit : null); if (val2 != null && val.HasLevel && !MpPrivateEnemies.IsPrivate((Unit)(object)val2) && !MpBattleSync.IsInjected((BattleAction)(object)__instance)) { int num = MpLoveGirl.Stacks(val.Level); val.SetInitLevel(num); args.Level = num; } } } }); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class LoveGirlRegretRatePatch { [HarmonyPostfix] private static void Postfix(LoveGirlDamageReduce __instance, ref int __result) { int shipped = __result; __result = MpSafe.Run("LoveGirlRegretRatePatch", delegate { int ratePerStack = MpLoveGirl.RatePerStack; return (ratePerStack >= 20 || MpPrivateEnemies.IsPrivate(((StatusEffect)__instance).Owner)) ? shipped : Mathf.Min(100, ((StatusEffect)__instance).Level * ratePerStack); }, shipped); } } internal static class MpResilience { internal static int LevelFor(Unit unit) { if (!MpSession.EnemyResilience) { return 0; } return MpEnemyScaling.ExtraFighters; } internal static void Grant(EnemyUnit enemy) { if (enemy == null) { return; } int num = LevelFor((Unit)(object)enemy); if (num <= 0 || MpPrivateEnemies.IsPrivate((Unit)(object)enemy)) { return; } BattleController battle = ((Unit)enemy).Battle; if (battle != null && !((Unit)enemy).HasStatusEffect()) { MpResilient mpResilient = Library.CreateStatusEffect(); ((StatusEffect)mpResilient).SetInitLevel(num); battle.TryAddStatusEffect((Unit)(object)enemy, (StatusEffect)(object)mpResilient); IEnemyUnitView view = enemy.View; IEnemyUnitView obj = ((view is UnitView) ? view : null); if (obj != null) { ((UnitView)obj).OnAddStatusEffect((StatusEffect)(object)mpResilient, (StatusEffectAddResult)0); } } } } [HarmonyPatch(typeof(Unit), "EnterBattle")] public static class EnemyResilienceApplyPatch { [HarmonyPostfix] private static void Postfix(Unit __instance) { MpSafe.Run("EnemyResilienceApplyPatch", delegate { Unit obj = __instance; MpResilience.Grant((EnemyUnit)(object)((obj is EnemyUnit) ? obj : null)); }); } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] public static class EnemyFirepowerDownResiliencePatch { [HarmonyPrefix] private static void Prefix(ApplyStatusEffectAction __instance) { MpSafe.Run("EnemyFirepowerDownResiliencePatch", delegate { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; StatusEffect val = ((args != null) ? args.Effect : null); if (val is FirepowerNegative || val is TempFirepowerNegative) { Unit unit = args.Unit; EnemyUnit val2 = (EnemyUnit)(object)((unit is EnemyUnit) ? unit : null); if (val2 != null && val.HasLevel && !MpBattleSync.IsInjected((BattleAction)(object)__instance)) { MpResilient statusEffect = ((Unit)val2).GetStatusEffect(); int num = ((statusEffect != null) ? ((StatusEffect)statusEffect).Level : 0); if (num > 0) { int num2 = Mathf.Max(1, val.Level - num); if (num2 < val.Level) { val.SetInitLevel(num2); args.Level = num2; } } } } }); } } [HarmonyPatch(typeof(DialogRunner), "Phases")] public static class EventAbortPatch { [HarmonyPostfix] private static void Postfix(ref IEnumerable __result) { IEnumerable enumerable = __result; if (enumerable != null) { __result = Truncated(enumerable); } } private static IEnumerable Truncated(IEnumerable original) { foreach (DialogPhase item in original) { if (MpSafe.Run("EventAbort", () => MpEventBattle.LocalEventAborted, fallback: false)) { MpPlugin.Log.LogInfo((object)"Cutting the event short; its fight was lost"); yield break; } yield return item; } } } [HarmonyPatch(typeof(AdventureStation), "OnEnter")] public static class EventBattleResetPatch { [HarmonyPostfix] private static void Postfix() { MpSafe.Run("EventBattleReset", MpEventBattle.Reset); } } [HarmonyPatch(typeof(VnPanel), "RunBattle")] public static class EventBattleStartPatch { private static readonly HashSet CombatAdventures = new HashSet { "MiyoiBartender", "YachieOppression" }; [HarmonyPostfix] private static void Postfix(VnPanel __instance, string enemyGroupName, bool reopenVnPanel, ref IEnumerator __result) { IEnumerator enumerator = __result; if (enumerator != null) { __result = Gated(__instance, enemyGroupName, reopenVnPanel, enumerator); } } private static IEnumerator Gated(VnPanel panel, string enemyGroupName, bool reopenVnPanel, IEnumerator battle) { if (MpSafe.Run("EventBattleStart", () => MpSession.IsActive && MpSession.IsInRun && InAdventure(), fallback: false) && !MpEventBattle.ModRequestedBattle) { MpSafe.Run("EventBattleAnnounce", delegate { MpEventBattle.Announce(fighting: true, enemyGroupName); }); yield return MpEventBattle.WaitForEveryone(); MpSafe.Run("EventBattleRole", MpEventBattle.SettleLocalRole); string agreed = MpSafe.Run("EventBattleAgreedGroup", () => MpEventBattle.EnemyGroupId, string.Empty); if (!string.IsNullOrEmpty(agreed) && agreed != enemyGroupName) { MpPlugin.Log.LogInfo((object)("This client rolled '" + enemyGroupName + "' for the event's fight, but the party is fighting '" + agreed + "'; taking the party's")); IEnumerator replacement = null; MpSafe.Run("EventBattleRegroup", MpEventBattle.BeginModRequest); MpSafe.Run("EventBattleRegroupStart", delegate { replacement = panel.RunBattle(agreed, reopenVnPanel); }); MpSafe.Run("EventBattleRegroupEnd", MpEventBattle.EndModRequest); if (replacement != null) { battle = replacement; } } } yield return battle; } internal static bool InAdventure() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentStation : null); } object obj2 = ((obj is AdventureStation) ? obj : null); Adventure val = ((obj2 != null) ? ((AdventureStation)obj2).Adventure : null); if (val != null) { return CombatAdventures.Contains(((object)val).GetType().Name); } return false; } } [HarmonyPatch(typeof(VnPanel), "CoRunDialog")] public static class EventBattleDeclinePatch { [HarmonyPostfix] private static void Postfix(VnPanel __instance, ref IEnumerator __result) { IEnumerator enumerator = __result; if (enumerator != null) { __result = Gated(__instance, enumerator); } } private static IEnumerator Gated(VnPanel panel, IEnumerator dialog) { yield return dialog; MpSafe.Run("EventAbortClear", MpEventBattle.ClearEventAbort); if (!MpSafe.Run("EventBattleDecline", () => MpSession.IsActive && MpSession.IsInRun && EventBattleStartPatch.InAdventure(), fallback: false)) { yield break; } MpSafe.Run("EventBattleDeclineAnnounce", delegate { MpEventBattle.Announce(fighting: false, string.Empty); }); yield return MpEventBattle.WaitForEveryone(); MpSafe.Run("EventBattleRole", MpEventBattle.SettleLocalRole); if (!MpSafe.Run("EventBattleShouldWatch", () => MpEventBattle.LocalSpectating, fallback: false)) { yield break; } string group = MpSafe.Run("EventBattleGroup", () => MpEventBattle.EnemyGroupId, string.Empty); if (string.IsNullOrEmpty(group)) { MpPlugin.Log.LogWarning((object)"Somebody took the event's fight but no enemy group came with it"); yield break; } MpPlugin.Log.LogInfo((object)("Watching the party fight '" + group + "'")); MpSafe.Run("EventBattleSpectateBegin", MpEventBattle.BeginModRequest); IEnumerator battle = null; if (MpSafe.Run("EventBattleSpectateStart", delegate { battle = panel.RunBattle(group, true); return battle != null; }, fallback: false)) { yield return battle; } MpSafe.Run("EventBattleSpectateEnd", MpEventBattle.EndModRequest); MpSafe.Run("EventBattleSpectateDone", MpEventBattle.ClearLocalRole); MpSafe.Run("EventAbortClearAfterWatch", MpEventBattle.ClearEventAbort); } } public static class MpHoveredUnit { private static UnitView _view; public static int HoveredPlayer { get { if (!((Object)(object)_view == (Object)null)) { return MpAllyUnits.PlayerFor(_view); } return -1; } } internal static void Entered(UnitView view) { _view = view; } internal static void Exited(UnitView view) { if (_view == view) { _view = null; } } } [HarmonyPatch(typeof(UnitView), "Event_OnPointerEnter")] public static class UnitHoverEnterPatch { [HarmonyPostfix] private static void Postfix(UnitView __instance) { MpHoveredUnit.Entered(__instance); } } [HarmonyPatch(typeof(UnitView), "Event_OnPointerExit")] public static class UnitHoverExitPatch { [HarmonyPostfix] private static void Postfix(UnitView __instance) { MpHoveredUnit.Exited(__instance); } } internal static class InspectRedirect { internal static bool Took(Action show) { if (!MpHandView.Active) { return false; } show(); return true; } } [HarmonyPatch(typeof(PlayBoard), "GetHoveringIndex")] public static class HoveringIndexPatch { [HarmonyPrefix] private static bool Prefix(ref int? __result) { if (!MpHandView.Active) { return true; } __result = null; return false; } } [HarmonyPatch(typeof(PlayBoard), "ShowDrawZone")] public static class ShowDrawZonePatch { [HarmonyPrefix] private static bool Prefix() { return !InspectRedirect.Took(MpInspectedPiles.ShowDraw); } } [HarmonyPatch(typeof(PlayBoard), "ShowDiscardZone")] public static class ShowDiscardZonePatch { [HarmonyPrefix] private static bool Prefix() { return !InspectRedirect.Took(MpInspectedPiles.ShowDiscard); } } [HarmonyPatch(typeof(PlayBoard), "ShowExileZone")] public static class ShowExileZonePatch { [HarmonyPrefix] private static bool Prefix() { return !InspectRedirect.Took(MpInspectedPiles.ShowExile); } } [HarmonyPatch(typeof(SystemBoard), "ShowBaseDeck")] public static class ShowBaseDeckPatch { [HarmonyPrefix] private static bool Prefix() { return !InspectRedirect.Took(MpInspectedPiles.ShowDeck); } } [HarmonyPatch(typeof(ShowCardsPanel), "OnShowing", new Type[] { typeof(ShowCardsPayload) })] public static class InspectedPilePortraitPatch { [HarmonyPostfix] private static void Postfix(ShowCardsPanel __instance) { MpSafe.Run("InspectedPilePortraitPatch", delegate { string showingFor = MpInspectedPiles.ShowingFor; if (!string.IsNullOrEmpty(showingFor) && !((Object)(object)__instance.portrait == (Object)null) && __instance.characterPortraits != null) { Sprite val = default(Sprite); if (!__instance.characterPortraits.TryGetValue(showingFor, ref val) || (Object)(object)val == (Object)null) { MpPlugin.Log.LogWarning((object)("The pile viewer has no illustration for '" + showingFor + "'")); } else { __instance.portrait.sprite = val; __instance._currentCharacterIndex = -1; } } }); } } [HarmonyPatch(typeof(StartGamePanel), "SelectDifficulty")] public static class LobbyDifficultyPatch { private static bool _applyingHostChoice; private const int DifficultyPhase = 3; private static bool LocalChoiceCounts { get { if (MpNet.IsOnline) { return MpNet.IsHost; } return true; } } [HarmonyPrefix] private static void Prefix(ref int index) { int num = MpSafe.Run("LobbyDifficultyPrefix", () => (!_applyingHostChoice && !LocalChoiceCounts) ? MpSession.HostDifficulty : (-1), -1); if (num >= 0) { index = num; } } [HarmonyPostfix] private static void Postfix(StartGamePanel __instance, int index) { MpSafe.Run("LobbyDifficultyPostfix", delegate { if (MpNet.IsOnline) { if (MpNet.IsHost) { MpSession.PublishHostDifficulty(index); } else { LockForClient(__instance, index); } } }); } private static void LockForClient(StartGamePanel panel, int index) { ((Selectable)panel.difficultyLeftButton).interactable = false; ((Selectable)panel.difficultyRightButton).interactable = false; if (panel._isDifficultyLock) { panel._isDifficultyLock = false; if (index >= 0 && index < panel.difficultyGroups.Length) { panel.difficultyGroups[index].SetLocked(false); } panel.RefreshDifficultyConfirm(); } } public static void ApplyHostChoice(int difficulty) { StartGamePanel panel = UiManager.GetPanel(); if ((Object)(object)panel == (Object)null) { return; } int num = Mathf.Clamp(difficulty, 0, 3); if (num == panel._difficultyIndex) { return; } bool flag = panel._currentPanelPhase == 3 && ((Behaviour)panel).isActiveAndEnabled; _applyingHostChoice = true; try { panel.SelectDifficulty(num, !flag); } finally { _applyingHostChoice = false; } } } [HarmonyPatch(typeof(MapPanel), "RequestEnterNode")] public static class MapVotingPatch { private static bool _bypass; private static bool _hasPending; private static int _pendingX; private static int _pendingY; private static bool _restoreWidgets; private static float _movingSince; private const float MoveConfirmSeconds = 6f; internal static bool MoveInFlight { get { if (!_hasPending) { if (_movingSince > 0f) { return !StandingOn(_pendingX, _pendingY); } return false; } return true; } } internal static (int X, int Y) PendingNode => (X: _pendingX, Y: _pendingY); [HarmonyPrefix] private static bool Prefix(MapNodeWidget enteringWidget) { if (_bypass || !MpSession.IsActive || !MpSession.IsInRun) { return true; } if (!ReadyToLeaveStation()) { MpPlugin.Log.LogWarning((object)"Ignored a map click: this client has not finished its station"); _restoreWidgets = true; return false; } MapSync.CastVote(enteringWidget.X, enteringWidget.Y); _restoreWidgets = true; MpPlugin.Log.LogInfo((object)$"Voted for map node ({enteringWidget.X}, {enteringWidget.Y})"); return false; } public static void EnterCommittedNode(int x, int y) { _hasPending = true; _pendingX = x; _pendingY = y; TryEnterPending(); } public static void Update() { if (_restoreWidgets) { _restoreWidgets = false; MpSafe.Run("MapVotingPatch.RestoreWidgets", RestoreNodeWidgets); } if (_hasPending) { TryEnterPending(); } else { ConfirmMoveTookEffect(); } } internal static bool StandingOn(int x, int y) { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; if (currentGameRun == null) { obj = null; } else { GameMap currentMap = currentGameRun.CurrentMap; obj = ((currentMap != null) ? currentMap.VisitingNode : null); } } MapNode val = (MapNode)obj; if (val != null && val.X == x) { return val.Y == y; } return false; } private static void ConfirmMoveTookEffect() { if (!(_movingSince <= 0f) && !(Time.unscaledTime - _movingSince < 6f)) { if (StandingOn(_pendingX, _pendingY)) { _movingSince = 0f; return; } MpPlugin.Log.LogWarning((object)$"Move to ({_pendingX}, {_pendingY}) never took effect; trying again"); _movingSince = 0f; _hasPending = true; } } private static void RestoreNodeWidgets() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentMap : null); } GameMap val = (GameMap)obj; MapPanel panel = UiManager.GetPanel(); if (((val != null) ? val.Nodes : null) == null || (Object)(object)panel == (Object)null) { return; } MapNode[,] nodes = val.Nodes; foreach (MapNode val2 in nodes) { if (val2 != null) { MapNodeWidget mapNodeWidget = panel.GetMapNodeWidget(val2.X, val2.Y); if ((Object)(object)mapNodeWidget != (Object)null) { mapNodeWidget.SetStatus(val2); } } } } private static bool ReadyToLeaveStation() { //IL_0016: 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_0030: Invalid comparison between Unknown and I4 GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); if (val == null || (int)val.Status != 0) { return false; } Station currentStation = val.CurrentStation; if (currentStation != null) { return (int)currentStation.Status == 4; } return true; } private static void TryEnterPending() { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Invalid comparison between Unknown and I4 //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Invalid comparison between Unknown and I4 if (StandingOn(_pendingX, _pendingY)) { _hasPending = false; _movingSince = 0f; return; } GameMaster instance = Singleton.Instance; GameRunController val = ((instance != null) ? instance.CurrentGameRun : null); if (((val != null) ? val.CurrentMap : null) == null || !ReadyToLeaveStation()) { return; } MapNode val2; try { val2 = val.CurrentMap.Nodes[_pendingX, _pendingY]; } catch (IndexOutOfRangeException) { MpPlugin.Log.LogError((object)$"Committed node ({_pendingX}, {_pendingY}) is off this client's map"); _hasPending = false; return; } if ((int)val2.Status != 1 && (int)val2.Status != 2) { return; } MapPanel panel = UiManager.GetPanel(); if ((Object)(object)panel == (Object)null || !((Behaviour)panel).isActiveAndEnabled) { return; } MapNodeWidget mapNodeWidget = panel.GetMapNodeWidget(_pendingX, _pendingY); if ((Object)(object)mapNodeWidget == (Object)null) { MpPlugin.Log.LogError((object)$"Committed node ({_pendingX}, {_pendingY}) has no widget"); _hasPending = false; return; } _hasPending = false; MapSync.ClearCommit(); _restoreWidgets = false; _bypass = true; try { panel.RequestEnterNode(mapNodeWidget); _movingSince = Time.unscaledTime; MpPlugin.Log.LogInfo((object)$"Party moving to map node ({_pendingX}, {_pendingY})"); } catch (Exception ex2) { MpPlugin.Log.LogError((object)("Failed to enter the committed node: " + ex2)); } finally { _bypass = false; } } public static void Reset() { _hasPending = false; _restoreWidgets = false; _movingSince = 0f; } } [HarmonyPatch(typeof(Stage), "GetSupplyExhibit")] public static class SupplyExhibitPatch { [HarmonyPrefix] private static bool Prefix(Stage __instance, ref Exhibit __result) { Exhibit val = MpSafe.Run("SupplyExhibitPatch", delegate { RandomGen supply = MpPersonalRng.Supply; Stage obj = __instance; GameRunController val2 = ((obj != null) ? ((GameEntity)obj).GameRun : null); return (supply == null || val2 == null) ? null : val2.RollNormalExhibit(supply, __instance.SupplyExhibitWeightTable, (Func)__instance.GetSentinelExhibit, (Predicate)null); }, null); if (val == null) { return true; } __result = val; return false; } } [HarmonyPatch(typeof(PlayBoard), "EnqueueRequest")] public static class PlayBoardInputPatch { private enum Decision { RunOriginal, Reject, Parked } [HarmonyPrefix] private static bool Prefix(PlayBoard __instance, RequestEntry request, ref bool __result) { switch (MpSafe.Run("PlayBoardInputPatch", () => Decide(__instance, request), Decision.RunOriginal)) { case Decision.Reject: __result = false; return false; case Decision.Parked: __result = true; return false; default: return true; } } private static Decision Decide(PlayBoard playBoard, RequestEntry request) { if (!MpSession.IsActive || !MpBattleSync.InBattle) { return Decision.RunOriginal; } if (MpDownedPlayers.OutOfFight) { return Decision.Reject; } if (MpHandView.Active) { return Decision.Reject; } if (!MpBattleSync.ShouldDeferPlayerInput) { return Decision.RunOriginal; } request.PlayBoard = playBoard; if (!request.Verify(false)) { return Decision.Reject; } request.Prepay(); playBoard._requests.Enqueue(request); MpPlugin.Log.LogInfo((object)("Parked " + ((object)request).GetType().Name + " while replaying another player's move")); return Decision.Parked; } } [HarmonyPatch(typeof(TiangouOrderSe), "OnDamageReceived")] internal static class TiangouOrderBlockPatch { private static bool Prefix(DamageEventArgs args, ref IEnumerable __result) { if (!MpSafe.Run("TiangouOrderBlockPatch", delegate { if (MpSession.IsActive && MpBattleSync.InBattle) { DamageEventArgs obj = args; return MpAllyUnits.IsMirror((obj != null) ? obj.Source : null); } return false; }, fallback: false)) { return true; } __result = Enumerable.Empty(); return false; } } [HarmonyPatch(typeof(ApplyStatusEffectAction), "MainPhase")] internal static class CuriosityReplicationPatch { [HarmonyPostfix] private static void Postfix(ApplyStatusEffectAction __instance) { MpSafe.Run("CuriosityReplicationPatch", delegate { if (MpSession.IsActive && MpBattleSync.InBattle) { GameEntity source = ((BattleAction)__instance).Source; Curiosity val = (Curiosity)(object)((source is Curiosity) ? source : null); if (val != null) { StatusEffectApplyEventArgs args = ((EventBattleAction)(object)__instance).Args; if (args != null && args.AddResult.HasValue && args.Effect is Firepower) { Unit unit = args.Unit; EnemyUnit val2 = (EnemyUnit)(object)((unit is EnemyUnit) ? unit : null); if (val2 != null && !MpPrivateEnemies.IsPrivate((Unit)(object)val2) && (object)((StatusEffect)val).Owner == val2) { MpBattleSync.ReportCuriosity(val2, args.Level ?? ((StatusEffect)val).Level); } } } } }); } } [HarmonyPatch(typeof(GameMaster), "RequestReenterStation")] internal static class RestartStationPatch { [HarmonyPrefix] private static bool Prefix() { return MpSafe.Run("RestartStationPatch", MpRestart.OnLocalRequest, fallback: true); } } [HarmonyPatch(typeof(SettingPanel))] internal static class RestartButtonLockPatch { private static bool? _restore; [HarmonyPostfix] [HarmonyPatch("OnShowing")] private static void AfterShowing(SettingPanel __instance, SettingsPanelType payload) { //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) MpSafe.Run("RestartButtonLockPatch.Show", delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 _restore = null; if ((int)payload == 1 && !MpRestart.LocalDecides) { Button reenterStationButton = __instance.reenterStationButton; if (!((Object)(object)reenterStationButton == (Object)null)) { _restore = ((Selectable)reenterStationButton).interactable; __instance.SetReenterStationInteractable(false); } } }); } [HarmonyPostfix] [HarmonyPatch("OnHiding")] private static void AfterHiding(SettingPanel __instance) { MpSafe.Run("RestartButtonLockPatch.Hide", delegate { if (_restore.HasValue) { bool value = _restore.Value; _restore = null; if ((Object)(object)__instance.reenterStationButton != (Object)null) { __instance.SetReenterStationInteractable(value); } } }); } } [HarmonyPatch(typeof(GameMaster), "RestoreGameRun")] public static class RestoreGameInterceptPatch { private static GameRunSaveData _pending; private static bool _allowThrough; public static bool HasPending => _pending != null; [HarmonyPrefix] private static bool Prefix(GameRunSaveData saveData) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected I4, but got Unknown if (_allowThrough || !MpNet.IsOnline || saveData == null) { return true; } GameMaster instance = Singleton.Instance; if (((instance != null) ? instance.CurrentGameRun : null) != null || !IsLoadable(saveData)) { return true; } if (MpSession.State != MpSessionState.Lobby) { MpPlugin.Log.LogWarning((object)"Continue pressed again while the party was still being waited on; ignoring it"); return false; } (int, int, int) tuple = PositionIn(saveData); _pending = saveData; MpSession.SubmitLocalResume(saveData.RootSeed, tuple.Item1, tuple.Item2, tuple.Item3, (int)saveData.Difficulty, saveData.Player?.Name); MpPlugin.Log.LogInfo((object)($"Continue held: run {saveData.RootSeed}, act {tuple.Item1 + 1}, " + $"node ({tuple.Item2}, {tuple.Item3}); waiting for the rest of the party")); return false; } public static void BeginPendingResume(ulong seed) { if (_pending == null) { MpPlugin.Log.LogWarning((object)"Resume arrived but this client had nothing held"); return; } GameRunSaveData pending = _pending; _pending = null; if (pending.RootSeed != seed) { MpPlugin.Log.LogError((object)$"Refusing to continue: the party agreed on run {seed} but this save is {pending.RootSeed}"); return; } MpPlugin.Log.LogInfo((object)$"Continuing multiplayer run {seed}"); _allowThrough = true; try { GameMaster.RestoreGameRun(pending); } finally { _allowThrough = false; } } public static void Cancel() { _pending = null; } private static bool IsLoadable(GameRunSaveData saveData) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected I4, but got Unknown SaveTiming timing = saveData.Timing; return (int)timing switch { 0 => false, 1 => saveData.EnteringNode != null, 2 => saveData.BattleStationEnemyGroup != null, 4 => saveData.AdventureState != null, _ => true, }; } internal static (int Stage, int X, int Y) PositionIn(GameRunSaveData saveData) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 int item = saveData.StageIndex ?? (-1); if ((int)saveData.Timing == 1 && saveData.EnteringNode != null) { return (Stage: item, X: saveData.EnteringNode.X, Y: saveData.EnteringNode.Y); } List path = saveData.Path; if (path != null && path.Count > 0) { MapNodeSaveData val = path[path.Count - 1]; return (Stage: item, X: val.X, Y: val.Y); } return (Stage: item, X: -1, Y: -1); } } [HarmonyPatch(typeof(GameMaster), "LeaveGameRun")] internal static class LeaveGameRunPatch { [HarmonyPostfix] private static void Postfix() { MpSafe.Run("LeaveGameRunPatch", MpSession.BackToLobby); } } [HarmonyPatch(typeof(SteamPlatformHandler), "GetSaveDataFolder")] public static class SaveIsolationPatch { private const string EnvironmentVariable = "LBOLMP_INSTANCE"; private const string CommandLineFlag = "--mp-instance="; private static string _suffix; private static bool _resolved; private static bool _logged; public static string InstanceName { get { if (_resolved) { return _suffix; } _resolved = true; _suffix = MpSafe.Run("SaveIsolationPatch.Resolve", Resolve, string.Empty); return _suffix; } } private static string Resolve() { string environmentVariable = Environment.GetEnvironmentVariable("LBOLMP_INSTANCE"); if (!string.IsNullOrWhiteSpace(environmentVariable)) { return Sanitise(environmentVariable); } string text = Environment.GetCommandLineArgs().FirstOrDefault((string a) => a.StartsWith("--mp-instance=", StringComparison.OrdinalIgnoreCase)); if (text != null) { return Sanitise(text.Substring("--mp-instance=".Length)); } return string.Empty; } private static string Sanitise(string value) { string text = new string((from c in value.Trim() where char.IsLetterOrDigit(c) || c == '-' || c == '_' select c).ToArray()); if (text.Length <= 24) { return text; } return text.Substring(0, 24); } [HarmonyPostfix] private static void Postfix(ref string __result) { string folder = __result; __result = MpSafe.Run("SaveIsolationPatch", () => Redirect(folder), folder); } private static string Redirect(string folder) { string instanceName = InstanceName; if (string.IsNullOrEmpty(instanceName) || string.IsNullOrEmpty(folder)) { return folder; } string text = folder + "_mp" + instanceName; if (!_logged) { _logged = true; MpPlugin.Log.LogInfo((object)("Save data redirected to " + text)); } return text; } } [HarmonyPatch(typeof(GameMaster))] public static class StartGameInterceptPatch { private sealed class PendingRun { public GameDifficulty Difficulty; public PuzzleFlag Puzzles; public PlayerUnit Player; public PlayerType PlayerType; public Exhibit InitExhibit; public int? InitMoneyOverride; public IEnumerable Deck; public IEnumerable Stages; public Type DebutAdventureType; public IEnumerable JadeBoxes; public GameMode GameMode; public bool ShowRandomResult; } private static PendingRun _pending; private static bool _allowThrough; public static bool HasPending => _pending != null; [HarmonyPrefix] [HarmonyPatch("StartGame", new Type[] { typeof(ulong?), typeof(GameDifficulty), typeof(PuzzleFlag), typeof(PlayerUnit), typeof(PlayerType), typeof(Exhibit), typeof(int?), typeof(IEnumerable), typeof(IEnumerable), typeof(Type), typeof(IEnumerable), typeof(GameMode), typeof(bool) })] private static bool StartGamePrefix(ulong? seed, GameDifficulty difficulty, PuzzleFlag puzzles, PlayerUnit player, PlayerType playerType, Exhibit initExhibit, int? initMoneyOverride, IEnumerable deck, IEnumerable stages, Type debutAdventureType, IEnumerable jadeBoxes, GameMode gameMode, bool showRandomResult) { //IL_0047: 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_004e: 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_005c: 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_00bd: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Invalid comparison between Unknown and I4 //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected I4, but got Unknown if (_allowThrough || !MpNet.IsOnline) { return true; } if (MpSession.State != MpSessionState.Lobby) { MpPlugin.Log.LogWarning((object)"Start Game pressed again while the lobby was still being waited on; ignoring the run, repairing the spell card's owner when it starts"); return false; } List list = deck?.ToList() ?? new List(); _pending = new PendingRun { Difficulty = difficulty, Puzzles = puzzles, Player = player, PlayerType = playerType, InitExhibit = initExhibit, InitMoneyOverride = initMoneyOverride, Deck = list, Stages = (stages?.ToList() ?? new List()), DebutAdventureType = debutAdventureType, JadeBoxes = (jadeBoxes?.ToList() ?? new List()), GameMode = gameMode, ShowRandomResult = showRandomResult }; MpSession.SubmitLocalReady(((player != null) ? ((GameEntity)player).Id : null) ?? string.Empty, ((int)playerType == 1) ? 1 : 0, ((initExhibit != null) ? ((GameEntity)initExhibit).Id : null) ?? string.Empty, list.Select(DescribeCard).ToList(), (int)difficulty); MpPlugin.Log.LogInfo((object)"Start Game held: waiting for the rest of the lobby"); return false; } private static string DescribeCard(Card card) { if (!card.IsUpgraded) { return ((GameEntity)card).Id; } return ((GameEntity)card).Id + "+"; } public static void BeginPendingRun(ulong seed) { //IL_0028: 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_002b: 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_003c: 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_008e: 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_009c: 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 (_pending == null) { MpPlugin.Log.LogWarning((object)"Run start arrived but this client had nothing pending"); return; } PendingRun pending = _pending; _pending = null; GameDifficulty val = (GameDifficulty)MpSession.RunDifficulty; if (val != pending.Difficulty) { MpPlugin.Log.LogInfo((object)$"Starting on the host's difficulty ({val}) rather than the one selected here ({pending.Difficulty})"); } MpPlugin.Log.LogInfo((object)$"Starting multiplayer run with seed {seed} on {val}"); RepairUsOwner(pending.Player); _allowThrough = true; try { GameMaster.StartGame((ulong?)seed, val, pending.Puzzles, pending.Player, pending.PlayerType, pending.InitExhibit, pending.InitMoneyOverride, pending.Deck, pending.Stages, pending.DebutAdventureType, pending.JadeBoxes, pending.GameMode, pending.ShowRandomResult); } finally { _allowThrough = false; } } public static void RepairUsOwner(PlayerUnit player) { MpSafe.Run("RepairUsOwner", delegate { PlayerUnit obj = player; UltimateSkill val = ((obj != null) ? obj.Us : null); if (val != null && val.Owner != player) { MpPlugin.Log.LogWarning((object)(((GameEntity)val).Id + " was owned by a discarded player unit; pointing it back at " + ((GameEntity)player).Id)); val.Owner = player; } }); } public static void Cancel() { _pending = null; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class PersonalRngPatch { [HarmonyPostfix] private static void Postfix(GameRunController __instance) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown ulong salt = MpPersonalRng.Salt; if (MpNet.IsOnline && salt != 0L) { __instance.ShopRng = new RandomGen(__instance.RootSeed ^ salt ^ 0x11); __instance.ExhibitRng = new RandomGen(__instance.RootSeed ^ salt ^ 0x33); __instance.ShiningExhibitRng = new RandomGen(__instance.RootSeed ^ salt ^ 0x44); __instance.CardRng = new RandomGen(__instance.RootSeed ^ salt ^ 0x55); __instance.AdventureRng = new RandomGen(__instance.RootSeed ^ salt ^ 0x66); MpPlugin.Log.LogInfo((object)$"Personalised reward RNG for player {MpNet.LocalPlayerId}"); MpSafe.Run("RngFixInterop", delegate { RngFixInterop.Personalise(__instance, __instance.RootSeed ^ salt); }); } } } [HarmonyPatch] public static class AdventureSyncPatch { private static bool _hostIsDeciding; private static readonly string[] CombatEventNames = new string[2] { "MiyoiBartender", "YachieOppression" }; private static int _forcedIndex; [HarmonyTargetMethods] private static IEnumerable TargetMethods() { List list = new List(); MethodInfo method = typeof(NormalStageBase).GetMethod("GetAdventure", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } catch (Exception) { continue; } Type[] array2 = array; foreach (Type type in array2) { if (!(type == null) && typeof(Stage).IsAssignableFrom(type)) { MethodInfo method2 = type.GetMethod("GetAdventure", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); if (method2 != null) { list.Add(method2); } } } } if (method != null && !list.Contains(method)) { MpPlugin.Log.LogWarning((object)"NormalStageBase.GetAdventure was not found by the scan; adding it directly"); list.Add(method); } MpPlugin.Log.LogInfo((object)$"Watching {list.Count} GetAdventure implementation(s) for the host's event choice"); return list; } public static Type RollLocally(Stage stage) { _hostIsDeciding = true; try { return stage.GetAdventure(); } finally { _hostIsDeciding = false; } } [HarmonyPrefix] private static bool Prefix(ref Type __result, out bool __state) { Type type = MpSafe.Run("AdventureSyncPatch", HostChoice, null); __state = type != null; if (type == null) { return true; } __result = type; return false; } [HarmonyPostfix] private static void Postfix(ref Type __result, bool __state) { if (__state || (!MpPlugin.ForceCombatEvents.Value && !MpPlugin.ForceDoremyEvent.Value)) { return; } Type type = MpSafe.Run("ForcedEvents", delegate { List list = ForcedEventNames(); if (list.Count == 0) { return (Type)null; } string text = list[_forcedIndex % list.Count]; Type type2 = TypeFactory.TryGetType(text); if (type2 == null) { MpPlugin.Log.LogWarning((object)("A Force* debug setting wanted '" + text + "' but this game does not have it")); return (Type)null; } _forcedIndex++; MpPlugin.Log.LogWarning((object)("A Force* debug setting is on: this event node is '" + text + "'")); return type2; }, null); if (type != null) { __result = type; } } private static List ForcedEventNames() { List list = new List(); if (MpPlugin.ForceCombatEvents.Value) { list.AddRange(CombatEventNames); } if (MpPlugin.ForceDoremyEvent.Value) { list.Add("DoremyPortal"); } return list; } private static Type HostChoice() { if (_hostIsDeciding || !MpSession.IsActive || !MpSession.IsInRun) { return null; } string pendingAdventureType = MapSync.PendingAdventureType; if (string.IsNullOrEmpty(pendingAdventureType)) { MpPlugin.Log.LogWarning((object)"No host adventure for this node; falling back to a local roll"); return null; } Type type = TypeFactory.TryGetType(pendingAdventureType); if (type == null) { MpPlugin.Log.LogWarning((object)("Host chose adventure '" + pendingAdventureType + "' which this client does not have")); return null; } MpPlugin.Log.LogInfo((object)("Running the host's event: " + pendingAdventureType)); return type; } } [HarmonyPatch(typeof(SelectStation), "OnEnter")] public static class SelectStationSyncPatch { private const int VisibleSlots = 3; internal static EnemyUnit[] Installed { get; private set; } public static void Reset() { Installed = null; } [HarmonyPostfix] private static void Postfix(SelectStation __instance) { MpSafe.Run("SelectStationSyncPatch", delegate { Installed = null; if (MpSession.IsActive && MpSession.IsInRun) { GameRunController gameRun = ((Station)__instance).GameRun; Stage stage = ((Station)__instance).Stage; if (((gameRun != null) ? gameRun.Player : null) != null && stage != null) { List list = BuildShortlist(gameRun, stage.Index); if (list == null) { MpPlugin.Log.LogWarning((object)"Not enough opponents to build a personal boss shortlist; using the game's own"); } else { List list2 = new List(); foreach (string item in list) { EnemyUnit val = Library.TryCreateEnemyUnit(item); if (val != null) { list2.Add(val); } } if (list2.Count != list.Count) { MpPlugin.Log.LogWarning((object)"Some of the boss shortlist could not be created; using the game's own"); } else { Installed = list2.ToArray(); __instance.Opponents = Installed; MpPlugin.Log.LogInfo((object)("Your boss shortlist: " + string.Join(", ", list.Take(3)) + " (random: " + list[3] + ")")); } } } } }); } private static List BuildShortlist(GameRunController gameRun, int stageIndex) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown string mine = ((GameEntity)gameRun.Player).Id; HashSet party = new HashSet(from p in MpSession.ConnectedPlayers select p.CharacterId into id where !string.IsNullOrEmpty(id) select id); party.Add(mine); RandomGen rng = new RandomGen(MpSession.RunSeed ^ (ulong)(-7046029254386353131L * (uint)(stageIndex + 1)) ^ (ulong)(-4417276706812531889L * (uint)(MpNet.LocalPlayerId + 1))); List collection = Library.EnumerateOpponentIds().ToList(); List pool = new List(collection); string text = Take(pool, (string id) => !party.Contains(id), rng) ?? Take(pool, (string id) => id != mine, rng); string text2 = Take(pool, (string id) => id != mine, rng); string text3 = Take(pool, (string id) => id != mine, rng); if (text == null || text2 == null || text3 == null) { return null; } string item = Take(new List(collection), (string id) => id != mine, rng) ?? text; return new List { text, text2, text3, item }; } private static string Take(List pool, Func allowed, RandomGen rng) { List list = pool.Where(allowed).ToList(); if (list.Count == 0) { return null; } string text = list[rng.NextInt(0, list.Count - 1)]; pool.Remove(text); return text; } } [HarmonyPatch(typeof(DialogStorage))] public static class RandomOpponentPatch { private const string IndexVariable = "$randomIndex"; private const string NameVariable = "$randomName"; [HarmonyPrefix] [HarmonyPatch("SetValue", new Type[] { typeof(string), typeof(float) })] private static void PrefixIndex(string variableName, ref float floatValue) { if (!(variableName != "$randomIndex") && MpSafe.Run("RandomOpponentPatch.Index", HiddenCandidate, null) != null) { floatValue = SelectStationSyncPatch.Installed.Length; } } [HarmonyPrefix] [HarmonyPatch("SetValue", new Type[] { typeof(string), typeof(string) })] private static void PrefixName(string variableName, ref string stringValue) { if (!(variableName != "$randomName")) { string text = MpSafe.Run("RandomOpponentPatch.Name", delegate { EnemyUnit obj = HiddenCandidate(); return (obj == null) ? null : ((Unit)obj).GetName().ToString(true, (NounCase)0, (UnitNameStyle)0); }, null); if (!string.IsNullOrEmpty(text)) { stringValue = text; } } } private static EnemyUnit HiddenCandidate() { EnemyUnit[] installed = SelectStationSyncPatch.Installed; if (!MpSession.IsActive || !MpSession.IsInRun || installed == null || installed.Length < 2) { return null; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentStation : null); } if (!(obj is SelectStation)) { return null; } return installed[^1]; } } [HarmonyPatch(typeof(Stage), "SetBoss")] public static class SetBossSyncPatch { internal static bool ApplyingRemote; internal static string LocalPick = string.Empty; internal static int LocalPickStage = -1; public static void Reset() { LocalPick = string.Empty; LocalPickStage = -1; } [HarmonyPrefix] private static bool Prefix(Stage __instance, string enemyGroupName) { return MpSafe.Run("SetBossSyncPatch", delegate { if (ApplyingRemote || !MpSession.IsActive || !MpSession.IsInRun) { return true; } if (__instance.IsSelectingBoss) { LocalPick = enemyGroupName; LocalPickStage = __instance.Index; } if (MpNet.IsHost) { MpNet.Send(new BossChosenMessage { StageIndex = __instance.Index, BossId = enemyGroupName }); return true; } MpPlugin.Log.LogInfo((object)("Ignoring local boss pick '" + enemyGroupName + "'; waiting for the host")); return false; }, fallback: true); } public static void ApplyHostChoice(int stageIndex, string bossId) { MpSafe.Run("SetBossSyncPatch.Apply", delegate { GameMaster instance = Singleton.Instance; GameRunController obj = ((instance != null) ? instance.CurrentGameRun : null); Stage val = ((obj != null) ? ((IEnumerable)obj.Stages).FirstOrDefault((Func)((Stage s) => s.Index == stageIndex)) : null); if (val != null && val.Boss == null) { ApplyingRemote = true; try { val.SetBoss(bossId); } finally { ApplyingRemote = false; } MpPlugin.Log.LogInfo((object)("Act boss set by the host: " + bossId)); } }); } } [HarmonyPatch(typeof(MapNodeWidget))] public static class BossMapIconPatch { private static string _applied = string.Empty; public static void Reset() { _applied = string.Empty; } private static string SettledBoss() { GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentStage : null); } Stage val = (Stage)obj; if (val == null || !val.IsSelectingBoss) { return string.Empty; } return val.SelectedBoss ?? string.Empty; } [HarmonyPrefix] [HarmonyPatch("SetBoss")] private static bool PrefixSetBoss(ref string bossId) { string text = MpSafe.Run("BossMapIconPatch.Prefix", delegate { if (!MpSession.IsActive || !MpSession.IsInRun) { return (string)null; } GameMaster instance = Singleton.Instance; object obj; if (instance == null) { obj = null; } else { GameRunController currentGameRun = instance.CurrentGameRun; obj = ((currentGameRun != null) ? currentGameRun.CurrentStage : null); } Stage val = (Stage)obj; return (val == null || !val.IsSelectingBoss) ? null : (val.SelectedBoss ?? string.Empty); }, null); if (text == null) { return true; } if (text.Length == 0) { return false; } bossId = text; return true; } [HarmonyPostfix] [HarmonyPatch("Initialize")] private static void PostfixInitialize(MapNodeWidget __instance, MapNode mapNode) { MpSafe.Run("BossMapIconPatch.Initialize", delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 if (MpSession.IsActive && MpSession.IsInRun && mapNode != null && (int)mapNode.StationType == 10) { string text = SettledBoss(); GameMap map = mapNode.Map; if (((map != null) ? map.BossId : null) == null && text.Length > 0) { __instance.SetBoss(text); } } }); } public static void Update() { MpSafe.Run("BossMapIconPatch.Update", delegate { if (MpSession.IsActive && MpSession.IsInRun) { string text = SettledBoss(); if (text.Length != 0 && !(text == _applied)) { MapPanel panel = UiManager.GetPanel(); MapNodeWidget val = ((panel != null) ? panel.FinalWidget : null); if (!((Object)(object)val == (Object)null)) { val.SetBoss(text); _applied = text; } } } }); } } [HarmonyPatch(typeof(GameRunController), "RollBossExhibits")] public static class BossExhibitChoicePatch { [HarmonyPrefix] private static void Prefix(GameRunController __instance, ref string bossId) { string current = bossId; string text = MpSafe.Run("BossExhibitChoicePatch", () => Substitute(__instance, current), null); if (!string.IsNullOrEmpty(text)) { MpPlugin.Log.LogInfo((object)("Boss reward drawn from your pick (" + text + ") rather than " + current)); bossId = text; } } private static string Substitute(GameRunController gameRun, string bossId) { if (!MpSession.IsActive || !MpSession.IsInRun || string.IsNullOrEmpty(SetBossSyncPatch.LocalPick) || SetBossSyncPatch.LocalPick == bossId) { return null; } Stage val = ((gameRun != null) ? gameRun.CurrentStage : null); if (val == null || !val.IsSelectingBoss || val.Index != SetBossSyncPatch.LocalPickStage || val.Boss == null || val.Boss.Id != bossId) { return null; } if (EnemyUnitConfig.FromId(SetBossSyncPatch.LocalPick) == null) { MpPlugin.Log.LogWarning((object)("No unit config for '" + SetBossSyncPatch.LocalPick + "'; keeping the boss's own reward")); return null; } return SetBossSyncPatch.LocalPick; } } } namespace LBOLMP.Net { public interface INetTransport { string Describe { get; } string LastError { get; } void Poll(); void Shutdown(string reason); } public enum NetRole { Offline, Host, Client } public static class MpNet { private static INetTransport _transport; private static readonly List _clients = new List(); private static NetConnection _serverLink; private static int _nextPlayerId = 1; private static readonly Dictionary>> Handlers = new Dictionary>>(); private static string _pendingConnectFailure; private static readonly Dictionary LastHeard = new Dictionary(); private static float _lastPumpTime = -1f; private const float PumpStallGraceSeconds = 2f; public static NetRole Role { get; private set; } = NetRole.Offline; public static bool IsOnline => Role != NetRole.Offline; public static bool IsHost => Role == NetRole.Host; public static bool IsClient => Role == NetRole.Client; public static int LocalPlayerId { get; internal set; } = -1; public static string LastError { get; private set; } public static string TransportName => _transport?.Describe ?? string.Empty; public static bool IsSteamSession => _transport is SteamTransport; public static NetConnection CurrentSource { get; private set; } public static IReadOnlyList Connections => _clients; public static event Action PeerDisconnected; public static event Action Disconnected; public static event Action ClientConnected; public static event Action ServerLinkReady; public static event Action ConnectFailed; public static bool StartHost(int port) { Shutdown("Restarting as host"); TcpTransport tcpTransport = new TcpTransport(); if (!tcpTransport.StartHost(port)) { LastError = tcpTransport.LastError; Shutdown("Host start failed"); return false; } BecomeHost(tcpTransport); return true; } public static bool StartSteamHost() { Shutdown("Restarting as host"); SteamTransport steamTransport = new SteamTransport(); if (!steamTransport.StartHost()) { LastError = steamTransport.LastError; Shutdown("Host start failed"); return false; } BecomeHost(steamTransport); return true; } private static void BecomeHost(INetTransport transport) { _transport = transport; Role = NetRole.Host; LocalPlayerId = 0; _nextPlayerId = 1; LastError = null; } public static bool StartClient(string address, int port) { Shutdown("Restarting as client"); TcpTransport tcpTransport = (TcpTransport)(_transport = new TcpTransport()); Role = NetRole.Client; LocalPlayerId = -1; if (!tcpTransport.StartClient(address, port)) { LastError = tcpTransport.LastError; Shutdown("Connect failed"); return false; } LastError = null; return true; } public static bool StartSteamClient(CSteamID host) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) Shutdown("Restarting as client"); SteamTransport steamTransport = (SteamTransport)(_transport = new SteamTransport()); Role = NetRole.Client; LocalPlayerId = -1; if (!steamTransport.StartClient(host)) { LastError = steamTransport.LastError; Shutdown("Connect failed"); return false; } LastError = null; return true; } public static void RegisterIncoming(NetConnection connection) { if (connection != null) { _clients.Add(connection); MpPlugin.Log.LogInfo((object)("Incoming connection from " + connection.RemoteEndPoint)); MpNet.ClientConnected?.Invoke(connection); } } public static void SetServerLink(NetConnection connection) { _serverLink = connection; MpNet.ServerLinkReady?.Invoke(); } public static void ReportConnectFailure(string reason) { LastError = reason; _pendingConnectFailure = reason ?? L10n.Encode(MpText.ReasonConnectionFailed); } public static NetConnection FindBySteamHandle(HSteamNetConnection handle) { //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) //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) if (_serverLink is SteamNetConnection steamNetConnection && steamNetConnection.Handle == handle) { return _serverLink; } for (int i = 0; i < _clients.Count; i++) { if (_clients[i] is SteamNetConnection steamNetConnection2 && steamNetConnection2.Handle == handle) { return _clients[i]; } } return null; } public static void Shutdown(string reason) { if (Role == NetRole.Offline && _transport == null && _serverLink == null && _clients.Count == 0) { return; } ManualLogSource log = MpPlugin.Log; if (log != null) { log.LogInfo((object)("Network shutdown: " + reason)); } foreach (NetConnection client in _clients) { client.Close(reason); } _clients.Clear(); _serverLink?.Close(reason); _serverLink = null; try { _transport?.Shutdown(reason); } catch (Exception ex) { ManualLogSource log2 = MpPlugin.Log; if (log2 != null) { log2.LogError((object)("Transport shutdown failed: " + ex.Message)); } } _transport = null; Role = NetRole.Offline; LocalPlayerId = -1; _pendingConnectFailure = null; LastHeard.Clear(); _lastPumpTime = -1f; } public static void Send(NetMessage message) { if (!IsOnline) { return; } message.SenderId = LocalPlayerId; if (IsHost) { Dispatch(message); if (MessageRegistry.IsRelayed(message)) { BroadcastRaw(MessageRegistry.Serialize(message), null); } } else { _serverLink?.Send(MessageRegistry.Serialize(message)); } } public static void SendToConnection(NetConnection connection, NetMessage message) { message.SenderId = LocalPlayerId; connection?.Send(MessageRegistry.Serialize(message)); } public static void SendToHostDirect(NetMessage message) { message.SenderId = LocalPlayerId; _serverLink?.Send(MessageRegistry.Serialize(message)); } private static void BroadcastRaw(byte[] payload, NetConnection except) { for (int i = 0; i < _clients.Count; i++) { NetConnection netConnection = _clients[i]; if (netConnection != except && netConnection.HandshakeComplete) { netConnection.Send(payload); } } } public static void Pump() { if (!IsOnline) { return; } AbsorbPumpStall(); if (_pendingConnectFailure != null) { string pendingConnectFailure = _pendingConnectFailure; _pendingConnectFailure = null; MpNet.ConnectFailed?.Invoke(pendingConnectFailure); return; } try { _transport?.Poll(); } catch (Exception ex) { MpPlugin.Log.LogError((object)("Transport poll failed: " + ex.Message)); } if (IsHost) { PumpClients(); } else { PumpServerLink(); } } private static void PumpClients() { for (int num = _clients.Count - 1; num >= 0; num--) { NetConnection netConnection = _clients[num]; netConnection.Poll(); byte[] result; while (netConnection.Inbox.TryDequeue(out result)) { NetMessage netMessage; try { netMessage = MessageRegistry.Deserialize(result); } catch (Exception ex) { MpPlugin.Log.LogError((object)("Dropping malformed message from " + netConnection.RemoteEndPoint + ": " + ex.Message)); continue; } netMessage.SenderId = netConnection.PlayerId; HostReceived(netConnection, netMessage); } if (netConnection.IsClosed) { _clients.RemoveAt(num); MpPlugin.Log.LogInfo((object)$"Client {netConnection.PlayerId} disconnected: {L10n.DecodeEn(netConnection.DisconnectReason)}"); MpNet.PeerDisconnected?.Invoke(netConnection.PlayerId, netConnection.DisconnectReason); } } } private static void HostReceived(NetConnection connection, NetMessage message) { LogVerbose($"host <- {message}"); CurrentSource = connection; try { Dispatch(message); } finally { CurrentSource = null; } if (MessageRegistry.IsRelayed(message) && connection.HandshakeComplete) { BroadcastRaw(MessageRegistry.Serialize(message), null); } } private static void PumpServerLink() { NetConnection serverLink = _serverLink; if (serverLink == null) { return; } serverLink.Poll(); byte[] result; while (serverLink.Inbox.TryDequeue(out result)) { NetMessage netMessage; try { netMessage = MessageRegistry.Deserialize(result); } catch (Exception ex) { MpPlugin.Log.LogError((object)("Dropping malformed message from host: " + ex.Message)); continue; } LogVerbose($"client <- {netMessage}"); Dispatch(netMessage); } if (serverLink.IsClosed) { string disconnectReason = serverLink.DisconnectReason; _serverLink = null; Shutdown("Link to host lost"); MpNet.Disconnected?.Invoke(disconnectReason); } } public static void On(Action handler) where T : NetMessage { if (!Handlers.TryGetValue(typeof(T), out var value)) { value = new List>(); Handlers[typeof(T)] = value; } value.Add(delegate(NetMessage m) { handler((T)m); }); } public static float SilenceFor(int playerId) { if (playerId == LocalPlayerId || !LastHeard.TryGetValue(playerId, out var value)) { return 0f; } return Math.Max(0f, Time.unscaledTime - value); } private static void AbsorbPumpStall() { float unscaledTime = Time.unscaledTime; float num = ((_lastPumpTime < 0f) ? 0f : (unscaledTime - _lastPumpTime)); _lastPumpTime = unscaledTime; if (!(num <= 2f) && LastHeard.Count != 0) { int[] array = new int[LastHeard.Count]; LastHeard.Keys.CopyTo(array, 0); int[] array2 = array; foreach (int key in array2) { LastHeard[key] += num; } } } private static void Dispatch(NetMessage message) { LastHeard[message.SenderId] = Time.unscaledTime; if (!Handlers.TryGetValue(message.GetType(), out var value)) { MpPlugin.Log.LogWarning((object)("No handler registered for " + message.GetType().Name)); return; } for (int i = 0; i < value.Count; i++) { try { value[i](message); } catch (Exception arg) { MpPlugin.Log.LogError((object)$"Handler for {message.GetType().Name} threw: {arg}"); } } } public static int AllocatePlayerId() { return _nextPlayerId++; } public static NetConnection FindConnection(int playerId) { return _clients.FirstOrDefault((NetConnection c) => c.PlayerId == playerId); } public static void Kick(int playerId, string reason) { FindConnection(playerId)?.Close(reason); } private static void LogVerbose(string text) { if (MpPlugin.VerboseLogging != null && MpPlugin.VerboseLogging.Value) { MpPlugin.Log.LogInfo((object)("[net] " + text)); } } } public sealed class NetWriter { private readonly MemoryStream _stream = new MemoryStream(256); private readonly BinaryWriter _writer; public NetWriter() { _writer = new BinaryWriter(_stream, Encoding.UTF8); } public void Bool(bool v) { _writer.Write(v); } public void Byte(byte v) { _writer.Write(v); } public void SByte(sbyte v) { _writer.Write(v); } public void Short(short v) { _writer.Write(v); } public void UShort(ushort v) { _writer.Write(v); } public void Int(int v) { _writer.Write(v); } public void UInt(uint v) { _writer.Write(v); } public void Long(long v) { _writer.Write(v); } public void ULong(ulong v) { _writer.Write(v); } public void Float(float v) { _writer.Write(v); } public void String(string v) { _writer.Write(v ?? string.Empty); } public void IntList(IReadOnlyList values) { if (values == null) { Int(0); return; } Int(values.Count); for (int i = 0; i < values.Count; i++) { Int(values[i]); } } public void StringList(IReadOnlyList values) { if (values == null) { Int(0); return; } Int(values.Count); for (int i = 0; i < values.Count; i++) { String(values[i]); } } public void Bytes(byte[] value) { if (value == null) { Int(0); return; } Int(value.Length); _writer.Write(value); } public byte[] ToArray() { _writer.Flush(); return _stream.ToArray(); } } public sealed class NetReader { private readonly MemoryStream _stream; private readonly BinaryReader _reader; public bool AtEnd => _stream.Position >= _stream.Length; public NetReader(byte[] data) { _stream = new MemoryStream(data ?? Array.Empty(), writable: false); _reader = new BinaryReader(_stream, Encoding.UTF8); } public bool Bool() { return _reader.ReadBoolean(); } public byte Byte() { return _reader.ReadByte(); } public sbyte SByte() { return _reader.ReadSByte(); } public short Short() { return _reader.ReadInt16(); } public ushort UShort() { return _reader.ReadUInt16(); } public int Int() { return _reader.ReadInt32(); } public uint UInt() { return _reader.ReadUInt32(); } public long Long() { return _reader.ReadInt64(); } public ulong ULong() { return _reader.ReadUInt64(); } public float Float() { return _reader.ReadSingle(); } public string String() { return _reader.ReadString(); } public int[] IntArray() { int num = Int(); int[] array = new int[num]; for (int i = 0; i < num; i++) { array[i] = Int(); } return array; } public string[] StringArray() { int num = Int(); string[] array = new string[num]; for (int i = 0; i < num; i++) { array[i] = String(); } return array; } public byte[] Bytes() { int count = Int(); return _reader.ReadBytes(count); } } public abstract class NetConnection { private int _closed; public readonly ConcurrentQueue Inbox = new ConcurrentQueue(); public string DisconnectReason { get; private set; } public bool IsClosed => Volatile.Read(in _closed) != 0; public int PlayerId { get; set; } = -1; public bool HandshakeComplete { get; set; } public string RemoteEndPoint { get; protected set; } = "unknown"; public void Send(byte[] payload) { if (IsClosed || payload == null) { return; } try { SendCore(payload); } catch (Exception ex) { Close(L10n.Encode(MpText.ReasonSendFailed, ex.Message)); } } public void Close(string reason) { if (Interlocked.Exchange(ref _closed, 1) != 0) { return; } DisconnectReason = reason; try { CloseCore(reason); } catch (Exception) { } } protected abstract void SendCore(byte[] payload); protected abstract void CloseCore(string reason); public virtual void Poll() { } } [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class NetMessageAttribute : Attribute { public ushort Id { get; } public bool RelayedByHost { get; set; } = true; public NetMessageAttribute(ushort id) { Id = id; } } public abstract class NetMessage { public int SenderId { get; set; } = -1; public abstract void Write(NetWriter w); public abstract void Read(NetReader r); public override string ToString() { return $"{GetType().Name}(from {SenderId})"; } } public static class MpConstants { public const int InvalidPlayerId = -1; public const int HostPlayerId = 0; public const int BroadcastPlayerId = -2; public const int DefaultDifficulty = 1; public const int DifficultyCount = 4; public const int ActCount = 4; } public static class MessageRegistry { private static readonly Dictionary IdToType = new Dictionary(); private static readonly Dictionary TypeToInfo = new Dictionary(); public static void RegisterAll(Assembly assembly) { Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (!type.IsAbstract && typeof(NetMessage).IsAssignableFrom(type)) { NetMessageAttribute customAttribute = type.GetCustomAttribute(); if (customAttribute == null) { throw new InvalidOperationException(type.FullName + " derives from NetMessage but has no [NetMessage] attribute"); } if (IdToType.TryGetValue(customAttribute.Id, out var value)) { throw new InvalidOperationException($"Duplicate net message id {customAttribute.Id}: {value.FullName} and {type.FullName}"); } IdToType[customAttribute.Id] = type; TypeToInfo[type] = customAttribute; } } MpPlugin.Log.LogInfo((object)$"Registered {IdToType.Count} network message types"); } public static ushort GetId(NetMessage message) { return GetInfo(message.GetType()).Id; } public static bool IsRelayed(NetMessage message) { return GetInfo(message.GetType()).RelayedByHost; } private static NetMessageAttribute GetInfo(Type type) { if (!TypeToInfo.TryGetValue(type, out var value)) { throw new InvalidOperationException("Message type " + type.FullName + " is not registered"); } return value; } public static byte[] Serialize(NetMessage message) { NetWriter netWriter = new NetWriter(); netWriter.UShort(GetId(message)); netWriter.Int(message.SenderId); message.Write(netWriter); return netWriter.ToArray(); } public static NetMessage Deserialize(byte[] payload) { NetReader netReader = new NetReader(payload); ushort num = netReader.UShort(); int senderId = netReader.Int(); if (!IdToType.TryGetValue(num, out var value)) { throw new InvalidOperationException($"Unknown network message id {num}"); } NetMessage obj = (NetMessage)Activator.CreateInstance(value); obj.SenderId = senderId; obj.Read(netReader); return obj; } } public static class SteamNet { private static bool _available; private static bool _callbacksReady; private static CSteamID _lobby = CSteamID.Nil; private static Callback _joinRequested; private static CallResult _lobbyCreated; private static CallResult _lobbyEntered; public static bool IsAvailable { get { //IL_0011: 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 (_available) { return true; } try { int available; if (SteamAPI.IsSteamRunning()) { CSteamID steamID = SteamUser.GetSteamID(); available = (((CSteamID)(ref steamID)).IsValid() ? 1 : 0); } else { available = 0; } _available = (byte)available != 0; } catch (Exception) { _available = false; } return _available; } } public static bool InLobby => ((CSteamID)(ref _lobby)).IsValid(); public static event Action JoinRequested; public static event Action LobbyReady; public static void EnsureCallbacks() { if (_callbacksReady || !IsAvailable) { return; } try { _joinRequested = Callback.Create((DispatchDelegate)OnJoinRequestedFromOverlay); _lobbyCreated = CallResult.Create((APIDispatchDelegate)OnLobbyCreated); _lobbyEntered = CallResult.Create((APIDispatchDelegate)OnLobbyEntered); _callbacksReady = true; MpPlugin.Log.LogInfo((object)"Steam invites are being listened for"); } catch (Exception ex) { MpPlugin.Log.LogWarning((object)("Could not register Steam callbacks: " + ex.Message)); } } public static void CreateLobby() { //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_001f: Unknown result type (might be due to invalid IL or missing references) if (IsAvailable) { EnsureCallbacks(); LeaveLobby(); SteamAPICall_t val = SteamMatchmaking.CreateLobby((ELobbyType)1, 4); _lobbyCreated.Set(val, (APIDispatchDelegate)null); } } private unsafe static void OnLobbyCreated(LobbyCreated_t result, bool failed) { //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) MpSafe.Run("SteamLobbyCreated", delegate { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //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_0071: 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_00a0: Unknown result type (might be due to invalid IL or missing references) if (failed || (int)result.m_eResult != 1) { MpPlugin.Log.LogError((object)("Could not create a Steam lobby: " + (failed ? "the call failed" : ((object)(*(EResult*)(&result.m_eResult))/*cast due to .constrained prefix*/).ToString()))); MpSession.StatusLine = L10n.Get(MpText.StatusSteamLobbyFailed); } else { _lobby = new CSteamID(result.m_ulSteamIDLobby); SteamMatchmaking.SetLobbyData(_lobby, "lbolmp", "1"); SteamMatchmaking.SetLobbyData(_lobby, "protocol", 34.ToString()); SteamMatchmaking.SetLobbyData(_lobby, "version", "0.8.9"); MpPlugin.Log.LogInfo((object)"Steam lobby open; friends can be invited"); SteamNet.LobbyReady?.Invoke(); } }); } public static bool OpenInviteDialog() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!IsAvailable || !((CSteamID)(ref _lobby)).IsValid()) { return false; } SteamFriends.ActivateGameOverlayInviteDialog(_lobby); return true; } private static void OnJoinRequestedFromOverlay(GameLobbyJoinRequested_t callback) { //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) MpSafe.Run("SteamJoinRequested", delegate { //IL_0010: 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) MpPlugin.Log.LogInfo((object)("Accepting a Steam invite from " + NameOf(callback.m_steamIDFriend))); JoinLobby(callback.m_steamIDLobby); }); } public static void JoinLobby(CSteamID lobby) { //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) if (IsAvailable && ((CSteamID)(ref lobby)).IsValid()) { EnsureCallbacks(); _lobbyEntered.Set(SteamMatchmaking.JoinLobby(lobby), (APIDispatchDelegate)null); } } private static void OnLobbyEntered(LobbyEnter_t result, bool failed) { //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) MpSafe.Run("SteamLobbyEntered", delegate { //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_003c: 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_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_0061: 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_0080: Unknown result type (might be due to invalid IL or missing references) if (failed) { MpPlugin.Log.LogError((object)"Could not enter the Steam lobby"); MpSession.StatusLine = L10n.Get(MpText.StatusSteamJoinFailed); } else { CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(result.m_ulSteamIDLobby); CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(val); if (lobbyOwner == SteamUser.GetSteamID()) { _lobby = val; } else { _lobby = val; MpPlugin.Log.LogInfo((object)("Entered " + NameOf(lobbyOwner) + "'s Steam lobby")); SteamNet.JoinRequested?.Invoke(lobbyOwner); } } }); } public static void LeaveLobby() { //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_000e: Unknown result type (might be due to invalid IL or missing references) if (((CSteamID)(ref _lobby)).IsValid()) { try { SteamMatchmaking.LeaveLobby(_lobby); } catch (Exception) { } _lobby = CSteamID.Nil; } } public static string NameOf(CSteamID id) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!IsAvailable || !((CSteamID)(ref id)).IsValid()) { return string.Empty; } try { string friendPersonaName = SteamFriends.GetFriendPersonaName(id); return string.IsNullOrEmpty(friendPersonaName) ? id.m_SteamID.ToString() : friendPersonaName; } catch (Exception) { return id.m_SteamID.ToString(); } } public static string LocalName() { if (!IsAvailable) { return string.Empty; } try { return SteamFriends.GetPersonaName(); } catch (Exception) { return string.Empty; } } } public sealed class SteamNetConnection : NetConnection { private const int MaxFrameBytes = 491520; private const int MaxMessagesPerPoll = 64; private readonly IntPtr[] _received = new IntPtr[64]; public HSteamNetConnection Handle { get; } public CSteamID RemoteId { get; } public SteamNetConnection(HSteamNetConnection handle, CSteamID remoteId) { //IL_0014: 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_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) Handle = handle; RemoteId = remoteId; string text = SteamNet.NameOf(remoteId); base.RemoteEndPoint = (string.IsNullOrEmpty(text) ? ("Steam:" + remoteId.m_SteamID) : text); } protected unsafe override void SendCore(byte[] payload) { //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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 if (payload.Length > 491520) { Close($"Frame of {payload.Length} bytes is too large for Steam messaging"); return; } IntPtr intPtr = Marshal.AllocHGlobal(payload.Length); try { Marshal.Copy(payload, 0, intPtr, payload.Length); long num = default(long); EResult val = SteamNetworkingSockets.SendMessageToConnection(Handle, intPtr, (uint)payload.Length, 8, ref num); if ((int)val != 1) { Close(L10n.Encode(MpText.ReasonSendFailed, ((object)(*(EResult*)(&val))/*cast due to .constrained prefix*/).ToString())); } } finally { Marshal.FreeHGlobal(intPtr); } } public override void Poll() { //IL_000a: 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_0048: 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_005e: Unknown result type (might be due to invalid IL or missing references) if (base.IsClosed) { return; } int num = SteamNetworkingSockets.ReceiveMessagesOnConnection(Handle, _received, 64); for (int i = 0; i < num; i++) { IntPtr intPtr = _received[i]; if (intPtr == IntPtr.Zero) { continue; } try { SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(intPtr); if (val.m_cbSize > 0) { byte[] array = new byte[val.m_cbSize]; Marshal.Copy(val.m_pData, array, 0, val.m_cbSize); Inbox.Enqueue(array); } } finally { SteamNetworkingMessage_t.Release(intPtr); } } } protected override void CloseCore(string reason) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) SteamNetworkingSockets.CloseConnection(Handle, 0, reason, true); } } public sealed class SteamTransport : INetTransport { private const int VirtualPort = 0; private HSteamListenSocket _listenSocket = HSteamListenSocket.Invalid; private HSteamNetConnection _outgoing = HSteamNetConnection.Invalid; private Callback _statusChanged; public string Describe { get; private set; } = string.Empty; public string LastError { get; private set; } public bool StartHost() { //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_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) if (!SteamNet.IsAvailable) { LastError = L10n.Encode(MpText.ErrorSteamUnavailable); return false; } try { SteamNetworkingUtils.InitRelayNetworkAccess(); _statusChanged = Callback.Create((DispatchDelegate)OnConnectionStatusChanged); _listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(0, 0, (SteamNetworkingConfigValue_t[])null); if (_listenSocket == HSteamListenSocket.Invalid) { LastError = L10n.Encode(MpText.ErrorSteamListenFailed); return false; } Describe = L10n.Encode(MpText.LobbyHostingSteam); MpPlugin.Log.LogInfo((object)"Listening for Steam connections"); return true; } catch (Exception ex) { LastError = ex.Message; MpPlugin.Log.LogError((object)("Failed to host over Steam: " + ex)); return false; } } public bool StartClient(CSteamID host) { //IL_003a: 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_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_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_008b: 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 (!SteamNet.IsAvailable) { LastError = L10n.Encode(MpText.ErrorSteamUnavailable); return false; } try { SteamNetworkingUtils.InitRelayNetworkAccess(); _statusChanged = Callback.Create((DispatchDelegate)OnConnectionStatusChanged); SteamNetworkingIdentity val = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref val)).SetSteamID(host); _outgoing = SteamNetworkingSockets.ConnectP2P(ref val, 0, 0, (SteamNetworkingConfigValue_t[])null); if (_outgoing == HSteamNetConnection.Invalid) { LastError = L10n.Encode(MpText.ErrorSteamConnectFailed); return false; } Describe = L10n.Encode(MpText.LobbyConnectedSteam, SteamNet.NameOf(host)); MpPlugin.Log.LogInfo((object)("Connecting to " + SteamNet.NameOf(host) + " over Steam")); return true; } catch (Exception ex) { LastError = ex.Message; MpPlugin.Log.LogError((object)("Failed to connect over Steam: " + ex)); return false; } } private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback) { //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) MpSafe.Run("SteamConnectionStatus", delegate { //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_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_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_001e: 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_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_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: Expected I4, but got Unknown //IL_00b9: 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_0056: 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_0079: 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_0061: 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_009b: 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) HSteamNetConnection hConn = callback.m_hConn; SteamNetConnectionInfo_t info = callback.m_info; bool flag = info.m_hListenSocket != HSteamListenSocket.Invalid; ESteamNetworkingConnectionState eState = info.m_eState; switch (eState - 1) { case 0: if (flag) { AcceptIncoming(hConn, info); } break; case 2: if (flag) { MpNet.RegisterIncoming(new SteamNetConnection(hConn, ((SteamNetworkingIdentity)(ref info.m_identityRemote)).GetSteamID())); } else if (hConn == _outgoing) { MpPlugin.Log.LogInfo((object)"Steam connection established"); MpNet.SetServerLink(new SteamNetConnection(hConn, ((SteamNetworkingIdentity)(ref info.m_identityRemote)).GetSteamID())); } break; case 3: case 4: HandleDrop(hConn, info); break; case 1: break; } }); } private unsafe void AcceptIncoming(HSteamNetConnection handle, SteamNetConnectionInfo_t info) { //IL_004d: 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_0053: 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_0056: Invalid comparison between Unknown and I4 //IL_000d: 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_0079: Unknown result type (might be due to invalid IL or missing references) if (MpNet.Connections.Count >= 3) { SteamNetworkingSockets.CloseConnection(handle, 0, L10n.Encode(MpText.ReasonSessionFull), false); MpPlugin.Log.LogInfo((object)("Refused a Steam connection from " + SteamNet.NameOf(((SteamNetworkingIdentity)(ref info.m_identityRemote)).GetSteamID()) + ": session is full")); return; } EResult val = SteamNetworkingSockets.AcceptConnection(handle); if ((int)val != 1) { MpPlugin.Log.LogWarning((object)("Could not accept a Steam connection: " + ((object)(*(EResult*)(&val))/*cast due to .constrained prefix*/).ToString())); SteamNetworkingSockets.CloseConnection(handle, 0, "Accept failed", false); } } private void HandleDrop(HSteamNetConnection handle, SteamNetConnectionInfo_t info) { //IL_0024: 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_0055: 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_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) string text = (string.IsNullOrEmpty(((SteamNetConnectionInfo_t)(ref info)).m_szEndDebug) ? L10n.Encode(MpText.ReasonSteamClosed) : ((SteamNetConnectionInfo_t)(ref info)).m_szEndDebug); NetConnection netConnection = MpNet.FindBySteamHandle(handle); if (netConnection != null) { netConnection.Close(text); return; } MpPlugin.Log.LogWarning((object)("Steam connection failed: " + text)); SteamNetworkingSockets.CloseConnection(handle, 0, text, false); if (handle == _outgoing) { _outgoing = HSteamNetConnection.Invalid; MpNet.ReportConnectFailure(text); } } public void Poll() { } public void Shutdown(string reason) { //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_002f: 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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (_listenSocket != HSteamListenSocket.Invalid) { try { SteamNetworkingSockets.CloseListenSocket(_listenSocket); } catch (Exception) { } _listenSocket = HSteamListenSocket.Invalid; } _outgoing = HSteamNetConnection.Invalid; try { _statusChanged?.Dispose(); } catch (Exception) { } _statusChanged = null; SteamNet.LeaveLobby(); } } public sealed class TcpNetConnection : NetConnection { private const int MaxFrameBytes = 4194304; private readonly TcpClient _client; private readonly NetworkStream _stream; private readonly BlockingCollection _outbox = new BlockingCollection(new ConcurrentQueue()); public TcpNetConnection(TcpClient client) { _client = client; _client.NoDelay = true; _stream = client.GetStream(); base.RemoteEndPoint = client.Client.RemoteEndPoint?.ToString() ?? "unknown"; Thread thread = new Thread(ReadLoop); thread.IsBackground = true; thread.Name = "LBOLMP-Read"; thread.Start(); Thread thread2 = new Thread(WriteLoop); thread2.IsBackground = true; thread2.Name = "LBOLMP-Write"; thread2.Start(); } protected override void SendCore(byte[] payload) { try { _outbox.Add(payload); } catch (InvalidOperationException) { } } private void WriteLoop() { try { foreach (byte[] item in _outbox.GetConsumingEnumerable()) { byte[] buffer = new byte[4] { (byte)(item.Length & 0xFF), (byte)((item.Length >> 8) & 0xFF), (byte)((item.Length >> 16) & 0xFF), (byte)((item.Length >> 24) & 0xFF) }; _stream.Write(buffer, 0, 4); _stream.Write(item, 0, item.Length); _stream.Flush(); } } catch (Exception ex) { Close(L10n.Encode(MpText.ReasonWriteFailed, ex.Message)); } } private void ReadLoop() { byte[] array = new byte[4]; try { while (!base.IsClosed) { if (!ReadExactly(array, 4)) { Close(L10n.Encode(MpText.ReasonRemoteClosed)); break; } int num = array[0] | (array[1] << 8) | (array[2] << 16) | (array[3] << 24); if (num < 0 || num > 4194304) { Close($"Bad frame length {num}"); break; } byte[] array2 = new byte[num]; if (!ReadExactly(array2, num)) { Close("Remote closed mid-frame"); break; } Inbox.Enqueue(array2); } } catch (Exception ex) { Close(L10n.Encode(MpText.ReasonReadFailed, ex.Message)); } } private bool ReadExactly(byte[] buffer, int count) { int num; for (int i = 0; i < count; i += num) { num = _stream.Read(buffer, i, count - i); if (num <= 0) { return false; } } return true; } protected override void CloseCore(string reason) { try { _outbox.CompleteAdding(); } catch (Exception) { } try { _stream.Close(); } catch (Exception) { } try { _client.Close(); } catch (Exception) { } } } public sealed class TcpTransport : INetTransport { private TcpListener _listener; public string Describe { get; private set; } = string.Empty; public string LastError { get; private set; } public bool StartHost(int port) { try { _listener = new TcpListener(IPAddress.Any, port); _listener.Start(); Describe = L10n.Encode(MpText.LobbyHostingDirectIp, port); MpPlugin.Log.LogInfo((object)$"Hosting on port {port}"); return true; } catch (Exception ex) { LastError = ex.Message; MpPlugin.Log.LogError((object)$"Failed to host on port {port}: {ex.Message}"); return false; } } public bool StartClient(string address, int port) { try { TcpClient tcpClient = new TcpClient(); IAsyncResult asyncResult = tcpClient.BeginConnect(address, port, null, null); if (!asyncResult.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5.0))) { tcpClient.Close(); throw new TimeoutException(L10n.Encode(MpText.ReasonTimedOut, address, port)); } tcpClient.EndConnect(asyncResult); Describe = L10n.Encode(MpText.LobbyConnectedDirectIp, address, port); MpNet.SetServerLink(new TcpNetConnection(tcpClient)); MpPlugin.Log.LogInfo((object)$"Connected to {address}:{port}"); return true; } catch (Exception ex) { LastError = ex.Message; MpPlugin.Log.LogError((object)$"Failed to connect to {address}:{port}: {L10n.DecodeEn(ex.Message)}"); return false; } } public void Poll() { try { while (_listener != null && _listener.Pending()) { MpNet.RegisterIncoming(new TcpNetConnection(_listener.AcceptTcpClient())); } } catch (Exception ex) { MpPlugin.Log.LogError((object)("Accept failed: " + ex.Message)); } } public void Shutdown(string reason) { try { _listener?.Stop(); } catch (Exception) { } _listener = null; } } } namespace LBOLMP.Entities { public sealed class MpResilientDefinition : StatusEffectTemplate { private static DirectorySource _source; private static DirectorySource Source { get { //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_001e: Expected O, but got Unknown object obj = _source; if (obj == null) { DirectorySource val = new DirectorySource("rokk.lbol.multiplayer.LBOLMP", ""); _source = val; obj = (object)val; } return (DirectorySource)obj; } } public override IdContainer GetId() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return IdContainer.op_Implicit("MpResilient"); } public override LocalizationOption LoadLocalization() { //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_0017: 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: Expected O, but got Unknown LocalizationFiles val = new LocalizationFiles((IResourceSource)(object)Source, (Locale)0); val.AddLocaleFile((Locale)0, "Resources/StatusEffectsEn.yaml"); val.AddLocaleFile((Locale)1, "Resources/StatusEffectsZhHans.yaml"); val.AddLocaleFile((Locale)2, "Resources/StatusEffectsZhHant.yaml"); return (LocalizationOption)val; } public override Sprite LoadSprite() { return ResourceLoader.LoadSprite("Resources/MpResilient.png", (IResourceSource)(object)Source, (Rect?)null, 1, (Vector2?)null); } public override StatusEffectConfig MakeConfig() { StatusEffectConfig obj = ((StatusEffectTemplate)this).DefaultConfig(); obj.Type = (StatusEffectType)0; obj.HasLevel = true; obj.LevelStackType = (StackType)1; obj.HasDuration = false; obj.RelativeEffects = new List { "Weak", "Vulnerable", "LockedOn", "FirepowerNegative" }; return obj; } } [EntityLogic(typeof(MpResilientDefinition))] public sealed class MpResilient : StatusEffect { protected override void OnAdded(Unit unit) { ((StatusEffect)this).HandleOwnerEvent(unit.TurnEnded, (GameEventHandler)OnTurnEnded); } private void OnTurnEnded(UnitEventArgs args) { MpSafe.Run("MpResilient.TurnEnded", delegate { int level = ((StatusEffect)this).Level; if (level > 0 && ((StatusEffect)this).Owner != null) { bool flag = false; foreach (StatusEffect item in ((StatusEffect)this).Owner.StatusEffects.ToList()) { if (item is Weak || item is Vulnerable || item is LockedOn) { flag |= Wither(item, level); } } if (flag) { ((StatusEffect)this).NotifyActivating(); } } }); } private bool Wither(StatusEffect effect, int extra) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown if (effect.HasDuration) { if (effect.Duration <= 0) { return false; } effect.Duration = Mathf.Max(0, effect.Duration - extra); if (effect.Duration == 0) { ((GameEntity)this).React(Reactor.op_Implicit((BattleAction)new RemoveStatusEffectAction(effect, true, 0.1f))); } return true; } if (effect.HasLevel) { if (effect.Level <= 0) { return false; } effect.Level = Mathf.Max(0, effect.Level - extra); if (effect.Level == 0) { ((GameEntity)this).React(Reactor.op_Implicit((BattleAction)new RemoveStatusEffectAction(effect, true, 0.1f))); } return true; } return false; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }