Decompiled source of LateJoinFix v1.4.2

latejoinfix.dll

Decompiled 5 days ago
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.IgnoreSymbolStoreSequencePoints)]
[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.4.2.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.4.2.0")]
namespace LateJoinFix;

[BepInPlugin("com.sol.latejoinfix", "LateJoinFix", "1.4.2")]
public class Plugin : BaseUnityPlugin
{
	internal sealed class LateJoinListener : MonoBehaviourPunCallbacks
	{
		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_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (!PhotonNetwork.InRoom)
			{
				return;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).name == "Airport")
			{
				return;
			}
			Player[] playerList = PhotonNetwork.PlayerList;
			for (int i = 0; i < playerList.Length; i++)
			{
				string stablePlayerKey = GetStablePlayerKey(playerList[i]);
				if (!string.IsNullOrEmpty(stablePlayerKey))
				{
					_knownPlayerKeys.Add(stablePlayerKey);
				}
			}
			LogInfo("[LateJoinFix] Initial player snapshot captured. Count = " + _knownPlayerKeys.Count);
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			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));
			}
		}

		private IEnumerator RemovePendingIfNeverRegistered(int actorNumber, float timeout)
		{
			yield return (object)new WaitForSeconds(timeout);
			_pendingLateJoinActors.Remove(actorNumber);
			_runningFixActors.Remove(actorNumber);
		}

		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));
				}
			}
		}

		private IEnumerator HandleLateJoin(Player newPlayer)
		{
			yield return (object)new WaitForSeconds(0.2f);
			Character newCharacter = null;
			for (float elapsed = 0f; elapsed < 30f; elapsed += 0.1f)
			{
				try
				{
					newCharacter = PlayerHandler.GetPlayerCharacter(newPlayer);
				}
				catch (Exception ex)
				{
					LogWarning("[LateJoinFix] GetPlayerCharacter exception: " + ex.Message);
				}
				if ((Object)(object)newCharacter != (Object)null)
				{
					break;
				}
				yield return (object)new WaitForSeconds(0.1f);
			}
			if ((Object)(object)newCharacter == (Object)null)
			{
				LogError("[LateJoinFix] Failed to find new character.");
				yield break;
			}
			yield return (object)new WaitForSeconds(0.1f);
			ImprovedSpawnTarget spawnTarget = PopulateSpawnData(newCharacter);
			if ((Object)(object)spawnTarget.LowestCharacter == (Object)null)
			{
				LogError("[LateJoinFix] No valid spawn target found.");
				yield break;
			}
			try
			{
				Vector3 val = spawnTarget.LowestCharacter.Center + Vector3.forward * WarpForwardOffset.Value;
				((MonoBehaviourPun)newCharacter).photonView.RPC("WarpPlayerRPC", (RpcTarget)0, new object[2] { val, false });
			}
			catch (Exception ex2)
			{
				LogWarning("[LateJoinFix] WarpPlayerRPC failed: " + ex2.Message);
			}
			yield return (object)new WaitForSeconds(AfterWarpDelay.Value);
			try
			{
				Vector3 val2 = spawnTarget.LowestCharacter.Center + new Vector3(0f, ReviveHeightOffset.Value, 0f);
				((MonoBehaviourPun)newCharacter).photonView.RPC("RPCA_ReviveAtPosition", (RpcTarget)0, new object[3] { val2, false, -1 });
			}
			catch (Exception ex3)
			{
				LogWarning("[LateJoinFix] RPCA_ReviveAtPosition failed: " + ex3.Message);
			}
		}

		private IEnumerator FixAliveStateRoutine(Character character, int actorNumber)
		{
			_runningFixActors.Add(actorNumber);
			yield return (object)new WaitForSeconds(1.5f);
			for (int i = 0; i < 12; i++)
			{
				if ((Object)(object)character == (Object)null)
				{
					break;
				}
				if ((Object)(object)character.data == (Object)null)
				{
					break;
				}
				character.data.dead = false;
				character.data.passedOut = false;
				character.data.fullyPassedOut = false;
				character.data.deathTimer = 0f;
				yield return (object)new WaitForSeconds(0.25f);
			}
			_pendingLateJoinActors.Remove(actorNumber);
			_runningFixActors.Remove(actorNumber);
			LogInfo("[LateJoinFix] Cleared revive/death state for actor " + 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_000b: 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 (!((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);
	}
}