Decompiled source of RepoFfaMod v0.1.0

plugins\RepoFfaMod\RepoFfaMod.dll

Decompiled 4 hours 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.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RepoFfaMod.Config;
using RepoFfaMod.Gameplay;
using RepoFfaMod.UI;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RepoFfaMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RepoFfaMod")]
[assembly: AssemblyTitle("RepoFfaMod")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 RepoFfaMod
{
	[BepInPlugin("repo.ffa.lastmanstanding", "REPO FFA Last Man Standing", "0.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "repo.ffa.lastmanstanding";

		public const string PluginName = "REPO FFA Last Man Standing";

		public const string PluginVersion = "0.1.0";

		private Harmony? _harmony;

		private RoundDirector? _roundDirector;

		private CrouchBudgetSystem? _crouchSystem;

		private HudOverlay? _hudOverlay;

		private LeaderboardSystem? _leaderboard;

		private float _tickAccumulator;

		internal static Plugin? Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal RoundDirector? RoundDirector => _roundDirector;

		internal CrouchBudgetSystem? CrouchSystem => _crouchSystem;

		internal FfaConfig? FfaConfig { get; private set; }

		private void Awake()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			FfaConfig config = (FfaConfig = new FfaConfig(((BaseUnityPlugin)this).Config));
			RepoGameBridge bridge = new RepoGameBridge(((BaseUnityPlugin)this).Logger, config);
			_leaderboard = new LeaderboardSystem();
			SpawnDirector spawnDirector = new SpawnDirector(config, bridge, ((BaseUnityPlugin)this).Logger);
			LoomDirector loomDirector = new LoomDirector(config, bridge, ((BaseUnityPlugin)this).Logger);
			_roundDirector = new RoundDirector(config, bridge, ((BaseUnityPlugin)this).Logger, _leaderboard, spawnDirector, loomDirector);
			_crouchSystem = new CrouchBudgetSystem(config);
			_hudOverlay = new HudOverlay(config, _leaderboard, _roundDirector);
			_harmony = new Harmony("repo.ffa.lastmanstanding");
			_harmony.PatchAll();
			_roundDirector.StartRound();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"REPO FFA Last Man Standing loaded.");
		}

		private void Update()
		{
			if (_roundDirector == null || _crouchSystem == null)
			{
				return;
			}
			float deltaTime = Time.deltaTime;
			_tickAccumulator += deltaTime;
			if (_tickAccumulator < 0.05f)
			{
				return;
			}
			float tickAccumulator = _tickAccumulator;
			_tickAccumulator = 0f;
			_roundDirector.Tick(tickAccumulator);
			foreach (PlayerState value in _roundDirector.CurrentRound.Players.Values)
			{
				_crouchSystem.TickPlayer(value, tickAccumulator);
			}
		}

		private void OnGUI()
		{
			_hudOverlay?.Draw();
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace RepoFfaMod.UI
{
	internal sealed class HudOverlay
	{
		private readonly FfaConfig _config;

		private readonly LeaderboardSystem _leaderboard;

		private readonly RoundDirector _roundDirector;

		public HudOverlay(FfaConfig config, LeaderboardSystem leaderboard, RoundDirector roundDirector)
		{
			_config = config;
			_leaderboard = leaderboard;
			_roundDirector = roundDirector;
		}

		public void Draw()
		{
			//IL_0022: 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_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			if (!_roundDirector.RoundStarted)
			{
				return;
			}
			GUI.Box(new Rect(20f, 20f, 310f, 180f), "R.E.P.O FFA");
			PlayerState playerState = _roundDirector.CurrentRound.Players.Values.FirstOrDefault();
			if (playerState != null)
			{
				GUI.Label(new Rect(30f, 50f, 290f, 22f), $"Crouch: {playerState.CrouchBudgetRemaining:0.0}s / {_config.MaxCrouchSeconds.Value:0}s");
			}
			GUI.Label(new Rect(30f, 72f, 290f, 22f), $"Round Time: {_roundDirector.CurrentRound.ElapsedSeconds:0}s");
			float num = 96f;
			foreach (var row in _leaderboard.GetRows(_roundDirector.CurrentRound))
			{
				string item = row.Name;
				bool item2 = row.Alive;
				int item3 = row.Wins;
				string arg = (item2 ? "ALIVE" : "OUT");
				GUI.Label(new Rect(30f, num, 290f, 20f), $"{item}: {arg} | Wins {item3}");
				num += 18f;
			}
		}
	}
}
namespace RepoFfaMod.Patches
{
	internal static class RuntimeReaders
	{
		public static string ReadString(object instance, string name)
		{
			return (instance.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance) as string) ?? string.Empty;
		}

		public static object? ReadField(object instance, string name)
		{
			return instance.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance);
		}
	}
	[HarmonyPatch]
	internal static class PlayerStatePatches
	{
		private static MethodBase? TargetMethod()
		{
			Type type = AccessTools.TypeByName("PlayerAvatar");
			if (!(type == null))
			{
				return AccessTools.Method(type, "SetState", (Type[])null, (Type[])null);
			}
			return null;
		}

		private static void Prefix(object __instance, ref bool crouching)
		{
			Plugin instance = Plugin.Instance;
			if (instance?.RoundDirector != null && instance.CrouchSystem != null && instance.FfaConfig != null)
			{
				string text = RuntimeReaders.ReadString(__instance, "steamID");
				string text2 = RuntimeReaders.ReadString(__instance, "playerName");
				if (string.IsNullOrWhiteSpace(text))
				{
					text = $"player_{__instance.GetHashCode()}";
				}
				PlayerState orCreatePlayer = instance.RoundDirector.GetOrCreatePlayer(text, string.IsNullOrWhiteSpace(text2) ? text : text2);
				orCreatePlayer.RuntimeRef = __instance;
				orCreatePlayer.IsCrouching = crouching;
				if (crouching && !instance.CrouchSystem.CanCrouch(orCreatePlayer))
				{
					crouching = false;
					orCreatePlayer.IsCrouching = false;
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class PlayerDeathPatches
	{
		private static MethodBase? TargetMethod()
		{
			Type type = AccessTools.TypeByName("PlayerHealth");
			if (!(type == null))
			{
				return AccessTools.Method(type, "Death", (Type[])null, (Type[])null);
			}
			return null;
		}

		private static void Postfix(object __instance)
		{
			Plugin instance = Plugin.Instance;
			if (instance?.RoundDirector == null)
			{
				return;
			}
			object obj = RuntimeReaders.ReadField(__instance, "playerAvatar");
			if (obj != null)
			{
				string text = RuntimeReaders.ReadString(obj, "steamID");
				if (string.IsNullOrWhiteSpace(text))
				{
					text = $"player_{obj.GetHashCode()}";
				}
				instance.RoundDirector.MarkDead(text);
			}
		}
	}
}
namespace RepoFfaMod.Gameplay
{
	internal sealed class CrouchBudgetSystem
	{
		private readonly FfaConfig _config;

		public CrouchBudgetSystem(FfaConfig config)
		{
			_config = config;
		}

		public void EnsurePlayerRegistered(PlayerState player)
		{
			if (player.CrouchBudgetRemaining <= 0f)
			{
				player.CrouchBudgetRemaining = _config.MaxCrouchSeconds.Value;
			}
		}

		public bool CanCrouch(PlayerState player)
		{
			return player.CrouchBudgetRemaining > 0f;
		}

		public void TickPlayer(PlayerState player, float deltaTime)
		{
			float value = _config.MaxCrouchSeconds.Value;
			float num = value / _config.CrouchRechargeSeconds.Value;
			if (!player.IsAlive)
			{
				return;
			}
			if (player.IsCrouching)
			{
				player.CrouchBudgetRemaining -= deltaTime;
				if (player.CrouchBudgetRemaining < 0f)
				{
					player.CrouchBudgetRemaining = 0f;
					player.IsCrouching = false;
				}
			}
			else
			{
				player.CrouchBudgetRemaining += num * deltaTime;
				if (player.CrouchBudgetRemaining > value)
				{
					player.CrouchBudgetRemaining = value;
				}
			}
		}
	}
	internal sealed class PlayerState
	{
		public string PlayerId { get; }

		public string DisplayName { get; }

		public bool IsAlive { get; set; } = true;


		public bool IsCrouching { get; set; }

		public float CrouchBudgetRemaining { get; set; }

		public object? RuntimeRef { get; set; }

		public PlayerState(string playerId, string displayName)
		{
			PlayerId = playerId;
			DisplayName = displayName;
			CrouchBudgetRemaining = 20f;
		}
	}
	internal sealed class RoundState
	{
		public readonly Dictionary<string, PlayerState> Players = new Dictionary<string, PlayerState>();

		public float ElapsedSeconds { get; set; }

		public bool ExtraWaveSpawned { get; set; }

		public bool LoomPhaseTriggered { get; set; }

		public string? WinnerPlayerId { get; set; }
	}
	internal interface IGameBridge
	{
		IReadOnlyCollection<PlayerState> SnapshotPlayers();

		bool IsHost();

		void ForceStage(int stageIndex, bool superMoonEnabled);

		void SpawnMonsters(int count);

		void SpawnMeleeWeapons(int count);

		void SpawnLoomForPlayer(string playerId, float speedMultiplier);

		void UpdateLoomSpeed(float speedMultiplier);
	}
	internal sealed class LeaderboardSystem
	{
		private readonly Dictionary<string, int> _winsByPlayer = new Dictionary<string, int>();

		public void SyncPlayers(RoundState round)
		{
			foreach (PlayerState value in round.Players.Values)
			{
				if (!_winsByPlayer.ContainsKey(value.PlayerId))
				{
					_winsByPlayer[value.PlayerId] = 0;
				}
			}
		}

		public void MarkPlayerDead(RoundState round, string playerId)
		{
			if (round.Players.TryGetValue(playerId, out PlayerState value))
			{
				value.IsAlive = false;
			}
		}

		public void RecordWin(string playerId)
		{
			if (!_winsByPlayer.ContainsKey(playerId))
			{
				_winsByPlayer[playerId] = 0;
			}
			_winsByPlayer[playerId]++;
		}

		public IReadOnlyList<(string PlayerId, string Name, bool Alive, int Wins)> GetRows(RoundState round)
		{
			int value;
			return (from player in round.Players.Values
				select (player.PlayerId, player.DisplayName, player.IsAlive, _winsByPlayer.TryGetValue(player.PlayerId, out value) ? value : 0) into row
				orderby row.Item4 descending, row.IsAlive descending, row.DisplayName
				select row).ToList();
		}
	}
	internal sealed class LoomDirector
	{
		private readonly FfaConfig _config;

		private readonly IGameBridge _bridge;

		private readonly ManualLogSource _log;

		private float _loomPhaseSeconds;

		private readonly HashSet<string> _loomTargets = new HashSet<string>();

		public LoomDirector(FfaConfig config, IGameBridge bridge, ManualLogSource log)
		{
			_config = config;
			_bridge = bridge;
			_log = log;
		}

		public void TriggerFinalThreePhase(RoundState round)
		{
			if (!_bridge.IsHost())
			{
				return;
			}
			_loomPhaseSeconds = 0f;
			_loomTargets.Clear();
			foreach (PlayerState item in round.Players.Values.Where((PlayerState p) => p.IsAlive).Take(3).ToList())
			{
				_loomTargets.Add(item.PlayerId);
				_bridge.SpawnLoomForPlayer(item.PlayerId, _config.LoomBaseSpeedMultiplier.Value);
			}
			_log.LogInfo((object)$"Final-3 Loom phase activated with {_config.FinalThreeLoomCount.Value} Looms.");
		}

		public void Tick(RoundState round, float deltaTime)
		{
			if (_bridge.IsHost() && round.LoomPhaseTriggered)
			{
				_loomPhaseSeconds += deltaTime;
				float speedMultiplier = _config.LoomBaseSpeedMultiplier.Value + _loomPhaseSeconds * _config.LoomSpeedIncreasePerSecond.Value;
				_bridge.UpdateLoomSpeed(speedMultiplier);
			}
		}
	}
	internal sealed class RoundDirector
	{
		private readonly FfaConfig _config;

		private readonly IGameBridge _bridge;

		private readonly ManualLogSource _log;

		private readonly LeaderboardSystem _leaderboard;

		private readonly SpawnDirector _spawnDirector;

		private readonly LoomDirector _loomDirector;

		public RoundState CurrentRound { get; } = new RoundState();


		public bool RoundStarted { get; private set; }

		public RoundDirector(FfaConfig config, IGameBridge bridge, ManualLogSource log, LeaderboardSystem leaderboard, SpawnDirector spawnDirector, LoomDirector loomDirector)
		{
			_config = config;
			_bridge = bridge;
			_log = log;
			_leaderboard = leaderboard;
			_spawnDirector = spawnDirector;
			_loomDirector = loomDirector;
		}

		public void StartRound()
		{
			RoundStarted = true;
			CurrentRound.ElapsedSeconds = 0f;
			CurrentRound.ExtraWaveSpawned = false;
			CurrentRound.LoomPhaseTriggered = false;
			CurrentRound.WinnerPlayerId = null;
			CurrentRound.Players.Clear();
			foreach (PlayerState item in _bridge.SnapshotPlayers())
			{
				MergePlayer(item);
			}
			if (_bridge.IsHost())
			{
				_bridge.ForceStage(_config.StartStage.Value, superMoonEnabled: true);
				_spawnDirector.SpawnOpeningWave(CurrentRound.Players.Count);
				_log.LogInfo((object)$"FFA round started at stage {_config.StartStage.Value}.");
			}
		}

		public void Tick(float deltaTime)
		{
			if (!RoundStarted)
			{
				return;
			}
			CurrentRound.ElapsedSeconds += deltaTime;
			foreach (PlayerState item in _bridge.SnapshotPlayers())
			{
				MergePlayer(item);
			}
			_leaderboard.SyncPlayers(CurrentRound);
			if (_bridge.IsHost())
			{
				if (!CurrentRound.ExtraWaveSpawned && CurrentRound.ElapsedSeconds >= _config.ExtraWaveDelaySeconds.Value)
				{
					_spawnDirector.SpawnExtraWave();
					CurrentRound.ExtraWaveSpawned = true;
				}
				int num = CurrentRound.Players.Values.Count((PlayerState p) => p.IsAlive);
				if (num <= 3 && !CurrentRound.LoomPhaseTriggered)
				{
					_loomDirector.TriggerFinalThreePhase(CurrentRound);
					CurrentRound.LoomPhaseTriggered = true;
				}
				if (num == 1 && CurrentRound.WinnerPlayerId == null)
				{
					PlayerState playerState = CurrentRound.Players.Values.First((PlayerState p) => p.IsAlive);
					CurrentRound.WinnerPlayerId = playerState.PlayerId;
					_leaderboard.RecordWin(playerState.PlayerId);
					_log.LogInfo((object)("Round winner: " + playerState.DisplayName));
				}
			}
			_loomDirector.Tick(CurrentRound, deltaTime);
		}

		public PlayerState GetOrCreatePlayer(string playerId, string displayName)
		{
			if (!CurrentRound.Players.TryGetValue(playerId, out PlayerState value))
			{
				value = new PlayerState(playerId, displayName);
				CurrentRound.Players[playerId] = value;
			}
			return value;
		}

		public void MarkDead(string playerId)
		{
			if (CurrentRound.Players.TryGetValue(playerId, out PlayerState value))
			{
				value.IsAlive = false;
			}
		}

		private void MergePlayer(PlayerState incoming)
		{
			PlayerState orCreatePlayer = GetOrCreatePlayer(incoming.PlayerId, incoming.DisplayName);
			orCreatePlayer.IsAlive = incoming.IsAlive;
			orCreatePlayer.IsCrouching = incoming.IsCrouching;
			orCreatePlayer.RuntimeRef = incoming.RuntimeRef;
		}
	}
	internal sealed class SpawnDirector
	{
		private readonly FfaConfig _config;

		private readonly IGameBridge _bridge;

		private readonly ManualLogSource _log;

		public SpawnDirector(FfaConfig config, IGameBridge bridge, ManualLogSource log)
		{
			_config = config;
			_bridge = bridge;
			_log = log;
		}

		public void SpawnOpeningWave(int playerCount)
		{
			if (_bridge.IsHost())
			{
				_bridge.SpawnMonsters(_config.InitialMonsterCount.Value);
				int num = Math.Max(1, (int)Math.Ceiling((float)playerCount * _config.MeleeSpawnPerPlayer.Value));
				_bridge.SpawnMeleeWeapons(num);
				_log.LogInfo((object)$"Spawned opening wave ({_config.InitialMonsterCount.Value}) and {num} melee weapons.");
			}
		}

		public void SpawnExtraWave()
		{
			if (_bridge.IsHost())
			{
				_bridge.SpawnMonsters(_config.ExtraWaveCount.Value);
				_log.LogInfo((object)$"Spawned extra wave (+{_config.ExtraWaveCount.Value} monsters).");
			}
		}
	}
	internal sealed class RepoGameBridge : IGameBridge
	{
		private readonly ManualLogSource _log;

		private readonly FfaConfig _config;

		private readonly Assembly _gameAssembly;

		private readonly Type _playerControllerType;

		private readonly Type _playerAvatarType;

		private readonly Type _playerHealthType;

		private readonly Type _runManagerType;

		private readonly Type _levelGeneratorType;

		private readonly Type _enemyParentType;

		private readonly Type _itemMeleeType;

		private readonly Type _spawnPointType;

		private readonly Type _photonNetworkType;

		public RepoGameBridge(ManualLogSource log, FfaConfig config)
		{
			_log = log;
			_config = config;
			_gameAssembly = AppDomain.CurrentDomain.GetAssemblies().First((Assembly a) => a.GetName().Name == "Assembly-CSharp");
			_playerControllerType = _gameAssembly.GetType("PlayerController");
			_playerAvatarType = _gameAssembly.GetType("PlayerAvatar");
			_playerHealthType = _gameAssembly.GetType("PlayerHealth");
			_runManagerType = _gameAssembly.GetType("RunManager");
			_levelGeneratorType = _gameAssembly.GetType("LevelGenerator");
			_enemyParentType = _gameAssembly.GetType("EnemyParent");
			_itemMeleeType = _gameAssembly.GetType("ItemMelee");
			_spawnPointType = _gameAssembly.GetType("SpawnPoint");
			_photonNetworkType = (from a in AppDomain.CurrentDomain.GetAssemblies()
				select a.GetType("Photon.Pun.PhotonNetwork")).FirstOrDefault((Type t) => t != null);
		}

		public IReadOnlyCollection<PlayerState> SnapshotPlayers()
		{
			List<PlayerState> list = new List<PlayerState>();
			Object[] array = Object.FindObjectsOfType(_playerControllerType);
			foreach (object obj in array)
			{
				object field = GetField(obj, "playerAvatarScript");
				if (field != null)
				{
					string text = ReadString(field, "steamID");
					string text2 = ReadString(field, "playerName");
					bool isCrouching = ReadBool(field, "isCrouching");
					object field2 = GetField(field, "playerHealth");
					bool isAlive = field2 == null || ReadInt(field2, "health") > 0;
					string text3 = (string.IsNullOrWhiteSpace(text) ? $"player_{obj.GetHashCode()}" : text);
					string displayName = (string.IsNullOrWhiteSpace(text2) ? text3 : text2);
					list.Add(new PlayerState(text3, displayName)
					{
						IsAlive = isAlive,
						IsCrouching = isCrouching,
						CrouchBudgetRemaining = _config.MaxCrouchSeconds.Value,
						RuntimeRef = field
					});
				}
			}
			return list;
		}

		public bool IsHost()
		{
			PropertyInfo property = _photonNetworkType.GetProperty("IsMasterClient", BindingFlags.Static | BindingFlags.Public);
			if (!(property == null))
			{
				return (bool)(property.GetValue(null) ?? ((object)true));
			}
			return true;
		}

		public void ForceStage(int stageIndex, bool superMoonEnabled)
		{
			object singleton = GetSingleton(_runManagerType, "instance");
			if (singleton != null)
			{
				int num = Mathf.Max(0, stageIndex - 1);
				SetField(singleton, "levelsCompleted", num);
				_runManagerType.GetMethod("UpdateMoonLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(singleton, Array.Empty<object>());
				_log.LogInfo((object)$"Stage force applied: stage={stageIndex}, levelsCompleted={num}, superMoon={superMoonEnabled}");
			}
		}

		public void SpawnMonsters(int count)
		{
			object singleton = GetSingleton(_levelGeneratorType, "Instance");
			if (singleton != null)
			{
				int num = ReadInt(singleton, "EnemiesSpawnTarget");
				SetField(singleton, "EnemiesSpawnTarget", num + count);
				_log.LogInfo((object)$"Adjusted enemy spawn target by +{count} (now {num + count}).");
			}
		}

		public void SpawnMeleeWeapons(int count)
		{
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			object[] array = Object.FindObjectsOfType(_spawnPointType);
			object[] array2 = array;
			if (array2.Length == 0)
			{
				_log.LogWarning((object)"No SpawnPoint objects found for melee placement.");
				return;
			}
			Object[] array3 = Object.FindObjectsOfType(_itemMeleeType);
			if (array3.Length == 0)
			{
				_log.LogWarning((object)"No ItemMelee prefab instance found in scene for cloning.");
				return;
			}
			Object val = array3[0];
			Vector3 val3 = default(Vector3);
			for (int i = 0; i < count; i++)
			{
				object obj = array2[i % array2.Length];
				object? obj2 = obj.GetType().GetProperty("transform")?.GetValue(obj);
				Transform val2 = (Transform)((obj2 is Transform) ? obj2 : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					((Vector3)(ref val3))..ctor(Random.Range(-1.5f, 1.5f), 0.3f, Random.Range(-1.5f, 1.5f));
					Object.Instantiate(val, val2.position + val3, Random.rotation);
				}
			}
			_log.LogInfo((object)$"Spawned {count} melee weapons around map points.");
		}

		public void SpawnLoomForPlayer(string playerId, float speedMultiplier)
		{
			SpawnMonsters(1);
			AssignNewestEnemyToPlayer(playerId, speedMultiplier);
		}

		public void UpdateLoomSpeed(float speedMultiplier)
		{
			object[] array = Object.FindObjectsOfType(_enemyParentType);
			array = array;
			for (int i = 0; i < array.Length; i++)
			{
				SetField(array[i], "actionMultiplier", speedMultiplier);
			}
		}

		private void AssignNewestEnemyToPlayer(string playerId, float speedMultiplier)
		{
			string playerId2 = playerId;
			object[] source = Object.FindObjectsOfType(_playerAvatarType);
			object obj = source.FirstOrDefault((object a) => ReadString(a, "steamID") == playerId2);
			if (obj == null)
			{
				return;
			}
			source = Object.FindObjectsOfType(_enemyParentType);
			object[] array = source;
			if (array.Length != 0)
			{
				object instance = array[^1];
				SetField(instance, "actionMultiplier", speedMultiplier);
				object field = GetField(instance, "Enemy");
				if (field != null)
				{
					field.GetType().GetMethod("SetChaseTarget", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(field, new object[1] { obj });
					_log.LogInfo((object)$"Assigned Loom target to player {playerId2} at speed {speedMultiplier:0.00}");
				}
			}
		}

		private static object? GetSingleton(Type type, string fieldName)
		{
			return type.GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
		}

		private static object? GetField(object instance, string name)
		{
			return instance.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance);
		}

		private static void SetField(object instance, string name, object value)
		{
			instance.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(instance, value);
		}

		private static string ReadString(object instance, string name)
		{
			return (GetField(instance, name) as string) ?? string.Empty;
		}

		private static bool ReadBool(object instance, string name)
		{
			object field = GetField(instance, name);
			bool flag = default(bool);
			int num;
			if (field is bool)
			{
				flag = (bool)field;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static int ReadInt(object instance, string name)
		{
			object field = GetField(instance, name);
			if (field is int)
			{
				return (int)field;
			}
			return 0;
		}
	}
}
namespace RepoFfaMod.Config
{
	internal sealed class FfaConfig
	{
		public ConfigEntry<float> MaxCrouchSeconds { get; }

		public ConfigEntry<float> CrouchRechargeSeconds { get; }

		public ConfigEntry<int> StartStage { get; }

		public ConfigEntry<int> InitialMonsterCount { get; }

		public ConfigEntry<float> ExtraWaveDelaySeconds { get; }

		public ConfigEntry<int> ExtraWaveCount { get; }

		public ConfigEntry<int> FinalThreeLoomCount { get; }

		public ConfigEntry<float> LoomBaseSpeedMultiplier { get; }

		public ConfigEntry<float> LoomSpeedIncreasePerSecond { get; }

		public ConfigEntry<float> MeleeSpawnPerPlayer { get; }

		public ConfigEntry<bool> VerboseLogging { get; }

		public FfaConfig(ConfigFile config)
		{
			MaxCrouchSeconds = config.Bind<float>("Crouch", "MaxCrouchSeconds", 20f, "Maximum crouch stamina in seconds.");
			CrouchRechargeSeconds = config.Bind<float>("Crouch", "RechargeDurationSeconds", 5f, "Time to recharge from empty to full crouch stamina.");
			StartStage = config.Bind<int>("Round", "StartStage", 20, "Stage to force for this mode.");
			InitialMonsterCount = config.Bind<int>("Round", "InitialMonsterCount", 11, "Initial monster count on round start.");
			ExtraWaveDelaySeconds = config.Bind<float>("Round", "ExtraWaveDelaySeconds", 180f, "Delay until the second enemy wave.");
			ExtraWaveCount = config.Bind<int>("Round", "ExtraWaveCount", 6, "Additional monsters to spawn at wave time.");
			FinalThreeLoomCount = config.Bind<int>("Loom", "CountAtFinalThree", 3, "Loom count when only three players remain.");
			LoomBaseSpeedMultiplier = config.Bind<float>("Loom", "BaseSpeedMultiplier", 1.25f, "Initial speed multiplier for Looms.");
			LoomSpeedIncreasePerSecond = config.Bind<float>("Loom", "SpeedIncreasePerSecond", 0.015f, "Extra Loom speed per second after spawn.");
			MeleeSpawnPerPlayer = config.Bind<float>("Weapons", "MeleeSpawnPerPlayer", 1.5f, "How many melee weapons to spawn per alive player.");
			VerboseLogging = config.Bind<bool>("Debug", "VerboseLogging", false, "Enable verbose mode logs.");
		}
	}
}