using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using InControl;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace RoundsReconnect
{
internal sealed class ReconnectCallbacks : MonoBehaviourPunCallbacks
{
public override void OnJoinedRoom()
{
ReconnectManager.Instance?.OnJoinedRoom();
((MonoBehaviourPunCallbacks)this).OnJoinedRoom();
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
ReconnectManager.Instance?.OnPlayerEnteredRoom(newPlayer);
((MonoBehaviourPunCallbacks)this).OnPlayerEnteredRoom(newPlayer);
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
ReconnectManager.Instance?.OnPlayerLeftRoom(otherPlayer);
((MonoBehaviourPunCallbacks)this).OnPlayerLeftRoom(otherPlayer);
}
}
internal sealed class ReconnectManager : MonoBehaviour
{
internal const string PlayerSteamKey = "rr.steam";
internal const string RoomSlotsKey = "rr.slots";
internal const string RoomGameActiveKey = "rr.active";
private string localSteamId;
internal static ReconnectManager Instance { get; private set; }
private void Awake()
{
Instance = this;
Object.DontDestroyOnLoad((Object)(object)this);
}
internal void PublishLocalSteamId()
{
if (PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null)
{
localSteamId = SteamIdentity.LocalSteamId();
SetPlayerProperty(PhotonNetwork.LocalPlayer, "rr.steam", localSteamId);
}
}
internal void SetGameActive(bool active)
{
if (IsPrivateRwfRoom() && PhotonNetwork.IsMasterClient)
{
SetRoomProperty("rr.active", active);
if (active)
{
RefreshSlotsFromLivePlayers();
}
}
}
internal bool IsGameActive()
{
if (PhotonNetwork.CurrentRoom != null && ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"rr.active", out object value) && value is bool)
{
return (bool)value;
}
return false;
}
internal bool IsPrivateRwfRoom()
{
if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && !PhotonNetwork.OfflineMode)
{
return !PhotonNetwork.CurrentRoom.IsVisible;
}
return false;
}
internal void OnJoinedRoom()
{
PublishLocalSteamId();
RequestClaimIfNeeded();
}
internal void OnPlayerEnteredRoom(Player player)
{
if (IsPrivateRwfRoom() && PhotonNetwork.IsMasterClient && player != null)
{
ResolveClaims();
}
}
internal void OnPlayerLeftRoom(Player player)
{
if (!IsPrivateRwfRoom() || !PhotonNetwork.IsMasterClient || player == null || player.IsMasterClient)
{
return;
}
List<SlotRecord> slots = GetSlots();
bool flag = false;
foreach (SlotRecord item in slots.Where((SlotRecord slot) => slot.OwnerActor == player.ActorNumber))
{
item.State = SlotState.Abandoned;
item.ClaimActor = 0;
flag = true;
}
if (flag)
{
SetSlots(slots);
RoundsReconnectPlugin.Log.LogInfo((object)$"Actor {player.ActorNumber} left; slot marked abandoned");
}
}
internal void BeforeCardPick(int pickerId)
{
if (IsPrivateRwfRoom())
{
PublishLocalSteamId();
RequestClaimIfNeeded();
if (PhotonNetwork.IsMasterClient)
{
RefreshSlotsFromLivePlayers();
MarkDisconnectedOwners();
ResolveClaims();
TransferClaimIfPicker(pickerId);
ConvertClaimableSlotsToBots();
}
ApplyRoomSlotsToLocalObjects();
}
}
internal void MarkLocalSlotsAbandonedBeforeLeave()
{
if (!IsPrivateRwfRoom() || PhotonNetwork.IsMasterClient || !IsGameActive())
{
return;
}
PublishLocalSteamId();
List<SlotRecord> slots = GetSlots();
bool flag = false;
foreach (Player player in LivePlayers())
{
if (!((Object)(object)player.data == (Object)null) && !((Object)(object)player.data.view == (Object)null) && player.data.view.OwnerActorNr == PhotonNetwork.LocalPlayer.ActorNumber)
{
SlotRecord slotRecord = slots.FirstOrDefault((SlotRecord candidate) => candidate.PlayerId == player.playerID);
if (slotRecord == null)
{
slotRecord = SlotFromPlayer(player);
slots.Add(slotRecord);
}
slotRecord.SteamId = localSteamId;
slotRecord.OwnerActor = PhotonNetwork.LocalPlayer.ActorNumber;
slotRecord.State = SlotState.Abandoned;
slotRecord.ClaimActor = 0;
flag = true;
}
}
if (flag)
{
SetSlots(slots);
RoundsReconnectPlugin.Log.LogInfo((object)"Local player slot marked abandoned before leave");
}
}
internal bool ShouldBlockLobbyReady()
{
if (!IsPrivateRwfRoom() || !IsGameActive())
{
return false;
}
string steamId = localSteamId ?? SteamIdentity.LocalSteamId();
return GetSlots().Any((SlotRecord slot) => slot.IsClaimable && slot.SteamId == steamId);
}
private void RefreshSlotsFromLivePlayers()
{
if (!IsPrivateRwfRoom() || !PhotonNetwork.IsMasterClient)
{
return;
}
List<SlotRecord> slots = GetSlots();
bool flag = false;
foreach (Player player in LivePlayers())
{
SlotRecord slotRecord = slots.FirstOrDefault((SlotRecord candidate) => candidate.PlayerId == player.playerID);
if (slotRecord == null)
{
slots.Add(SlotFromPlayer(player));
flag = true;
}
else if (slotRecord.State == SlotState.Human)
{
slotRecord.TeamId = player.teamID;
slotRecord.ViewId = player.data.view.ViewID;
slotRecord.OwnerActor = player.data.view.OwnerActorNr;
flag = true;
}
}
if (flag)
{
SetSlots(slots);
}
}
private void MarkDisconnectedOwners()
{
List<SlotRecord> slots = GetSlots();
bool flag = false;
foreach (SlotRecord item in slots.Where((SlotRecord slot) => slot.State == SlotState.Human))
{
if (!PhotonNetwork.CurrentRoom.Players.ContainsKey(item.OwnerActor))
{
item.State = SlotState.Abandoned;
item.ClaimActor = 0;
flag = true;
}
}
if (flag)
{
SetSlots(slots);
}
}
private void ResolveClaims()
{
List<SlotRecord> slots = GetSlots();
bool flag = false;
foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values)
{
string steamId = GetPlayerProperty<string>(value, "rr.steam");
if (!string.IsNullOrEmpty(steamId))
{
SlotRecord slotRecord = slots.FirstOrDefault((SlotRecord candidate) => candidate.IsClaimable && candidate.SteamId == steamId);
if (slotRecord != null && slotRecord.ClaimActor != value.ActorNumber)
{
slotRecord.ClaimActor = value.ActorNumber;
flag = true;
RoundsReconnectPlugin.Log.LogInfo((object)$"Actor {value.ActorNumber} queued to reclaim player {slotRecord.PlayerId}");
}
}
}
if (flag)
{
SetSlots(slots);
}
}
private void TransferClaimIfPicker(int pickerId)
{
List<SlotRecord> slots = GetSlots();
SlotRecord slotRecord = slots.FirstOrDefault((SlotRecord candidate) => candidate.PlayerId == pickerId && candidate.ClaimActor > 0);
if (slotRecord != null && PhotonNetwork.CurrentRoom.Players.ContainsKey(slotRecord.ClaimActor))
{
Player val = FindPlayer(slotRecord.PlayerId);
if (!((Object)(object)val == (Object)null) && !((Object)(object)val.data == (Object)null) && !((Object)(object)val.data.view == (Object)null))
{
TryTransferOwnership(val, slotRecord.ClaimActor);
slotRecord.OwnerActor = slotRecord.ClaimActor;
slotRecord.ClaimActor = 0;
slotRecord.State = SlotState.Human;
SetSlots(slots);
RoundsReconnectPlugin.Log.LogInfo((object)$"Player {slotRecord.PlayerId} reclaimed by actor {slotRecord.OwnerActor}");
}
}
}
private void ConvertClaimableSlotsToBots()
{
bool flag = false;
List<SlotRecord> slots = GetSlots();
foreach (SlotRecord item in slots.Where((SlotRecord candidate) => candidate.IsClaimable))
{
Player val = FindPlayer(item.PlayerId);
if (!((Object)(object)val == (Object)null))
{
RoundsWithBotsBridge.ConvertToBot(val);
if (item.State != SlotState.Bot)
{
item.State = SlotState.Bot;
flag = true;
}
}
}
if (flag)
{
SetSlots(slots);
RoundsWithBotsBridge.RefreshBotPickerList();
}
}
private void ApplyRoomSlotsToLocalObjects()
{
foreach (SlotRecord slot in GetSlots())
{
Player val = FindPlayer(slot.PlayerId);
if (!((Object)(object)val == (Object)null))
{
if (slot.State == SlotState.Bot || slot.State == SlotState.Abandoned)
{
RoundsWithBotsBridge.ConvertToBot(val);
}
else if (slot.State == SlotState.Human && slot.OwnerActor == PhotonNetwork.LocalPlayer.ActorNumber)
{
RoundsWithBotsBridge.ConvertToHuman(val);
}
}
}
}
private void RequestClaimIfNeeded()
{
if (IsPrivateRwfRoom() && IsGameActive())
{
string steamId = localSteamId ?? SteamIdentity.LocalSteamId();
if (GetSlots().Any((SlotRecord slot) => slot.IsClaimable && slot.SteamId == steamId))
{
SetPlayerProperty(PhotonNetwork.LocalPlayer, "rr.steam", steamId);
}
}
}
private SlotRecord SlotFromPlayer(Player player)
{
int num = (((Object)(object)player.data?.view != (Object)null) ? player.data.view.OwnerActorNr : 0);
return new SlotRecord
{
SteamId = (GetPlayerProperty<string>(PhotonNetwork.CurrentRoom.GetPlayer(num), "rr.steam") ?? string.Empty),
PlayerId = player.playerID,
TeamId = player.teamID,
OwnerActor = num,
ClaimActor = 0,
ViewId = (((Object)(object)player.data?.view != (Object)null) ? player.data.view.ViewID : 0),
State = SlotState.Human
};
}
private static void TryTransferOwnership(Player player, int actorNumber)
{
try
{
player.data.view.TransferOwnership(actorNumber);
}
catch (Exception ex)
{
RoundsReconnectPlugin.Log.LogWarning((object)$"Ownership transfer failed for player {player.playerID}: {ex.Message}");
}
}
private static Player FindPlayer(int playerId)
{
return PlayerManager.instance?.players?.FirstOrDefault((Func<Player, bool>)((Player player) => (Object)(object)player != (Object)null && player.playerID == playerId));
}
private static IEnumerable<Player> LivePlayers()
{
return PlayerManager.instance?.players?.Where((Player player) => (Object)(object)player != (Object)null) ?? Enumerable.Empty<Player>();
}
private static List<SlotRecord> GetSlots()
{
if (PhotonNetwork.CurrentRoom == null || !((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"rr.slots", out object value))
{
return new List<SlotRecord>();
}
return SlotRecord.DeserializeMany(value as string);
}
private static void SetSlots(IEnumerable<SlotRecord> slots)
{
SetRoomProperty("rr.slots", SlotRecord.SerializeMany(slots));
}
private static void SetRoomProperty(string key, object value)
{
//IL_000d: 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_001a: Expected O, but got Unknown
//IL_0021: Expected O, but got Unknown
if (PhotonNetwork.CurrentRoom != null)
{
Room currentRoom = PhotonNetwork.CurrentRoom;
Hashtable val = new Hashtable();
((Dictionary<object, object>)val).Add((object)key, value);
currentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
}
}
private static void SetPlayerProperty(Player player, string key, object value)
{
//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: Expected O, but got Unknown
//IL_0018: Expected O, but got Unknown
if (player != null)
{
Hashtable val = new Hashtable();
((Dictionary<object, object>)val).Add((object)key, value);
player.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
}
}
private static T GetPlayerProperty<T>(Player player, string key)
{
if (player == null)
{
return default(T);
}
if (!((Dictionary<object, object>)(object)player.CustomProperties).TryGetValue((object)key, out object value) || !(value is T))
{
return default(T);
}
return (T)value;
}
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("com.joema.rounds.roundsreconnect", "Rounds Reconnect", "0.1.0")]
[BepInProcess("Rounds.exe")]
public sealed class RoundsReconnectPlugin : BaseUnityPlugin
{
public const string ModId = "com.joema.rounds.roundsreconnect";
public const string ModName = "Rounds Reconnect";
public const string Version = "0.1.0";
internal static RoundsReconnectPlugin Instance { get; private set; }
internal static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
new Harmony("com.joema.rounds.roundsreconnect").PatchAll();
((Component)this).gameObject.AddComponent<ReconnectCallbacks>();
((Component)this).gameObject.AddComponent<ReconnectManager>();
((BaseUnityPlugin)this).Logger.LogInfo((object)"Rounds Reconnect 0.1.0 loaded");
}
}
internal static class RoundsWithBotsBridge
{
private static Type characterDataExtensionType;
private static Type botManagerType;
public static void SetIsBot(CharacterData data, bool isBot)
{
try
{
characterDataExtensionType = characterDataExtensionType ?? AccessTools.TypeByName("RoundsWithBots.Extensions.CharacterDataExtension");
object obj = characterDataExtensionType?.GetMethod("GetAdditionalData")?.Invoke(null, new object[1] { data });
if (obj != null)
{
AccessTools.Property(obj.GetType(), "IsBot")?.SetValue(obj, isBot, null);
}
}
catch (Exception ex)
{
RoundsReconnectPlugin.Log.LogWarning((object)$"Could not set RoundsWithBots IsBot={isBot}: {ex.Message}");
}
}
public static void RefreshBotPickerList()
{
try
{
botManagerType = botManagerType ?? AccessTools.TypeByName("RoundsWithBots.BotAIManager");
object obj = AccessTools.Field(botManagerType, "Instance")?.GetValue(null);
AccessTools.Method(botManagerType, "SetBotsId", (Type[])null, (Type[])null)?.Invoke(obj, null);
}
catch (Exception ex)
{
RoundsReconnectPlugin.Log.LogDebug((object)("Could not refresh bot picker list: " + ex.Message));
}
}
public static void ConvertToBot(Player player)
{
if (!((Object)(object)player == (Object)null) && !((Object)(object)player.data == (Object)null))
{
CharacterData data = player.data;
data.SetAI((Player)null);
data.input.controlledElseWhere = true;
PlayerAPI component = ((Component)player).GetComponent<PlayerAPI>();
if ((Object)(object)component != (Object)null)
{
((Behaviour)component).enabled = true;
}
if ((Object)(object)((Component)player).GetComponentInChildren<PlayerAIPhilip>(true) == (Object)null)
{
((Component)player).gameObject.AddComponent<PlayerAIPhilip>();
}
SetIsBot(data, isBot: true);
}
}
public static void ConvertToHuman(Player player)
{
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)player == (Object)null) && !((Object)(object)player.data == (Object)null))
{
PlayerAIPhilip[] componentsInChildren = ((Component)player).GetComponentsInChildren<PlayerAIPhilip>(true);
for (int i = 0; i < componentsInChildren.Length; i++)
{
Object.Destroy((Object)(object)componentsInChildren[i]);
}
CharacterData data = player.data;
data.input.controlledElseWhere = false;
data.input.inputType = (InputType)1;
data.playerActions = PlayerActions.CreateWithKeyboardBindings();
((PlayerActionSet)data.playerActions).Device = null;
PlayerAPI component = ((Component)player).GetComponent<PlayerAPI>();
if ((Object)(object)component != (Object)null)
{
((Behaviour)component).enabled = false;
}
SetIsBot(data, isBot: false);
}
}
}
internal enum SlotState
{
Human,
Abandoned,
Bot,
Claimed
}
internal sealed class SlotRecord
{
public string SteamId;
public int PlayerId;
public int TeamId;
public int OwnerActor;
public int ClaimActor;
public int ViewId;
public SlotState State;
public bool IsClaimable
{
get
{
if (State != SlotState.Abandoned)
{
return State == SlotState.Bot;
}
return true;
}
}
public string Serialize()
{
string[] obj = new string[7]
{
Escape(SteamId),
PlayerId.ToString(),
TeamId.ToString(),
OwnerActor.ToString(),
ClaimActor.ToString(),
ViewId.ToString(),
null
};
int state = (int)State;
obj[6] = state.ToString();
return string.Join("|", obj);
}
public static SlotRecord Deserialize(string value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}
string[] array = value.Split(new char[1] { '|' });
if (array.Length != 7)
{
return null;
}
if (!int.TryParse(array[1], out var result) || !int.TryParse(array[2], out var result2) || !int.TryParse(array[3], out var result3) || !int.TryParse(array[4], out var result4) || !int.TryParse(array[5], out var result5) || !int.TryParse(array[6], out var result6))
{
return null;
}
return new SlotRecord
{
SteamId = Unescape(array[0]),
PlayerId = result,
TeamId = result2,
OwnerActor = result3,
ClaimActor = result4,
ViewId = result5,
State = (SlotState)result6
};
}
public static string SerializeMany(IEnumerable<SlotRecord> slots)
{
return string.Join(";", (from slot in slots
orderby slot.PlayerId
select slot.Serialize()).ToArray());
}
public static List<SlotRecord> DeserializeMany(string value)
{
if (string.IsNullOrEmpty(value))
{
return new List<SlotRecord>();
}
return (from slot in value.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(Deserialize)
where slot != null
select slot).ToList();
}
private static string Escape(string value)
{
return (value ?? string.Empty).Replace("%", "%25").Replace("|", "%7C").Replace(";", "%3B");
}
private static string Unescape(string value)
{
return (value ?? string.Empty).Replace("%3B", ";").Replace("%7C", "|").Replace("%25", "%");
}
}
internal static class SteamIdentity
{
public static string LocalSteamId()
{
try
{
object obj = AccessTools.TypeByName("Steamworks.SteamUser")?.GetMethod("GetSteamID")?.Invoke(null, null);
if (obj != null)
{
return obj.ToString();
}
}
catch (Exception ex)
{
RoundsReconnectPlugin.Log.LogDebug((object)("Steam ID lookup failed: " + ex.Message));
}
Player localPlayer = PhotonNetwork.LocalPlayer;
object obj2 = ((localPlayer != null) ? localPlayer.UserId : null);
if (obj2 == null)
{
Player localPlayer2 = PhotonNetwork.LocalPlayer;
obj2 = ((localPlayer2 != null) ? localPlayer2.NickName : null) ?? string.Empty;
}
return (string)obj2;
}
}
}
namespace RoundsReconnect.Patches
{
[HarmonyPatch(typeof(CardChoice), "DoPick")]
internal static class CardChoiceDoPickPatch
{
private static void Prefix(int picketIDToSet)
{
ReconnectManager.Instance?.BeforeCardPick(picketIDToSet);
}
}
[HarmonyPatch(typeof(CardChoiceVisuals), "Show")]
internal static class CardChoiceVisualsShowPatch
{
private static void Prefix(int pickerID)
{
ReconnectManager.Instance?.BeforeCardPick(pickerID);
}
}
[HarmonyPatch(typeof(NetworkConnectionHandler), "CreateRoom")]
internal static class NetworkConnectionHandlerCreateRoomPatch
{
private static void Prefix(RoomOptions roomOptions)
{
if (roomOptions != null)
{
roomOptions.CleanupCacheOnLeave = false;
roomOptions.PlayerTtl = 600000;
roomOptions.EmptyRoomTtl = 600000;
}
}
}
[HarmonyPatch(typeof(NetworkConnectionHandler), "NetworkRestart")]
internal static class NetworkConnectionHandlerRestartPatch
{
private static void Prefix()
{
ReconnectManager.Instance?.MarkLocalSlotsAbandonedBeforeLeave();
}
}
[HarmonyPatch]
internal static class RwfGameModeStartGamePatch
{
private static MethodBase TargetMethod()
{
return AccessTools.Method(AccessTools.TypeByName("RWF.GameModes.RWFGameMode"), "StartGame", (Type[])null, (Type[])null);
}
private static void Postfix()
{
ReconnectManager.Instance?.SetGameActive(active: true);
}
}
[HarmonyPatch]
internal static class RwfGameModeGameOverPatch
{
private static MethodBase TargetMethod()
{
return AccessTools.Method(AccessTools.TypeByName("RWF.GameModes.RWFGameMode"), "GameOver", new Type[1] { typeof(int[]) }, (Type[])null);
}
private static void Prefix()
{
ReconnectManager.Instance?.SetGameActive(active: false);
}
}
[HarmonyPatch]
internal static class RwfPrivateRoomToggleReadyPatch
{
private static MethodBase TargetMethod()
{
return AccessTools.Method(AccessTools.TypeByName("RWF.PrivateRoomHandler"), "ToggleReady", (Type[])null, (Type[])null);
}
private static bool Prefix()
{
int num;
if ((Object)(object)ReconnectManager.Instance != (Object)null)
{
num = (ReconnectManager.Instance.ShouldBlockLobbyReady() ? 1 : 0);
if (num != 0)
{
RoundsReconnectPlugin.Log.LogInfo((object)"Ready blocked: this client is waiting to reclaim an abandoned slot");
}
}
else
{
num = 0;
}
return num == 0;
}
}
}