Decompiled source of RepoLevelMVP v0.1.1

Matsu.RepoMvp.dll

Decompiled a day ago
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 Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("Matsu.RepoMvp")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1")]
[assembly: AssemblyProduct("Matsu.RepoMvp")]
[assembly: AssemblyTitle("Matsu.RepoMvp")]
[assembly: AssemblyVersion("0.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Matsu.RepoMvp
{
	internal sealed class MvpFallbackUi : MonoBehaviour
	{
		private const float UiPollSeconds = 0.5f;

		private Canvas? canvas;

		private Text? header;

		private Text? mvp;

		private Text? reverse;

		private Texture2D? pixel;

		private Sprite? sprite;

		private MethodInfo? deathCounterBridge;

		private bool bridgeResolved;

		private float pollAt;

		private float gameOverShowUntil;

		private int shownRevision = -1;

		private void Update()
		{
			if (Time.unscaledTime < pollAt)
			{
				return;
			}
			pollAt = Time.unscaledTime + 0.5f;
			try
			{
				if (!bridgeResolved)
				{
					Type type = AccessTools.TypeByName("Matsu.RepoDeathCounter.Plugin");
					deathCounterBridge = ((type == null) ? null : AccessTools.Method(type, "SetLastLevelAwards", (Type[])null, (Type[])null));
					bridgeResolved = true;
				}
				MethodInfo methodInfo = deathCounterBridge;
				if (shownRevision != MvpRuntime.DisplayRevision)
				{
					shownRevision = MvpRuntime.DisplayRevision;
					AwardDisplay lastDisplay = MvpRuntime.LastDisplay;
					if (methodInfo != null)
					{
						methodInfo.Invoke(null, new object[3]
						{
							lastDisplay?.Level ?? "",
							lastDisplay?.Mvp ?? "",
							lastDisplay?.Reverse ?? ""
						});
					}
					if (lastDisplay != null && lastDisplay.GameOver)
					{
						gameOverShowUntil = Time.unscaledTime + 15f;
					}
					if (lastDisplay != null && methodInfo == null)
					{
						Render(lastDisplay);
					}
				}
				AwardDisplay lastDisplay2 = MvpRuntime.LastDisplay;
				bool flag = methodInfo == null && IsTruck();
				bool flag2 = lastDisplay2 != null && lastDisplay2.GameOver && Time.unscaledTime < gameOverShowUntil && !Plugin.IsGameplayLevel();
				bool flag3 = lastDisplay2 != null && ((methodInfo == null && flag) || flag2);
				if (flag3 && (Object)(object)canvas == (Object)null && lastDisplay2 != null)
				{
					Render(lastDisplay2);
				}
				if ((Object)(object)canvas != (Object)null && ((Behaviour)canvas).enabled != flag3)
				{
					((Behaviour)canvas).enabled = flag3;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("[MVP] Results UI: " + ex));
				}
				if ((Object)(object)canvas != (Object)null)
				{
					((Behaviour)canvas).enabled = false;
				}
				pollAt = Time.unscaledTime + 3f;
			}
		}

		private static bool IsTruck()
		{
			try
			{
				return (Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelLobby;
			}
			catch
			{
				return false;
			}
		}

		private void Render(AwardDisplay result)
		{
			if ((Object)(object)canvas == (Object)null)
			{
				Build();
			}
			if (!((Object)(object)canvas == (Object)null))
			{
				header.text = "LAST LEVEL  /  " + result.Level;
				mvp.text = result.Mvp;
				reverse.text = result.Reverse;
			}
		}

		private void Build()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_0036: 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_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Expected O, but got Unknown
			//IL_0168: 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_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			Font builtinResource = Resources.GetBuiltinResource<Font>("Arial.ttf");
			if ((Object)(object)builtinResource == (Object)null)
			{
				throw new InvalidOperationException("Unity built-in font missing");
			}
			pixel = new Texture2D(1, 1, (TextureFormat)4, false);
			pixel.SetPixel(0, 0, Color.white);
			pixel.Apply(false, true);
			sprite = Sprite.Create(pixel, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f));
			GameObject val = new GameObject("REPO MVP Results Canvas", new Type[2]
			{
				typeof(RectTransform),
				typeof(Canvas)
			});
			val.transform.SetParent(((Component)this).transform, false);
			canvas = val.GetComponent<Canvas>();
			canvas.renderMode = (RenderMode)0;
			canvas.sortingOrder = 32001;
			((Behaviour)canvas).enabled = false;
			GameObject val2 = new GameObject("MVP panel", new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val2.transform.SetParent(val.transform, false);
			Image component = val2.GetComponent<Image>();
			component.sprite = sprite;
			((Graphic)component).color = new Color(0.025f, 0.03f, 0.04f, 0.95f);
			((Graphic)component).raycastTarget = false;
			RectTransform rectTransform = ((Graphic)component).rectTransform;
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(1f, 1f);
			rectTransform.anchorMax = val3;
			rectTransform.anchorMin = val3;
			rectTransform.pivot = new Vector2(1f, 1f);
			rectTransform.anchoredPosition = new Vector2(-20f, -100f);
			rectTransform.sizeDelta = new Vector2(440f, 160f);
			header = Label(val2.transform, builtinResource, "Level", 15, new Color(1f, 0.35f, 0.35f), new Vector2(12f, -10f), new Vector2(416f, 28f));
			mvp = Label(val2.transform, builtinResource, "MVP", 19, new Color(1f, 0.84f, 0.25f), new Vector2(12f, -48f), new Vector2(416f, 42f));
			reverse = Label(val2.transform, builtinResource, "OOPS CHAMP", 18, Color.white, new Vector2(12f, -100f), new Vector2(416f, 50f));
		}

		private static Text Label(Transform parent, Font font, string name, int size, Color color, Vector2 position, Vector2 dimensions)
		{
			//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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Text)
			});
			val.transform.SetParent(parent, false);
			Text component = val.GetComponent<Text>();
			component.font = font;
			component.fontSize = size;
			component.fontStyle = (FontStyle)1;
			((Graphic)component).color = color;
			component.alignment = (TextAnchor)0;
			component.horizontalOverflow = (HorizontalWrapMode)0;
			component.verticalOverflow = (VerticalWrapMode)0;
			((Graphic)component).raycastTarget = false;
			RectTransform rectTransform = ((Graphic)component).rectTransform;
			RectTransform rectTransform2 = ((Graphic)component).rectTransform;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0f, 1f);
			rectTransform2.anchorMax = val2;
			rectTransform.anchorMin = val2;
			((Graphic)component).rectTransform.pivot = new Vector2(0f, 1f);
			((Graphic)component).rectTransform.anchoredPosition = position;
			((Graphic)component).rectTransform.sizeDelta = dimensions;
			return component;
		}

		private void OnDestroy()
		{
			if ((Object)(object)sprite != (Object)null)
			{
				Object.Destroy((Object)(object)sprite);
			}
			if ((Object)(object)pixel != (Object)null)
			{
				Object.Destroy((Object)(object)pixel);
			}
		}
	}
	internal sealed class AwardDisplay
	{
		internal string Level = "";

		internal string Mvp = "";

		internal string Reverse = "";

		internal bool GameOver;

		internal int Sequence;
	}
	internal sealed class MvpRuntime : MonoBehaviour, IOnEventCallback, IInRoomCallbacks
	{
		private sealed class Participant
		{
			internal string SteamId = "";

			internal string Name = "";

			internal int ActorNumber;
		}

		private const byte ReportEvent = 229;

		private const byte AwardEvent = 230;

		private const string Protocol = "matsu.repo.mvp/4";

		private const float CloseoutSeconds = 3f;

		internal static MvpRuntime? Active;

		internal static AwardDisplay? LastDisplay;

		internal static int DisplayRevision;

		private readonly Dictionary<string, PlayerRoundStats> reports = new Dictionary<string, PlayerRoundStats>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> combat = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> healing = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> control = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> lastControlByTarget = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly HashSet<int> extractedValuables = new HashSet<int>();

		private readonly HashSet<int> extractedHeads = new HashSet<int>();

		private readonly Dictionary<int, Participant> participantsByView = new Dictionary<int, Participant>();

		private bool registered;

		private bool sessionActive;

		private bool closing;

		private bool gameOver;

		private float sessionStart;

		private float closeoutDeadline;

		private float pollAt;

		private float roundSeconds;

		private int expectedPlayers;

		private int deaths;

		private int damage;

		private int damagingHits;

		private int heavyHits;

		private int criticalEpisodes;

		private float deliveredHaul;

		private float roomScouting;

		private float valuableDiscovery;

		private float rescue;

		private float downedSeconds;

		private float lastDownedPoll;

		private float lastDeathAt = -1000f;

		private int sequence;

		private int lastClosedSceneHandle = -1;

		private string levelName = "";

		private string roundToken = "";

		private bool authorityChanged;

		private void Awake()
		{
			Active = this;
		}

		private void OnDestroy()
		{
			if (registered)
			{
				PhotonNetwork.RemoveCallbackTarget((object)this);
			}
			registered = false;
			if ((Object)(object)Active == (Object)(object)this)
			{
				Active = null;
			}
		}

		private void Update()
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			if (Time.unscaledTime < pollAt)
			{
				return;
			}
			pollAt = Time.unscaledTime + 0.25f;
			try
			{
				bool flag = PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode;
				if (flag && !registered)
				{
					PhotonNetwork.AddCallbackTarget((object)this);
					registered = true;
				}
				else if (!flag && registered)
				{
					PhotonNetwork.RemoveCallbackTarget((object)this);
					registered = false;
					LastDisplay = null;
					DisplayRevision++;
				}
				if (!sessionActive && !closing && Plugin.IsGameplayLevel())
				{
					Scene activeScene = SceneManager.GetActiveScene();
					if (((Scene)(ref activeScene)).handle != lastClosedSceneHandle && (Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated)
					{
						StartRound();
					}
				}
				if (sessionActive && Plugin.IsGameplayLevel())
				{
					float realtimeSinceStartup = Time.realtimeSinceStartup;
					PlayerAvatar instance = PlayerAvatar.instance;
					if ((Object)(object)instance != (Object)null && (bool)Plugin.AvatarDead.GetValue(instance))
					{
						downedSeconds += Mathf.Clamp(realtimeSinceStartup - lastDownedPoll, 0f, 1f);
					}
					lastDownedPoll = realtimeSinceStartup;
				}
				if (closing && Plugin.IsHost() && Time.realtimeSinceStartup >= closeoutDeadline)
				{
					FinalizeRound();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("[MVP] Runtime update: " + ex));
				}
			}
		}

		private void StartRound()
		{
			sessionActive = true;
			closing = false;
			lastClosedSceneHandle = -1;
			deaths = (damage = (damagingHits = (heavyHits = (criticalEpisodes = 0))));
			deliveredHaul = (roomScouting = (valuableDiscovery = (rescue = (downedSeconds = 0f))));
			lastDeathAt = -1000f;
			combat.Clear();
			healing.Clear();
			control.Clear();
			lastControlByTarget.Clear();
			extractedValuables.Clear();
			extractedHeads.Clear();
			reports.Clear();
			participantsByView.Clear();
			authorityChanged = false;
			sessionStart = Time.realtimeSinceStartup;
			lastDownedPoll = sessionStart;
			levelName = RunManager.instance.levelCurrent?.NarrativeName ?? "LEVEL";
			if (LastDisplay != null)
			{
				LastDisplay = null;
				DisplayRevision++;
			}
			ManualLogSource? log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[MVP] Collecting " + levelName));
			}
		}

		internal void RecordDamage(int amount, bool enteredCritical)
		{
			if (sessionActive && !closing && amount > 0)
			{
				damage = Math.Min(10000, damage + amount);
				damagingHits = Math.Min(100, damagingHits + 1);
				if (amount >= 25)
				{
					heavyHits = Math.Min(100, heavyHits + 1);
				}
				if (enteredCritical)
				{
					criticalEpisodes = Math.Min(20, criticalEpisodes + 1);
				}
			}
		}

		internal void RecordDeath()
		{
			if (sessionActive && !closing && !(Time.unscaledTime - lastDeathAt < 0.5f))
			{
				lastDeathAt = Time.unscaledTime;
				deaths = Math.Min(100, deaths + 1);
			}
		}

		internal void RecordCombat(string steamId, int actualHpLoss)
		{
			if (sessionActive && !closing && Plugin.IsHost() && !string.IsNullOrEmpty(steamId))
			{
				combat.TryGetValue(steamId, out var value);
				combat[steamId] = Math.Min(10000f, value + (float)Math.Max(0, actualHpLoss));
			}
		}

		internal void RecordRoomDiscovery(float points)
		{
			if (sessionActive && !closing && points > 0f)
			{
				roomScouting = Math.Min(100000f, roomScouting + points);
			}
		}

		internal void RecordValuableDiscovery(float points)
		{
			if (sessionActive && !closing && points > 0f)
			{
				valuableDiscovery = Math.Min(100000f, valuableDiscovery + points);
			}
		}

		internal void RecordExtraction(ExtractionPoint point)
		{
			if (!sessionActive || closing || (Object)(object)point == (Object)null || (Object)(object)RoundDirector.instance == (Object)null)
			{
				return;
			}
			try
			{
				ValuableObject val = default(ValuableObject);
				foreach (GameObject dollarHaul in RoundDirector.instance.dollarHaulList)
				{
					if (Object.op_Implicit((Object)(object)dollarHaul) && dollarHaul.TryGetComponent<ValuableObject>(ref val) && extractedValuables.Add(((Object)dollarHaul).GetInstanceID()))
					{
						float localPlayerHaulScore = val.GetLocalPlayerHaulScore(((Component)point).gameObject);
						if (!(localPlayerHaulScore <= 0f))
						{
							float num = (float)Plugin.ValuableDollarValue.GetValue(val);
							deliveredHaul = Math.Min(100000f, deliveredHaul + Mathf.Clamp(num / 10f * localPlayerHaulScore, 0f, 10000f));
						}
					}
				}
				if ((Object)(object)GameDirector.instance == (Object)null)
				{
					return;
				}
				foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
				{
					if ((Object)(object)player == (Object)null)
					{
						continue;
					}
					object? value = Plugin.AvatarDeathHead.GetValue(player);
					PlayerDeathHead val2 = (PlayerDeathHead)((value is PlayerDeathHead) ? value : null);
					if (val2 != null && (bool)Plugin.HeadInExtraction.GetValue(val2) && extractedHeads.Add(((Object)val2).GetInstanceID()))
					{
						float localPlayerHaulScore2 = val2.GetLocalPlayerHaulScore(((Component)point).gameObject);
						if (localPlayerHaulScore2 > 0f)
						{
							rescue = Math.Min(100000f, rescue + Mathf.Clamp(100f * localPlayerHaulScore2, 0f, 10000f));
						}
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[MVP] Extraction contribution unavailable: " + ex.Message));
				}
			}
		}

		internal void RecordHealing(string steamId, float amount)
		{
			if (sessionActive && !closing && Plugin.IsHost() && !string.IsNullOrEmpty(steamId) && !(amount <= 0f))
			{
				healing.TryGetValue(steamId, out var value);
				healing[steamId] = Math.Min(100000f, value + amount);
			}
		}

		internal void RecordControl(string steamId, int enemyId, float seconds)
		{
			if (sessionActive && !closing && Plugin.IsHost() && !string.IsNullOrEmpty(steamId) && !(seconds < 0.5f))
			{
				string key = steamId + ":" + enemyId;
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (!lastControlByTarget.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < 5f))
				{
					lastControlByTarget[key] = realtimeSinceStartup;
					control.TryGetValue(steamId, out var value2);
					control[steamId] = Math.Min(1000f, value2 + Math.Min(seconds, 5f));
				}
			}
		}

		internal void CloseRound(bool isGameOver)
		{
			//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_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b1: Expected O, but got Unknown
			if (!sessionActive || closing)
			{
				return;
			}
			try
			{
				roundSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - sessionStart);
				Scene activeScene = SceneManager.GetActiveScene();
				lastClosedSceneHandle = ((Scene)(ref activeScene)).handle;
				string text = RunManager.instance.levelsCompleted.ToString();
				Level levelCurrent = RunManager.instance.levelCurrent;
				roundToken = text + ":" + ((levelCurrent != null) ? ((Object)levelCurrent).name : null);
				gameOver = isGameOver;
				PlayerAvatar instance = PlayerAvatar.instance;
				PlayerRoundStats playerRoundStats = new PlayerRoundStats
				{
					SteamId = Plugin.Id(instance),
					Name = Plugin.PlayerName(instance),
					Hauling = Mathf.Max(deliveredHaul, ProgressionManager.instance?.roundPointsHauling ?? 0f),
					Scouting = (ProgressionManager.instance?.roundPointsScouting ?? 0f),
					Support = (ProgressionManager.instance?.roundPointsSupport ?? 0f),
					RoomScouting = roomScouting,
					ValuableDiscovery = valuableDiscovery,
					Rescue = rescue,
					Deaths = deaths,
					DamageTaken = damage,
					DamagingHits = damagingHits,
					HeavyHits = heavyHits,
					CriticalEpisodes = criticalEpisodes,
					DownedSeconds = downedSeconds,
					ParticipationSeconds = roundSeconds,
					AliveAtEnd = ((Object)(object)instance != (Object)null && !(bool)Plugin.AvatarDead.GetValue(instance))
				};
				sessionActive = false;
				closing = true;
				if (Plugin.IsHost())
				{
					CaptureParticipants();
					expectedPlayers = Math.Max(reports.Count, participantsByView.Values.Select((Participant participant) => participant.SteamId).Distinct<string>(StringComparer.Ordinal).Count());
					closeoutDeadline = Time.realtimeSinceStartup + 3f;
					if (!string.IsNullOrEmpty(playerRoundStats.SteamId))
					{
						reports[playerRoundStats.SteamId] = playerRoundStats;
					}
					expectedPlayers = Math.Max(expectedPlayers, reports.Count);
				}
				else if ((Object)(object)instance != (Object)null && (Object)(object)instance.photonView != (Object)null && instance.photonView.ViewID > 0 && PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
				{
					PhotonNetwork.RaiseEvent((byte)229, (object)new object[17]
					{
						"matsu.repo.mvp/4",
						roundToken,
						instance.photonView.ViewID,
						playerRoundStats.Hauling,
						playerRoundStats.Scouting,
						playerRoundStats.Support,
						playerRoundStats.RoomScouting,
						playerRoundStats.ValuableDiscovery,
						playerRoundStats.Rescue,
						playerRoundStats.DownedSeconds,
						playerRoundStats.Deaths,
						playerRoundStats.DamageTaken,
						playerRoundStats.DamagingHits,
						playerRoundStats.HeavyHits,
						playerRoundStats.CriticalEpisodes,
						playerRoundStats.ParticipationSeconds,
						playerRoundStats.AliveAtEnd
					}, new RaiseEventOptions
					{
						Receivers = (ReceiverGroup)2
					}, SendOptions.SendReliable);
				}
				if (!Plugin.IsHost())
				{
					closing = false;
				}
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[MVP] Closed {levelName}: haul={playerRoundStats.Hauling:0}, rooms={playerRoundStats.RoomScouting:0}, valuables={playerRoundStats.ValuableDiscovery:0}, rescue={playerRoundStats.Rescue:0}, scout={playerRoundStats.Scouting:0}, support={playerRoundStats.Support:0}, deaths={playerRoundStats.Deaths}, damage={playerRoundStats.DamageTaken}, hits={playerRoundStats.DamagingHits}, heavy={playerRoundStats.HeavyHits}, critical={playerRoundStats.CriticalEpisodes}");
				}
			}
			catch (Exception ex)
			{
				sessionActive = (closing = false);
				ManualLogSource? log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogError((object)("[MVP] Level closeout failed: " + ex));
				}
			}
		}

		private void CaptureParticipants()
		{
			participantsByView.Clear();
			try
			{
				foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
				{
					if (!((Object)(object)player == (Object)null) && !((Object)(object)player.photonView == (Object)null) && player.photonView.ViewID > 0 && player.photonView.Owner != null)
					{
						string text = Plugin.Id(player);
						if (!string.IsNullOrEmpty(text))
						{
							participantsByView[player.photonView.ViewID] = new Participant
							{
								SteamId = text,
								Name = Plugin.PlayerName(player),
								ActorNumber = player.photonView.Owner.ActorNumber
							};
						}
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[MVP] Could not snapshot all closeout participants: " + ex.Message));
				}
			}
		}

		public void OnEvent(EventData photonEvent)
		{
			try
			{
				if (photonEvent.Code == 229 && Plugin.IsHost())
				{
					ReceiveReport(photonEvent);
				}
				else if (photonEvent.Code == 230 && !Plugin.IsHost())
				{
					ReceiveAward(photonEvent);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("[MVP] Photon event: " + ex));
				}
			}
		}

		private void ReceiveReport(EventData photonEvent)
		{
			if (closing && !(Time.realtimeSinceStartup > closeoutDeadline + 1f) && photonEvent.CustomData is object[] array && array.Length == 17 && array[0] is string text && !(text != "matsu.repo.mvp/4") && array[1] is string text2 && !(text2 != roundToken) && array[2] is int num && num > 0 && participantsByView.TryGetValue(num, out Participant value) && value.ActorNumber == photonEvent.Sender)
			{
				string steamId = value.SteamId;
				if (!string.IsNullOrEmpty(steamId) && !reports.ContainsKey(steamId) && array[3] is float num2 && array[4] is float num3 && array[5] is float num4 && array[6] is float num5 && array[7] is float num6 && array[8] is float num7 && array[9] is float num8 && array[10] is int num9 && array[11] is int num10 && array[12] is int num11 && array[13] is int num12 && array[14] is int num13 && array[15] is float num14 && array[16] is bool aliveAtEnd)
				{
					reports[steamId] = new PlayerRoundStats
					{
						SteamId = steamId,
						Name = value.Name,
						Hauling = Mathf.Clamp(num2, 0f, 100000f),
						Scouting = Mathf.Clamp(num3, 0f, 100000f),
						Support = Mathf.Clamp(num4, 0f, 100000f),
						RoomScouting = Mathf.Clamp(num5, 0f, 100000f),
						ValuableDiscovery = Mathf.Clamp(num6, 0f, 100000f),
						Rescue = Mathf.Clamp(num7, 0f, 100000f),
						DownedSeconds = Mathf.Clamp(num8, 0f, roundSeconds + 5f),
						Deaths = Mathf.Clamp(num9, 0, 20),
						DamageTaken = Mathf.Clamp(num10, 0, 2000),
						DamagingHits = Mathf.Clamp(num11, 0, 20),
						HeavyHits = Mathf.Clamp(num12, 0, 20),
						CriticalEpisodes = Mathf.Clamp(num13, 0, 10),
						ParticipationSeconds = Mathf.Clamp(num14, 0f, roundSeconds + 5f),
						AliveAtEnd = aliveAtEnd
					};
				}
			}
		}

		private void FinalizeRound()
		{
			closing = false;
			foreach (PlayerRoundStats value4 in reports.Values)
			{
				if (combat.TryGetValue(value4.SteamId, out var value))
				{
					value4.CombatDamage = value;
				}
				if (healing.TryGetValue(value4.SteamId, out var value2))
				{
					value4.Healing = value2;
				}
				if (control.TryGetValue(value4.SteamId, out var value3))
				{
					value4.CrowdControl = value3;
				}
			}
			AwardDisplay awardDisplay = FormatAwards(MvpScoring.Evaluate(reports.Values, roundSeconds, authorityChanged || reports.Count < expectedPlayers));
			awardDisplay.Level = levelName;
			awardDisplay.GameOver = gameOver;
			awardDisplay.Sequence = ++sequence;
			ApplyDisplay(awardDisplay);
			BroadcastAward();
			ManualLogSource? log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[MVP] Result: {awardDisplay.Mvp}; {awardDisplay.Reverse}; reports={reports.Count}/{expectedPlayers}");
			}
		}

		private static AwardDisplay FormatAwards(LevelAwards awards)
		{
			AwardDisplay awardDisplay = new AwardDisplay();
			if (awards.Incomplete)
			{
				awardDisplay.Mvp = "MVP  DATA INCOMPLETE";
				awardDisplay.Reverse = "OOPS CHAMP  DATA INCOMPLETE";
				return awardDisplay;
			}
			if (awards.Ranked.Count == 1)
			{
				awardDisplay.Mvp = "SOLO  " + awards.Ranked[0].Player.Name + "  /  " + (awards.HasActivity ? awards.Ranked[0].LeadingCategory : "NO ACTIVITY");
				awardDisplay.Reverse = "OOPS CHAMP  N/A (SOLO)";
				return awardDisplay;
			}
			PlayerAwardScore mvp = awards.Mvp;
			awardDisplay.Mvp = ((mvp == null) ? "MVP  NO ACTIVITY" : $"MVP  {mvp.Player.Name}  /  {mvp.LeadingCategory} {mvp.Total:0}");
			PlayerAwardScore reverseMvp = awards.ReverseMvp;
			awardDisplay.Reverse = ((reverseMvp == null) ? "OOPS CHAMP  NONE" : ("OOPS CHAMP  " + reverseMvp.Player.Name + "  /  " + OopsReason(reverseMvp)));
			return awardDisplay;
		}

		private static string OopsReason(PlayerAwardScore score)
		{
			PlayerRoundStats player = score.Player;
			List<(float, string)> list = new List<(float, string)>();
			if (score.LowScorePenalty > 0f)
			{
				list.Add((score.LowScorePenalty, $"{score.Total:0} MVP PTS"));
			}
			if (player.Deaths > 0)
			{
				list.Add(((float)Math.Min(3, player.Deaths) * 85f, $"{player.Deaths} KOs"));
			}
			if (player.DamageTaken > 0)
			{
				list.Add(((float)Math.Min(300, player.DamageTaken) * 0.25f, $"{player.DamageTaken} DMG"));
			}
			if (player.DamagingHits > 0)
			{
				list.Add(((float)Math.Min(12, player.DamagingHits) * 3f, $"{player.DamagingHits} HITS"));
			}
			if (player.HeavyHits > 0)
			{
				list.Add(((float)Math.Min(6, player.HeavyHits) * 6f, $"{player.HeavyHits} HARD HITS"));
			}
			if (player.CriticalEpisodes > 0)
			{
				list.Add(((float)Math.Min(4, player.CriticalEpisodes) * 12f, $"{player.CriticalEpisodes} LOW-HP"));
			}
			if (player.DownedSeconds >= 1f)
			{
				list.Add((Math.Min(90f, player.DownedSeconds) * 0.45f, $"{player.DownedSeconds:0}s DOWN"));
			}
			if (!player.AliveAtEnd && (player.Deaths > 0 || player.DownedSeconds > 0f))
			{
				list.Add((20f, "ENDED DOWN"));
			}
			return string.Join(", ", (from item in list.OrderByDescending<(float, string), float>(((float Weight, string Label) item) => item.Weight).Take(2)
				select item.Label).ToArray());
		}

		private static void ApplyDisplay(AwardDisplay display)
		{
			if (LastDisplay == null || display.Sequence > LastDisplay.Sequence)
			{
				LastDisplay = display;
				DisplayRevision++;
			}
		}

		private void BroadcastAward(int[]? targets = null)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0034: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			if (LastDisplay != null && PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
			{
				RaiseEventOptions val = ((targets == null) ? new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				} : new RaiseEventOptions
				{
					TargetActors = targets
				});
				PhotonNetwork.RaiseEvent((byte)230, (object)new object[6] { "matsu.repo.mvp/4", LastDisplay.Level, LastDisplay.Mvp, LastDisplay.Reverse, LastDisplay.GameOver, LastDisplay.Sequence }, val, SendOptions.SendReliable);
			}
		}

		private static void ReceiveAward(EventData photonEvent)
		{
			if (PhotonNetwork.MasterClient != null && photonEvent.Sender == PhotonNetwork.MasterClient.ActorNumber && photonEvent.CustomData is object[] array && array.Length == 6 && array[0] is string text && !(text != "matsu.repo.mvp/4") && array[1] is string text2 && array[2] is string text3 && array[3] is string text4 && array[4] is bool flag && array[5] is int num)
			{
				ApplyDisplay(new AwardDisplay
				{
					Level = ((text2.Length > 80) ? text2.Substring(0, 80) : text2),
					Mvp = ((text3.Length > 160) ? text3.Substring(0, 160) : text3),
					Reverse = ((text4.Length > 160) ? text4.Substring(0, 160) : text4),
					GameOver = flag,
					Sequence = num
				});
			}
		}

		public void OnPlayerEnteredRoom(Player newPlayer)
		{
			if (Plugin.IsHost() && LastDisplay != null)
			{
				BroadcastAward(new int[1] { newPlayer.ActorNumber });
			}
		}

		public void OnPlayerLeftRoom(Player otherPlayer)
		{
		}

		public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
		{
		}

		public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
		{
		}

		public void OnMasterClientSwitched(Player newMasterClient)
		{
			if (sessionActive || closing)
			{
				authorityChanged = true;
			}
			if (Plugin.IsHost() && LastDisplay != null)
			{
				BroadcastAward();
			}
		}
	}
	public sealed class PlayerRoundStats
	{
		public string SteamId = "";

		public string Name = "";

		public float Hauling;

		public float Scouting;

		public float Support;

		public float RoomScouting;

		public float ValuableDiscovery;

		public float Rescue;

		public float Healing;

		public float CombatDamage;

		public float CrowdControl;

		public int Deaths;

		public int DamageTaken;

		public int DamagingHits;

		public int HeavyHits;

		public int CriticalEpisodes;

		public float DownedSeconds;

		public float ParticipationSeconds;

		public bool AliveAtEnd;
	}
	public sealed class PlayerAwardScore
	{
		public PlayerRoundStats Player;

		public float Total;

		public float Hauling;

		public float Scouting;

		public float Support;

		public float Rooms;

		public float Valuables;

		public float Rescue;

		public float Healing;

		public float Combat;

		public float Control;

		public float Versatility;

		public float Survival;

		public float Mishap;

		public float LowScorePenalty;

		public string LeadingCategory => new(string, float)[11]
		{
			("HAUL", Hauling),
			("ROOMS", Rooms),
			("VALUABLES", Valuables),
			("RESCUE", Rescue),
			("HEAL", Healing),
			("SCOUT", Scouting),
			("SUPPORT", Support),
			("COMBAT", Combat),
			("CONTROL", Control),
			("VERSATILE", Versatility),
			("SURVIVAL", Survival)
		}.OrderByDescending(((string, float) entry) => entry.Item2).First().Item1;
	}
	public sealed class LevelAwards
	{
		public IReadOnlyList<PlayerAwardScore> Ranked = Array.Empty<PlayerAwardScore>();

		public PlayerAwardScore? Mvp;

		public PlayerAwardScore? ReverseMvp;

		public bool HasActivity;

		public bool Incomplete;
	}
	public static class MvpScoring
	{
		private const float MinimumOopsScore = 15f;

		public static LevelAwards Evaluate(IEnumerable<PlayerRoundStats> reports, float levelSeconds, bool incomplete = false)
		{
			if (reports == null)
			{
				throw new ArgumentNullException("reports");
			}
			List<PlayerRoundStats> list = (from @group in reports.Where((PlayerRoundStats player) => player != null && !string.IsNullOrWhiteSpace(player.SteamId) && player.ParticipationSeconds > 0f).GroupBy<PlayerRoundStats, string>((PlayerRoundStats player) => player.SteamId, StringComparer.Ordinal)
				select @group.OrderByDescending((PlayerRoundStats player) => player.ParticipationSeconds).First()).ToList();
			LevelAwards awards = new LevelAwards
			{
				Incomplete = incomplete
			};
			if (list.Count == 0)
			{
				return awards;
			}
			float[][] values = list.Select((PlayerRoundStats player) => new float[9]
			{
				Valid(player.Hauling, 100000f),
				Valid(player.RoomScouting, 100000f),
				Valid(player.ValuableDiscovery, 100000f),
				Math.Max(0f, Valid(player.Scouting, 100000f) - Valid(player.RoomScouting, 100000f) - Valid(player.ValuableDiscovery, 100000f)),
				Valid(player.Rescue, 100000f),
				Valid(player.Healing, 100000f),
				Math.Max(0f, Valid(player.Support, 100000f) - Valid(player.Rescue, 100000f) - Valid(player.Healing, 100000f)),
				Valid(player.CombatDamage, 10000f),
				Valid(player.CrowdControl, 1000f)
			}).ToArray();
			float[] maxima = (from index in Enumerable.Range(0, 9)
				select values.Max((float[] row) => row[index])).ToArray();
			bool flag = maxima[1] + maxima[2] + maxima[3] > 0f;
			bool flag2 = maxima[4] + maxima[5] + maxima[6] > 0f;
			awards.HasActivity = maxima.Any((float value) => value > 0f);
			int num = values.Max((float[] row) => CountRoles(row, maxima));
			float num2 = 10f + ((maxima[0] > 0f) ? 24f : 0f) + (flag ? 20f : 0f) + (flag2 ? 20f : 0f) + ((maxima[7] > 0f) ? 14f : 0f) + ((maxima[8] > 0f) ? 6f : 0f) + ((num >= 2) ? 6f : 0f);
			float num3 = 100f / num2;
			List<PlayerAwardScore> list2 = new List<PlayerAwardScore>(list.Count);
			for (int num4 = 0; num4 < list.Count; num4++)
			{
				PlayerRoundStats playerRoundStats = list[num4];
				float[] array = values[num4];
				PlayerAwardScore playerAwardScore = new PlayerAwardScore
				{
					Player = playerRoundStats
				};
				playerAwardScore.Hauling = Category(array[0], maxima[0], 24f) * num3;
				float activeWeight = ActiveSubWeight(maxima, 1, 3, new float[3] { 10f, 10f, 10f });
				playerAwardScore.Rooms = SubCategory(array[1], maxima[1], 10f, activeWeight, 20f) * num3;
				playerAwardScore.Valuables = SubCategory(array[2], maxima[2], 10f, activeWeight, 20f) * num3;
				playerAwardScore.Scouting = SubCategory(array[3], maxima[3], 10f, activeWeight, 20f) * num3;
				float activeWeight2 = ActiveSubWeight(maxima, 4, 6, new float[3] { 12f, 8f, 10f });
				playerAwardScore.Rescue = SubCategory(array[4], maxima[4], 12f, activeWeight2, 20f) * num3;
				playerAwardScore.Healing = SubCategory(array[5], maxima[5], 8f, activeWeight2, 20f) * num3;
				playerAwardScore.Support = SubCategory(array[6], maxima[6], 10f, activeWeight2, 20f) * num3;
				playerAwardScore.Combat = Category(array[7], maxima[7], 14f) * num3;
				playerAwardScore.Control = Category(array[8], maxima[8], 6f) * num3;
				if (num >= 2)
				{
					playerAwardScore.Versatility = 6f * Math.Min(1f, Math.Max(0f, ((float)CountRoles(array, maxima) - 1f) / 4f)) * num3;
				}
				int num5 = Math.Max(0, Math.Min(20, playerRoundStats.Deaths));
				int num6 = Math.Max(0, Math.Min(2000, playerRoundStats.DamageTaken));
				int val = Math.Max(0, Math.Min(20, playerRoundStats.DamagingHits));
				int val2 = Math.Max(0, Math.Min(20, playerRoundStats.HeavyHits));
				int val3 = Math.Max(0, Math.Min(10, playerRoundStats.CriticalEpisodes));
				float num7 = Valid(playerRoundStats.DownedSeconds, Math.Max(0f, levelSeconds));
				float num8 = Math.Max(0f, 1f - (float)num5 * 0.35f - (float)num6 / 500f - ((levelSeconds > 0f) ? (num7 / levelSeconds * 0.2f) : 0f));
				if (!playerRoundStats.AliveAtEnd)
				{
					num8 *= 0.5f;
				}
				playerAwardScore.Survival = 10f * num8 * num3;
				float num9 = Contribution(playerAwardScore);
				playerAwardScore.Total = num9 + playerAwardScore.Survival + playerAwardScore.Versatility;
				playerAwardScore.Mishap = (float)Math.Min(3, num5) * 85f + (float)Math.Min(300, num6) * 0.25f + (float)Math.Min(12, val) * 3f + (float)Math.Min(6, val2) * 6f + (float)Math.Min(4, val3) * 12f + Math.Min(90f, num7) * 0.45f + ((!playerRoundStats.AliveAtEnd && (num5 > 0 || num7 > 0f)) ? 20f : 0f);
				list2.Add(playerAwardScore);
			}
			awards.Ranked = list2.OrderByDescending((PlayerAwardScore score) => score.Total).ThenByDescending(Contribution).ThenBy<PlayerAwardScore, string>((PlayerAwardScore score) => score.Player.SteamId, StringComparer.Ordinal)
				.ToArray();
			if (list.Count > 1 && awards.HasActivity)
			{
				awards.Mvp = awards.Ranked.FirstOrDefault((PlayerAwardScore score) => Contribution(score) > 0f);
			}
			if (awards.Mvp != null && awards.Mvp.Total > 0f)
			{
				foreach (PlayerAwardScore item in list2)
				{
					if (!(item.Player.SteamId == awards.Mvp.Player.SteamId))
					{
						float num10 = Math.Max(0f, Math.Min(1f, (awards.Mvp.Total - item.Total) / awards.Mvp.Total));
						item.LowScorePenalty = 25f * Math.Max(0f, (num10 - 0.25f) / 0.75f);
						item.Mishap += item.LowScorePenalty;
					}
				}
			}
			if (list.Count > 1)
			{
				awards.ReverseMvp = (from score in list2
					where score.Mishap >= 15f && (awards.Mvp == null || score.Player.SteamId != awards.Mvp.Player.SteamId)
					orderby score.Mishap descending, score.Player.Deaths descending
					select score).ThenBy<PlayerAwardScore, string>((PlayerAwardScore score) => score.Player.SteamId, StringComparer.Ordinal).FirstOrDefault();
			}
			return awards;
		}

		private static float Valid(float value, float maximum)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Max(0f, Math.Min(maximum, value));
			}
			return 0f;
		}

		private static float Category(float value, float maximum, float weight)
		{
			if (!(maximum <= 0f))
			{
				return weight * (float)Math.Sqrt(value / maximum);
			}
			return 0f;
		}

		private static float ActiveSubWeight(float[] maxima, int start, int end, float[] weights)
		{
			float num = 0f;
			for (int i = start; i <= end; i++)
			{
				if (maxima[i] > 0f)
				{
					num += weights[i - start];
				}
			}
			return num;
		}

		private static float SubCategory(float value, float maximum, float localWeight, float activeWeight, float groupWeight)
		{
			if (!(activeWeight <= 0f))
			{
				return Category(value, maximum, groupWeight * localWeight / activeWeight);
			}
			return 0f;
		}

		private static int CountRoles(float[] row, float[] maxima)
		{
			int num = 0;
			int[] array = new int[7] { 0, 1, 2, 4, 5, 7, 8 };
			foreach (int num2 in array)
			{
				if (maxima[num2] > 0f && row[num2] >= maxima[num2] * 0.1f)
				{
					num++;
				}
			}
			if (maxima[3] > 0f && row[3] >= maxima[3] * 0.1f && row[1] == 0f && row[2] == 0f)
			{
				num++;
			}
			if (maxima[6] > 0f && row[6] >= maxima[6] * 0.1f && row[4] == 0f && row[5] == 0f)
			{
				num++;
			}
			return num;
		}

		private static float Contribution(PlayerAwardScore score)
		{
			return score.Hauling + score.Rooms + score.Valuables + score.Scouting + score.Rescue + score.Healing + score.Support + score.Combat + score.Control;
		}
	}
	[BepInPlugin("matsu.repo.mvp", "REPO Level MVP", "0.1.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private readonly struct HealthBeforeHit
		{
			internal readonly int Health;

			internal readonly int Maximum;

			internal HealthBeforeHit(int health, int maximum)
			{
				Health = health;
				Maximum = maximum;
			}
		}

		private readonly struct CombatHit
		{
			internal readonly string Actor;

			internal readonly Enemy? Enemy;

			internal readonly EnemyHealth? Health;

			internal readonly int Before;

			internal readonly float StunBefore;

			internal CombatHit(string actor, Enemy enemy, EnemyHealth? health, int before, float stunBefore)
			{
				Actor = actor;
				Enemy = enemy;
				Health = health;
				Before = before;
				StunBefore = stunBefore;
			}
		}

		public const string Guid = "matsu.repo.mvp";

		public const string Name = "REPO Level MVP";

		public const string Version = "0.1.1";

		internal static ManualLogSource? Log;

		private static GameObject? runtimeRoot;

		private Harmony harmony;

		internal static readonly FieldInfo AvatarSteamId = AccessTools.Field(typeof(PlayerAvatar), "steamID");

		internal static readonly FieldInfo AvatarName = AccessTools.Field(typeof(PlayerAvatar), "playerName");

		internal static readonly FieldInfo AvatarDead = AccessTools.Field(typeof(PlayerAvatar), "deadSet");

		internal static readonly FieldInfo AvatarDeathHead = AccessTools.Field(typeof(PlayerAvatar), "playerDeathHead");

		internal static readonly FieldInfo HealthValue = AccessTools.Field(typeof(PlayerHealth), "health");

		internal static readonly FieldInfo HealthMaximum = AccessTools.Field(typeof(PlayerHealth), "maxHealth");

		internal static readonly FieldInfo EnemyHealthField = AccessTools.Field(typeof(Enemy), "Health");

		internal static readonly FieldInfo EnemyHealthValue = AccessTools.Field(typeof(EnemyHealth), "healthCurrent");

		internal static readonly FieldInfo ColliderCauser = AccessTools.Field(typeof(HurtCollider), "playerCausingHurt");

		internal static readonly FieldInfo RoomPlayer = AccessTools.Field(typeof(RoomVolumeCheck), "player");

		internal static readonly FieldInfo ExtractionStateStart = AccessTools.Field(typeof(ExtractionPoint), "stateStart");

		internal static readonly FieldInfo ExtractionState = AccessTools.Field(typeof(ExtractionPoint), "currentState");

		internal static readonly FieldInfo ValuableDollarValue = AccessTools.Field(typeof(ValuableObject), "dollarValueCurrent");

		internal static readonly FieldInfo HeadInExtraction = AccessTools.Field(typeof(PlayerDeathHead), "inExtractionPoint");

		internal static readonly FieldInfo EnemyStunned = AccessTools.Field(typeof(Enemy), "StateStunned");

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			//IL_00df: Expected O, but got Unknown
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Expected O, but got Unknown
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Expected O, but got Unknown
			//IL_0169: Expected O, but got Unknown
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Expected O, but got Unknown
			//IL_01b8: Expected O, but got Unknown
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Expected O, but got Unknown
			//IL_0207: Expected O, but got Unknown
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Expected O, but got Unknown
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			harmony = new Harmony("matsu.repo.mvp");
			try
			{
				harmony.Patch((MethodBase)AccessTools.Method(typeof(RunManager), "ChangeLevel", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "ChangeLevelPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(RunManager), "UpdateLevel", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "UpdateLevelPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerHealth), "Hurt", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "HurtPrefix", (Type[])null), new HarmonyMethod(typeof(Plugin), "HurtPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerAvatar), "PlayerDeath", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "DeathPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(HurtCollider), "EnemyHurt", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "EnemyHurtPrefix", (Type[])null), new HarmonyMethod(typeof(Plugin), "EnemyHurtPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(RoomVolumeCheck), "CheckSet", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "RoomCheckPrefix", (Type[])null), new HarmonyMethod(typeof(Plugin), "RoomCheckPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PhysGrabber), "DiscoverLogic", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "DiscoveryPrefix", (Type[])null), new HarmonyMethod(typeof(Plugin), "DiscoveryPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ExtractionPoint), "StateExtracting", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "ExtractionPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerAvatar), "HealedOther", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "HealedOtherPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				SceneManager.sceneLoaded += OnInitialSceneLoaded;
				Log.LogInfo((object)"REPO Level MVP 0.1.1 loaded. Waiting for game scene.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("[MVP] Required native hook missing: " + ex));
				harmony.UnpatchSelf();
			}
		}

		private static void OnInitialSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			SceneManager.sceneLoaded -= OnInitialSceneLoaded;
			if (!((Object)(object)runtimeRoot != (Object)null))
			{
				runtimeRoot = new GameObject("REPO MVP Runtime");
				Object.DontDestroyOnLoad((Object)(object)runtimeRoot);
				runtimeRoot.AddComponent<MvpRuntime>();
				runtimeRoot.AddComponent<MvpFallbackUi>();
			}
		}

		internal static bool IsGameplayLevel()
		{
			try
			{
				return (Object)(object)RunManager.instance != (Object)null && (SemiFunc.RunIsLevel() || SemiFunc.RunIsArena());
			}
			catch
			{
				return false;
			}
		}

		internal static bool IsHost()
		{
			try
			{
				return PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient;
			}
			catch
			{
				return true;
			}
		}

		internal static string Id(PlayerAvatar? avatar)
		{
			if ((Object)(object)avatar == (Object)null)
			{
				return "";
			}
			string text = (AvatarSteamId.GetValue(avatar) as string) ?? "";
			if (!string.IsNullOrWhiteSpace(text) && text != "0")
			{
				return text;
			}
			if (PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
			{
				return "view-" + avatar.photonView.ViewID;
			}
			return "solo-local";
		}

		internal static string PlayerName(PlayerAvatar? avatar)
		{
			if ((Object)(object)avatar == (Object)null)
			{
				return "UNKNOWN PLAYER";
			}
			string text = AvatarName.GetValue(avatar) as string;
			if (string.IsNullOrWhiteSpace(text))
			{
				text = "UNKNOWN PLAYER";
			}
			if (text.Length <= 48)
			{
				return text;
			}
			return text.Substring(0, 48);
		}

		private static bool Owns(PlayerAvatar? avatar)
		{
			if ((Object)(object)avatar != (Object)null)
			{
				if (!PhotonNetwork.OfflineMode && PhotonNetwork.InRoom)
				{
					if ((Object)(object)avatar.photonView != (Object)null)
					{
						return avatar.photonView.IsMine;
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static void ChangeLevelPrefix(bool _levelFailed)
		{
			if (IsHost() && IsGameplayLevel())
			{
				MvpRuntime.Active?.CloseRound(_levelFailed && SemiFunc.RunIsArena());
			}
		}

		private static void UpdateLevelPrefix(bool _gameOver)
		{
			if (!IsHost() && IsGameplayLevel())
			{
				MvpRuntime.Active?.CloseRound(_gameOver);
			}
		}

		private static void HurtPrefix(PlayerHealth __instance, bool hurtByHeal, ref HealthBeforeHit __state)
		{
			__state = default(HealthBeforeHit);
			if (!hurtByHeal && IsGameplayLevel() && Owns(((Component)__instance).GetComponent<PlayerAvatar>()))
			{
				__state = new HealthBeforeHit((int)HealthValue.GetValue(__instance), (int)HealthMaximum.GetValue(__instance));
			}
		}

		private static void HurtPostfix(PlayerHealth __instance, HealthBeforeHit __state)
		{
			if (__state.Health > 0)
			{
				int num = (int)HealthValue.GetValue(__instance);
				int num2 = __state.Health - num;
				if (num2 > 0)
				{
					bool enteredCritical = __state.Maximum > 0 && __state.Health * 4 > __state.Maximum && num > 0 && num * 4 <= __state.Maximum;
					MvpRuntime.Active?.RecordDamage(Math.Min(num2, 1000), enteredCritical);
				}
			}
		}

		private static void DeathPrefix(PlayerAvatar __instance)
		{
			if (IsGameplayLevel() && Owns(__instance) && !(bool)AvatarDead.GetValue(__instance))
			{
				MvpRuntime.Active?.RecordDeath();
			}
		}

		private static void RoomCheckPrefix(RoomVolumeCheck __instance, ref float __state)
		{
			__state = -1f;
			if (IsGameplayLevel() && !((Object)(object)MvpRuntime.Active == (Object)null))
			{
				object? value = RoomPlayer.GetValue(__instance);
				PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null);
				if (val != null && !((Object)(object)val != (Object)(object)PlayerAvatar.instance) && !((Object)(object)ProgressionManager.instance == (Object)null))
				{
					__state = ProgressionManager.instance.roundPointsScouting;
				}
			}
		}

		private static void RoomCheckPostfix(float __state)
		{
			if (!(__state < 0f) && !((Object)(object)ProgressionManager.instance == (Object)null))
			{
				float num = ProgressionManager.instance.roundPointsScouting - __state;
				if (num > 0f)
				{
					MvpRuntime.Active?.RecordRoomDiscovery(num);
				}
			}
		}

		private static void DiscoveryPrefix(PhysGrabber __instance, ValuableObject _valuableObject, ref float __state)
		{
			__state = -1f;
			if (IsGameplayLevel() && Object.op_Implicit((Object)(object)_valuableObject) && !((Object)(object)__instance.playerAvatar == (Object)null) && !((Object)(object)__instance.playerAvatar != (Object)(object)PlayerAvatar.instance) && !((Object)(object)ProgressionManager.instance == (Object)null))
			{
				__state = ProgressionManager.instance.roundPointsScouting;
			}
		}

		private static void DiscoveryPostfix(float __state)
		{
			if (!(__state < 0f) && !((Object)(object)ProgressionManager.instance == (Object)null))
			{
				float num = ProgressionManager.instance.roundPointsScouting - __state;
				if (num > 0f)
				{
					MvpRuntime.Active?.RecordValuableDiscovery(num);
				}
			}
		}

		private static void ExtractionPrefix(ExtractionPoint __instance)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			if (IsGameplayLevel() && !((Object)(object)MvpRuntime.Active == (Object)null) && (bool)ExtractionStateStart.GetValue(__instance) && (int)(State)ExtractionState.GetValue(__instance) == 6)
			{
				MvpRuntime.Active.RecordExtraction(__instance);
			}
		}

		private static void HealedOtherPrefix(PlayerAvatar __instance)
		{
			if (IsHost() && IsGameplayLevel())
			{
				MvpRuntime.Active?.RecordHealing(Id(__instance), 10f);
			}
		}

		private static void EnemyHurtPrefix(HurtCollider __instance, Enemy _enemy, ref CombatHit __state)
		{
			__state = default(CombatHit);
			if (!IsHost() || !IsGameplayLevel() || (Object)(object)_enemy == (Object)null)
			{
				return;
			}
			object? value = ColliderCauser.GetValue(__instance);
			PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null);
			if (!((Object)(object)val == (Object)null))
			{
				object? value2 = EnemyHealthField.GetValue(_enemy);
				EnemyHealth val2 = (EnemyHealth)((value2 is EnemyHealth) ? value2 : null);
				string text = Id(val);
				if (!string.IsNullOrWhiteSpace(text))
				{
					object? value3 = EnemyStunned.GetValue(_enemy);
					EnemyStateStunned val3 = (EnemyStateStunned)((value3 is EnemyStateStunned) ? value3 : null);
					__state = new CombatHit(text, _enemy, val2, (!((Object)(object)val2 == (Object)null)) ? ((int)EnemyHealthValue.GetValue(val2)) : 0, ((Object)(object)val3 == (Object)null) ? 0f : val3.stunTimer);
				}
			}
		}

		private static void EnemyHurtPostfix(bool __result, CombatHit __state)
		{
			if (!__result || (Object)(object)__state.Enemy == (Object)null || string.IsNullOrEmpty(__state.Actor))
			{
				return;
			}
			if ((Object)(object)__state.Health != (Object)null)
			{
				int num = __state.Before - (int)EnemyHealthValue.GetValue(__state.Health);
				if (num > 0)
				{
					MvpRuntime.Active?.RecordCombat(__state.Actor, Math.Min(num, 1000));
				}
			}
			object? value = EnemyStunned.GetValue(__state.Enemy);
			EnemyStateStunned val = (EnemyStateStunned)((value is EnemyStateStunned) ? value : null);
			float num2 = (((Object)(object)val == (Object)null) ? 0f : (val.stunTimer - __state.StunBefore));
			if (num2 >= 0.5f)
			{
				MvpRuntime.Active?.RecordControl(__state.Actor, ((Object)__state.Enemy).GetInstanceID(), num2);
			}
		}
	}
}