using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("latejoinfix2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("latejoinfix2")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b78d6a4e-bdfc-4a7a-9193-c02df76c2f95")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace LateJoinFix;
[BepInPlugin("com.sol.latejoinfix", "LateJoinFix", "1.4.0")]
public class Plugin : BaseUnityPlugin
{
internal sealed class LateJoinListener : MonoBehaviourPunCallbacks
{
[CompilerGenerated]
private sealed class <FixAliveStateRoutine>d__11 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public Character character;
public int actorNumber;
public LateJoinListener <>4__this;
private int <i>5__1;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <FixAliveStateRoutine>d__11(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Expected O, but got Unknown
//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
//IL_00fa: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>4__this._runningFixActors.Add(actorNumber);
<>2__current = (object)new WaitForSeconds(1.5f);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<i>5__1 = 0;
break;
case 2:
<>1__state = -1;
<i>5__1++;
break;
}
if (<i>5__1 < 12 && !((Object)(object)character == (Object)null) && !((Object)(object)character.data == (Object)null))
{
character.data.dead = false;
character.data.passedOut = false;
character.data.fullyPassedOut = false;
character.data.deathTimer = 0f;
<>2__current = (object)new WaitForSeconds(0.25f);
<>1__state = 2;
return true;
}
<>4__this._pendingLateJoinActors.Remove(actorNumber);
<>4__this._runningFixActors.Remove(actorNumber);
LogInfo("[LateJoinFix] Cleared revive/death state for actor " + actorNumber);
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <HandleLateJoin>d__10 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public Player newPlayer;
public LateJoinListener <>4__this;
private Character <newCharacter>5__1;
private float <elapsed>5__2;
private ImprovedSpawnTarget <spawnTarget>5__3;
private Exception <ex>5__4;
private Vector3 <warpTarget>5__5;
private Exception <ex>5__6;
private Vector3 <revivePos>5__7;
private Exception <ex>5__8;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <HandleLateJoin>d__10(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<newCharacter>5__1 = null;
<spawnTarget>5__3 = null;
<ex>5__4 = null;
<ex>5__6 = null;
<ex>5__8 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_021d: Unknown result type (might be due to invalid IL or missing references)
//IL_0227: Expected O, but got Unknown
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: 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
//IL_0244: Unknown result type (might be due to invalid IL or missing references)
//IL_025d: Unknown result type (might be due to invalid IL or missing references)
//IL_0262: Unknown result type (might be due to invalid IL or missing references)
//IL_0267: Unknown result type (might be due to invalid IL or missing references)
//IL_0286: Unknown result type (might be due to invalid IL or missing references)
//IL_0191: Unknown result type (might be due to invalid IL or missing references)
//IL_0196: Unknown result type (might be due to invalid IL or missing references)
//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
//IL_01af: Unknown result type (might be due to invalid IL or missing references)
//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
//IL_0133: Unknown result type (might be due to invalid IL or missing references)
//IL_013d: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(0.2f);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<newCharacter>5__1 = null;
<elapsed>5__2 = 0f;
goto IL_00f9;
case 2:
<>1__state = -1;
<elapsed>5__2 += 0.1f;
goto IL_00f9;
case 3:
<>1__state = -1;
<spawnTarget>5__3 = PopulateSpawnData(<newCharacter>5__1);
if ((Object)(object)<spawnTarget>5__3.LowestCharacter == (Object)null)
{
LogError("[LateJoinFix] No valid spawn target found.");
return false;
}
try
{
<warpTarget>5__5 = <spawnTarget>5__3.LowestCharacter.Center + Vector3.forward * WarpForwardOffset.Value;
((MonoBehaviourPun)<newCharacter>5__1).photonView.RPC("WarpPlayerRPC", (RpcTarget)0, new object[2] { <warpTarget>5__5, false });
}
catch (Exception ex)
{
<ex>5__6 = ex;
LogWarning("[LateJoinFix] WarpPlayerRPC failed: " + <ex>5__6.Message);
}
<>2__current = (object)new WaitForSeconds(AfterWarpDelay.Value);
<>1__state = 4;
return true;
case 4:
{
<>1__state = -1;
try
{
<revivePos>5__7 = <spawnTarget>5__3.LowestCharacter.Center + new Vector3(0f, ReviveHeightOffset.Value, 0f);
((MonoBehaviourPun)<newCharacter>5__1).photonView.RPC("RPCA_ReviveAtPosition", (RpcTarget)0, new object[3] { <revivePos>5__7, false, -1 });
}
catch (Exception ex)
{
<ex>5__8 = ex;
LogWarning("[LateJoinFix] RPCA_ReviveAtPosition failed: " + <ex>5__8.Message);
}
return false;
}
IL_00f9:
if (<elapsed>5__2 < 30f)
{
try
{
<newCharacter>5__1 = PlayerHandler.GetPlayerCharacter(newPlayer);
}
catch (Exception ex)
{
<ex>5__4 = ex;
LogWarning("[LateJoinFix] GetPlayerCharacter exception: " + <ex>5__4.Message);
}
if (!((Object)(object)<newCharacter>5__1 != (Object)null))
{
<>2__current = (object)new WaitForSeconds(0.1f);
<>1__state = 2;
return true;
}
}
if ((Object)(object)<newCharacter>5__1 == (Object)null)
{
LogError("[LateJoinFix] Failed to find new character.");
return false;
}
<>2__current = (object)new WaitForSeconds(0.1f);
<>1__state = 3;
return true;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <RemovePendingIfNeverRegistered>d__8 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public int actorNumber;
public float timeout;
public LateJoinListener <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <RemovePendingIfNeverRegistered>d__8(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(timeout);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<>4__this._pendingLateJoinActors.Remove(actorNumber);
<>4__this._runningFixActors.Remove(actorNumber);
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private readonly HashSet<string> _knownPlayerKeys = new HashSet<string>();
private readonly HashSet<int> _pendingLateJoinActors = new HashSet<int>();
private readonly HashSet<int> _runningFixActors = new HashSet<int>();
private void Start()
{
CaptureInitialPlayers();
SceneManager.sceneLoaded += OnSceneLoaded;
PlayerHandler.CharacterRegistered = (Action<Character>)Delegate.Combine(PlayerHandler.CharacterRegistered, new Action<Character>(OnCharacterRegistered));
}
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
PlayerHandler.CharacterRegistered = (Action<Character>)Delegate.Remove(PlayerHandler.CharacterRegistered, new Action<Character>(OnCharacterRegistered));
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (((Scene)(ref scene)).name == "Airport")
{
_knownPlayerKeys.Clear();
_pendingLateJoinActors.Clear();
_runningFixActors.Clear();
}
else
{
CaptureInitialPlayers();
}
}
private void CaptureInitialPlayers()
{
//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)
if (!PhotonNetwork.InRoom)
{
return;
}
Scene activeScene = SceneManager.GetActiveScene();
if (((Scene)(ref activeScene)).name == "Airport")
{
return;
}
Player[] playerList = PhotonNetwork.PlayerList;
foreach (Player player in playerList)
{
string stablePlayerKey = GetStablePlayerKey(player);
if (!string.IsNullOrEmpty(stablePlayerKey))
{
_knownPlayerKeys.Add(stablePlayerKey);
}
}
LogInfo("[LateJoinFix] Initial player snapshot captured. Count = " + _knownPlayerKeys.Count);
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
//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)
if (newPlayer == null || !PhotonNetwork.InRoom)
{
return;
}
Scene activeScene = SceneManager.GetActiveScene();
if (((Scene)(ref activeScene)).name == "Airport")
{
return;
}
string stablePlayerKey = GetStablePlayerKey(newPlayer);
if (string.IsNullOrEmpty(stablePlayerKey))
{
LogWarning("[LateJoinFix] Failed to build stable player key. Skip.");
return;
}
if (_knownPlayerKeys.Contains(stablePlayerKey))
{
LogInfo("[LateJoinFix] Rejoin/known player detected. Skip late-join handling for: " + newPlayer.NickName + " / " + stablePlayerKey);
return;
}
_knownPlayerKeys.Add(stablePlayerKey);
_pendingLateJoinActors.Add(newPlayer.ActorNumber);
LogInfo("[LateJoinFix] True late join detected: " + newPlayer.NickName + " / " + newPlayer.ActorNumber);
((MonoBehaviour)this).StartCoroutine(RemovePendingIfNeverRegistered(newPlayer.ActorNumber, 30f));
if (PhotonNetwork.IsMasterClient)
{
((MonoBehaviour)this).StartCoroutine(HandleLateJoin(newPlayer));
}
}
[IteratorStateMachine(typeof(<RemovePendingIfNeverRegistered>d__8))]
private IEnumerator RemovePendingIfNeverRegistered(int actorNumber, float timeout)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <RemovePendingIfNeverRegistered>d__8(0)
{
<>4__this = this,
actorNumber = actorNumber,
timeout = timeout
};
}
private void OnCharacterRegistered(Character character)
{
if ((Object)(object)character == (Object)null || (Object)(object)character.data == (Object)null || (Object)(object)((MonoBehaviourPun)character).photonView == (Object)null)
{
return;
}
Player owner = ((MonoBehaviourPun)character).photonView.Owner;
if (owner != null)
{
int actorNumber = owner.ActorNumber;
if (_pendingLateJoinActors.Contains(actorNumber) && !_runningFixActors.Contains(actorNumber))
{
((MonoBehaviour)this).StartCoroutine(FixAliveStateRoutine(character, actorNumber));
}
}
}
[IteratorStateMachine(typeof(<HandleLateJoin>d__10))]
private IEnumerator HandleLateJoin(Player newPlayer)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <HandleLateJoin>d__10(0)
{
<>4__this = this,
newPlayer = newPlayer
};
}
[IteratorStateMachine(typeof(<FixAliveStateRoutine>d__11))]
private IEnumerator FixAliveStateRoutine(Character character, int actorNumber)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <FixAliveStateRoutine>d__11(0)
{
<>4__this = this,
character = character,
actorNumber = actorNumber
};
}
private static ImprovedSpawnTarget PopulateSpawnData(Character newCharacter)
{
ImprovedSpawnTarget improvedSpawnTarget = new ImprovedSpawnTarget();
foreach (Character allCharacter in Character.AllCharacters)
{
if (!((Object)(object)allCharacter == (Object)null) && !((Object)(object)allCharacter.data == (Object)null) && !((Object)(object)allCharacter == (Object)(object)newCharacter) && !allCharacter.data.dead)
{
improvedSpawnTarget.RegisterCharacter(allCharacter);
}
}
return improvedSpawnTarget;
}
private static string GetStablePlayerKey(Player player)
{
if (player == null)
{
return string.Empty;
}
if (!string.IsNullOrEmpty(player.UserId))
{
return "USERID:" + player.UserId;
}
if (!string.IsNullOrEmpty(player.NickName))
{
return "NICK:" + player.NickName;
}
return "ACTOR:" + player.ActorNumber;
}
}
public class ImprovedSpawnTarget
{
private float lowestClimbed = float.MaxValue;
public Character LowestCharacter;
public void RegisterCharacter(Character character)
{
//IL_000f: 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)
if (!((Object)(object)character == (Object)null) && character.Center.y < lowestClimbed)
{
lowestClimbed = character.Center.y;
LowestCharacter = character;
}
}
}
internal static ManualLogSource Log;
internal static ConfigEntry<float> AfterWarpDelay;
internal static ConfigEntry<float> WarpForwardOffset;
internal static ConfigEntry<float> ReviveHeightOffset;
private const float InitialDelayAfterJoinEvent = 0.2f;
private const float CharacterSearchInterval = 0.1f;
private const float AfterCharacterFoundDelay = 0.1f;
private const float CharacterSearchTimeoutSeconds = 30f;
private LateJoinListener _listener;
private void Awake()
{
Log = ((BaseUnityPlugin)this).Logger;
AfterWarpDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "AfterWarpDelay", 2f, "워프 후 부활 RPC를 보내기 전 대기 시간(초).");
WarpForwardOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "WarpForwardOffset", 2f, "대상 플레이어 위치에서 월드 전방(Vector3.forward)으로 비껴서 워프할 거리.");
ReviveHeightOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "ReviveHeightOffset", 3f, "대상 플레이어 Center 기준 부활 위치의 높이 오프셋.");
_listener = ((Component)this).gameObject.AddComponent<LateJoinListener>();
((BaseUnityPlugin)this).Logger.LogInfo((object)"LateJoinFix loaded.");
}
internal static void LogInfo(string msg)
{
Log.LogInfo((object)msg);
}
internal static void LogWarning(string msg)
{
Log.LogWarning((object)msg);
}
internal static void LogError(string msg)
{
Log.LogError((object)msg);
}
}