Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RepoLevelMVP v0.1.1
Matsu.RepoMvp.dll
Decompiled a day agousing 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); } } } }