Decompiled source of StraftShuffle v0.3.1

StraftShuffle.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using StraftatModding;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("StraftShuffle")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.3.1.0")]
[assembly: AssemblyInformationalVersion("0.3.1+2d9a4faaa1ab2a143f6d2c726cfb78d7f9ac2453")]
[assembly: AssemblyProduct("StraftShuffle")]
[assembly: AssemblyTitle("StraftShuffle")]
[assembly: AssemblyVersion("0.3.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 StraftatModding
{
	internal static class ModPresence
	{
		public const string MoreStraftsGuid = "com.nitrogenia.morestrafts";

		public const string UiSpawnAddonGuid = "com.morestrafts.uispawn.addon";

		public static bool MoreStrafts => IsLoaded("com.nitrogenia.morestrafts");

		public static bool UiSpawnAddon => IsLoaded("com.morestrafts.uispawn.addon");

		public static bool IsLoaded(string guid)
		{
			try
			{
				return Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey(guid);
			}
			catch
			{
				return false;
			}
		}

		public static string VersionOf(string guid)
		{
			try
			{
				if (Chainloader.PluginInfos != null && Chainloader.PluginInfos.TryGetValue(guid, out var value))
				{
					object result;
					if (value == null)
					{
						result = null;
					}
					else
					{
						BepInPlugin metadata = value.Metadata;
						result = ((metadata == null) ? null : metadata.Version?.ToString());
					}
					return (string)result;
				}
			}
			catch
			{
			}
			return null;
		}

		public static string Summary()
		{
			string text = VersionOf("com.nitrogenia.morestrafts");
			string text2 = VersionOf("com.morestrafts.uispawn.addon");
			return "moreStrafts=" + (text ?? "absent") + ", UISpawnAddon=" + (text2 ?? "absent");
		}
	}
	internal static class StaticAccess
	{
		public static Func<object> Getter(Type owner, string name)
		{
			if (owner == null || string.IsNullOrEmpty(name))
			{
				return null;
			}
			Type type = owner;
			while (type != null)
			{
				FieldInfo field = type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
				if (field != null && field.IsStatic)
				{
					return () => field.GetValue(null);
				}
				PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
				MethodInfo methodInfo = property?.GetGetMethod(nonPublic: true);
				if (methodInfo != null && methodInfo.IsStatic)
				{
					return () => property.GetValue(null);
				}
				type = type.BaseType;
			}
			return null;
		}

		public static object Read(Type owner, string name)
		{
			return Getter(owner, name)?.Invoke();
		}
	}
}
namespace StraftShuffle
{
	internal static class GameAccess
	{
		private static bool _resolved;

		private static bool _usable;

		private static FieldInfo _currentSpawnPoints;

		private static FieldInfo _clientScript;

		private static FieldInfo _playerId;

		private static FieldInfo _playerInstances;

		private static Func<object> _scoreInstance;

		private static FieldInfo _takeIndex;

		private static MethodInfo _setActiveSpawnPoints;

		private static MethodInfo _spawnAt;

		public static bool Usable
		{
			get
			{
				if (!_resolved)
				{
					Resolve();
				}
				return _usable;
			}
		}

		private static void Resolve()
		{
			_resolved = true;
			try
			{
				Type type = AccessTools.TypeByName("PlayerManager");
				Type type2 = AccessTools.TypeByName("ClientInstance");
				Type type3 = AccessTools.TypeByName("ScoreManager");
				if (type == null || type2 == null || type3 == null)
				{
					Log.Warn("could not find PlayerManager/ClientInstance/ScoreManager; standing down.");
					return;
				}
				_currentSpawnPoints = AccessTools.Field(type, "CurrentSpawnPoints");
				_clientScript = AccessTools.Field(type, "ClientScript");
				_playerId = AccessTools.Field(type2, "PlayerId");
				_playerInstances = AccessTools.Field(type2, "playerInstances");
				_scoreInstance = StaticAccess.Getter(type3, "Instance");
				_takeIndex = AccessTools.Field(type3, "TakeIndex");
				_setActiveSpawnPoints = AccessTools.Method(type, "SetActiveSpawnPoints", (Type[])null, (Type[])null);
				_spawnAt = AccessTools.Method(type, "SpawnPlayer", new Type[4]
				{
					typeof(int),
					typeof(int),
					typeof(Vector3),
					typeof(Quaternion)
				}, (Type[])null);
				_usable = _currentSpawnPoints != null && _clientScript != null && _playerId != null && _playerInstances != null && _scoreInstance != null && _takeIndex != null && _setActiveSpawnPoints != null && _spawnAt != null;
				if (!_usable)
				{
					Log.Warn("game layout changed (" + Missing() + "); standing down, spawns stay vanilla.");
				}
				else
				{
					Log.Info("game members resolved.");
				}
			}
			catch (Exception arg)
			{
				Log.Error($"resolving game members failed: {arg}");
				_usable = false;
			}
		}

		public static string MissingMembers()
		{
			if (!_resolved)
			{
				Resolve();
			}
			return Missing();
		}

		private static string Missing()
		{
			List<string> list = new List<string>();
			if (_currentSpawnPoints == null)
			{
				list.Add("PlayerManager.CurrentSpawnPoints");
			}
			if (_clientScript == null)
			{
				list.Add("PlayerManager.ClientScript");
			}
			if (_playerId == null)
			{
				list.Add("ClientInstance.PlayerId");
			}
			if (_playerInstances == null)
			{
				list.Add("ClientInstance.playerInstances");
			}
			if (_scoreInstance == null)
			{
				list.Add("ScoreManager.Instance");
			}
			if (_takeIndex == null)
			{
				list.Add("ScoreManager.TakeIndex");
			}
			if (_setActiveSpawnPoints == null)
			{
				list.Add("PlayerManager.SetActiveSpawnPoints");
			}
			if (_spawnAt == null)
			{
				list.Add("PlayerManager.SpawnPlayer(int,int,Vector3,Quaternion)");
			}
			return string.Join(", ", list);
		}

		public static void RefreshSpawnPoints(object playerManager)
		{
			_setActiveSpawnPoints.Invoke(playerManager, null);
		}

		public static IReadOnlyList<Transform> SpawnPoints(object playerManager)
		{
			if (!(_currentSpawnPoints.GetValue(playerManager) is Array array))
			{
				return Array.Empty<Transform>();
			}
			List<Transform> list = new List<Transform>(array.Length);
			foreach (object item in array)
			{
				object obj = ((item is Component) ? item : null);
				list.Add((obj != null) ? ((Component)obj).transform : null);
			}
			return list;
		}

		public static int PlayerIdOf(object playerManager)
		{
			object value = _clientScript.GetValue(playerManager);
			return (value == null) ? (-1) : ((int)_playerId.GetValue(value));
		}

		public static object AnyPlayerManager()
		{
			Type type = AccessTools.TypeByName("PlayerManager");
			return (type == null) ? null : Object.FindObjectOfType(type);
		}

		public static List<int> ConnectedPlayerIds()
		{
			List<int> list = new List<int>();
			if (_playerInstances.GetValue(null) is IDictionary dictionary)
			{
				foreach (object key in dictionary.Keys)
				{
					list.Add((int)key);
				}
			}
			return list;
		}

		public static int RoundIndex()
		{
			object obj = _scoreInstance();
			return (obj != null) ? ((int)_takeIndex.GetValue(obj)) : 0;
		}

		public static void SpawnAt(object playerManager, int suitIndex, int cigIndex, Vector3 position, Quaternion rotation)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			_spawnAt.Invoke(playerManager, new object[4] { suitIndex, cigIndex, position, rotation });
		}
	}
	internal static class Log
	{
		private static ManualLogSource _source;

		public static void Init(ManualLogSource source)
		{
			_source = source;
		}

		public static void Info(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogInfo((object)message);
			}
		}

		public static void Warn(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogWarning((object)message);
			}
		}

		public static void Error(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogError((object)message);
			}
		}
	}
	[BepInPlugin("straftshuffle", "Straft Shuffle", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "straftshuffle";

		public const string Name = "Straft Shuffle";

		public const string Version = "0.1.0";

		private int _lastTickFrame = -1;

		internal static StraftShuffleConfig Settings { get; private set; }

		internal static bool ShouldOffsetSharedPoints { get; private set; } = true;

		internal static int MatchSeed { get; private set; } = NewMatchSeed();

		internal static int EffectiveSalt
		{
			get
			{
				int valueOrDefault = (Settings?.Salt?.Value).GetValueOrDefault();
				StraftShuffleConfig settings = Settings;
				return valueOrDefault ^ ((settings == null || settings.RandomizeEachMatch?.Value != false) ? MatchSeed : 0);
			}
		}

		private static int NewMatchSeed()
		{
			long ticks = DateTime.UtcNow.Ticks;
			return (int)(ticks ^ (ticks >> 32) ^ (Environment.TickCount * 2654435761u));
		}

		private void Awake()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			Log.Init(((BaseUnityPlugin)this).Logger);
			Settings = new StraftShuffleConfig(((BaseUnityPlugin)this).Config);
			ShouldOffsetSharedPoints = ResolveOffsetBehaviour();
			new Harmony("straftshuffle").PatchAll(typeof(RoundSpawnPatch));
			Application.onBeforeRender += new UnityAction(OnBeforeRender);
			SceneManager.sceneLoaded += OnSceneLoaded;
			Log.Info("detected mods: " + ModPresence.Summary());
			Log.Info(ShouldOffsetSharedPoints ? "separating players who share a spawn point ourselves." : "leaving shared-point separation to MoreStrafts_UISpawnAddon.");
			Log.Info(Settings.Enabled.Value ? ("Straft Shuffle v0.1.0 loaded — spawns re-dealt each round " + $"(min {Settings.MinimumPlayers.Value} players, " + (Settings.RandomizeEachMatch.Value ? "randomised per map" : $"fixed salt {Settings.Salt.Value}") + ").") : "Straft Shuffle v0.1.0 loaded — disabled in config, spawns stay vanilla.");
		}

		private static bool ResolveOffsetBehaviour()
		{
			return Settings.OffsetMode.Value switch
			{
				OffsetBehaviour.Always => true, 
				OffsetBehaviour.Never => false, 
				_ => !ModPresence.UiSpawnAddon, 
			};
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			try
			{
				if (Settings != null && Settings.RandomizeEachMatch.Value && !(((Scene)(ref scene)).name == "MainMenu"))
				{
					MatchSeed = NewMatchSeed();
					Log.Info($"map '{((Scene)(ref scene)).name}' loaded; new spawn arrangement (match seed {MatchSeed}).");
				}
			}
			catch (Exception arg)
			{
				Log.Error($"scene-load hook failed: {arg}");
			}
		}

		private void Update()
		{
			Tick();
		}

		private void OnBeforeRender()
		{
			Tick();
		}

		private void Tick()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_lastTickFrame == Time.frameCount)
				{
					return;
				}
				_lastTickFrame = Time.frameCount;
				if (Settings != null)
				{
					KeyboardShortcut value = Settings.ReportKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown() || Input.GetKeyDown((KeyCode)291))
					{
						Report();
					}
				}
			}
			catch (Exception arg)
			{
				Log.Error($"tick failed: {arg}");
			}
		}

		private static void Report()
		{
			try
			{
				Log.Info("---- spawn shuffle report ----");
				Log.Info($"enabled                 : {Settings.Enabled.Value}");
				Log.Info("detected mods           : " + ModPresence.Summary());
				Log.Info($"we offset shared points : {ShouldOffsetSharedPoints}" + (ShouldOffsetSharedPoints ? "" : " (left to UISpawnAddon)"));
				Log.Info($"spawns re-dealt so far  : {RoundSpawnPatch.AppliedCount}");
				Log.Info("last spawn outcome      : " + RoundSpawnPatch.LastOutcome);
				if (!GameAccess.Usable)
				{
					Log.Warn("NOT running. Could not find: " + GameAccess.MissingMembers());
					Log.Info("------------------------------");
					return;
				}
				List<int> list = GameAccess.ConnectedPlayerIds();
				int num = GameAccess.RoundIndex();
				Log.Info($"round index             : {num}");
				Log.Info($"match seed              : {MatchSeed}" + (Settings.RandomizeEachMatch.Value ? " (new each map)" : " (randomisation off)"));
				Log.Info(string.Format("players present         : {0} [{1}]", list.Count, string.Join(",", list)));
				Log.Info($"minimum to act          : {Settings.MinimumPlayers.Value}");
				object obj = GameAccess.AnyPlayerManager();
				if (obj == null)
				{
					Log.Info("spawn points            : no PlayerManager yet (start a match)");
					Log.Info("------------------------------");
					return;
				}
				GameAccess.RefreshSpawnPoints(obj);
				IReadOnlyList<Transform> readOnlyList = GameAccess.SpawnPoints(obj);
				Log.Info($"spawn points on this map: {readOnlyList.Count}");
				if (readOnlyList.Count == 0)
				{
					Log.Info("------------------------------");
					return;
				}
				int num2 = Math.Max(list.Count, Settings.MinimumPlayers.Value);
				List<int> list2 = new List<int>();
				for (int i = 0; i < num2; i++)
				{
					list2.Add(i);
				}
				Log.Info($"dry run for {num2} players (round {num}):");
				for (int j = 0; j < num2; j++)
				{
					SpawnSlot spawnSlot = SpawnAssignment.Assign(j, list2, readOnlyList.Count, num, EffectiveSalt);
					Log.Info($"   player {j} -> point {spawnSlot.SpawnPointIndex}" + ((spawnSlot.Occupants > 1) ? $" (shared by {spawnSlot.Occupants}, ring {spawnSlot.Ring})" : " (alone)"));
				}
				Log.Info("------------------------------");
			}
			catch (Exception arg)
			{
				Log.Error($"report failed: {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class RoundSpawnPatch
	{
		private static bool _loggedFailure;

		internal static int AppliedCount { get; private set; }

		internal static string LastOutcome { get; private set; } = "no spawn seen yet";

		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(AccessTools.TypeByName("PlayerManager"), "SpawnPlayer", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null);
		}

		private static bool Skip(string reason)
		{
			LastOutcome = reason;
			return true;
		}

		private static bool Prefix(object __instance, int suitIndex, int cigIndex)
		{
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: 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_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			StraftShuffleConfig settings = Plugin.Settings;
			if (settings == null || !settings.Enabled.Value)
			{
				return Skip("disabled in config");
			}
			bool flag = false;
			try
			{
				if (!GameAccess.Usable)
				{
					return Skip("game members unresolved (" + GameAccess.MissingMembers() + ")");
				}
				List<int> list = GameAccess.ConnectedPlayerIds();
				if (list.Count < settings.MinimumPlayers.Value)
				{
					return Skip($"{list.Count} player(s), minimum is {settings.MinimumPlayers.Value}");
				}
				if (!settings.OverrideTeamModes.Value && TeamModeActive())
				{
					return Skip("team mode, and OverrideTeamModes is off");
				}
				int num = GameAccess.PlayerIdOf(__instance);
				if (num < 0)
				{
					return Skip("player id unknown");
				}
				GameAccess.RefreshSpawnPoints(__instance);
				IReadOnlyList<Transform> readOnlyList = GameAccess.SpawnPoints(__instance);
				if (readOnlyList.Count == 0)
				{
					return Skip("the map reports no spawn points");
				}
				int num2 = GameAccess.RoundIndex();
				SpawnSlot slot = SpawnAssignment.Assign(num, list, readOnlyList.Count, num2, Plugin.EffectiveSalt);
				Transform val = readOnlyList[slot.SpawnPointIndex];
				if ((Object)(object)val == (Object)null)
				{
					return Skip($"spawn point {slot.SpawnPointIndex} is null");
				}
				Vector3 val2 = val.position;
				if (SpawnAssignment.NeedsOffset(slot) && Plugin.ShouldOffsetSharedPoints)
				{
					double num3 = SpawnAssignment.AngleFor(slot, num2, Plugin.EffectiveSalt);
					float value = settings.ClusterRadius.Value;
					val2 += new Vector3((float)Math.Cos(num3) * value, 0f, (float)Math.Sin(num3) * value);
				}
				Quaternion rotation = Quaternion.Euler(0f, val.eulerAngles.y, 0f);
				flag = true;
				GameAccess.SpawnAt(__instance, suitIndex, cigIndex, val2, rotation);
				AppliedCount++;
				LastOutcome = $"round {num2}: player {num} -> point {slot.SpawnPointIndex}" + ((slot.Occupants > 1) ? $" (sharing with {slot.Occupants - 1}, ring {slot.Ring})" : " (alone)");
				Log.Info(LastOutcome);
				return false;
			}
			catch (Exception ex)
			{
				if (!_loggedFailure)
				{
					_loggedFailure = true;
					Log.Error($"spawn assignment failed: {ex}");
				}
				if (flag)
				{
					LastOutcome = "threw after spawning (" + ex.GetType().Name + "); suppressed vanilla to avoid a double spawn";
					return false;
				}
				return Skip("threw before spawning: " + ex.GetType().Name);
			}
		}

		private static bool TeamModeActive()
		{
			try
			{
				Type type = AccessTools.TypeByName("GameManager");
				object obj = StaticAccess.Read(type, "Instance");
				if (obj == null)
				{
					return false;
				}
				FieldInfo fieldInfo = AccessTools.Field(type, "playingTeams");
				return fieldInfo != null && (bool)fieldInfo.GetValue(obj);
			}
			catch
			{
				return true;
			}
		}
	}
	public readonly struct SpawnSlot
	{
		public readonly int SpawnPointIndex;

		public readonly int Ring;

		public readonly int Occupants;

		public SpawnSlot(int spawnPointIndex, int ring, int occupants)
		{
			SpawnPointIndex = spawnPointIndex;
			Ring = ring;
			Occupants = occupants;
		}
	}
	public static class SpawnAssignment
	{
		public static SpawnSlot Assign(int playerId, IReadOnlyList<int> playerIds, int spawnPointCount, int roundIndex, int salt)
		{
			if (spawnPointCount <= 0)
			{
				return new SpawnSlot(0, 0, 1);
			}
			List<int> list = Sorted(playerIds);
			int count = list.Count;
			int num = list.IndexOf(playerId);
			if (num < 0 || count == 0)
			{
				return new SpawnSlot(Mod(roundIndex + playerId, spawnPointCount), 0, 1);
			}
			int num2 = Deal(num, count, Seed(roundIndex, salt));
			int num3 = num2 % spawnPointCount;
			int spawnPointIndex = Permutation(spawnPointCount, Seed(roundIndex, salt ^ 0x2545F491))[num3];
			return new SpawnSlot(spawnPointIndex, num2 / spawnPointCount, OccupantsOf(num3, count, spawnPointCount));
		}

		public static double AngleFor(SpawnSlot slot, int roundIndex, int salt)
		{
			if (slot.Occupants <= 1)
			{
				return 0.0;
			}
			double num = (double)(Seed(roundIndex, salt ^ 0x5F37) & 0xFFFF) / 65536.0;
			return Math.PI * 2.0 * (((double)slot.Ring + num) / (double)slot.Occupants);
		}

		public static bool NeedsOffset(SpawnSlot slot)
		{
			return slot.Occupants > 1;
		}

		private static int Deal(int index, int seatCount, uint seed)
		{
			return Permutation(seatCount, seed)[index];
		}

		private static int[] Permutation(int count, uint seed)
		{
			int[] array = new int[count];
			for (int i = 0; i < count; i++)
			{
				array[i] = i;
			}
			uint state = seed;
			for (int num = count - 1; num > 0; num--)
			{
				int num2 = (int)NextBelow(ref state, (uint)(num + 1));
				ref int reference = ref array[num];
				ref int reference2 = ref array[num2];
				int num3 = array[num2];
				int num4 = array[num];
				reference = num3;
				reference2 = num4;
			}
			return array;
		}

		private static int OccupantsOf(int spawnPointIndex, int seatCount, int spawnPointCount)
		{
			if (spawnPointIndex >= seatCount)
			{
				return 0;
			}
			return (seatCount - 1 - spawnPointIndex) / spawnPointCount + 1;
		}

		private static List<int> Sorted(IReadOnlyList<int> playerIds)
		{
			List<int> list = new List<int>(playerIds?.Count ?? 0);
			if (playerIds != null)
			{
				list.AddRange(playerIds);
			}
			list.Sort();
			return list;
		}

		private static uint Seed(int roundIndex, int salt)
		{
			uint num = (uint)((roundIndex * -1640531527) ^ (salt * -2048144789));
			num ^= num >> 16;
			num *= 2146121005;
			num ^= num >> 15;
			num *= 2221713035u;
			num ^= num >> 16;
			return (num == 0) ? 2654435769u : num;
		}

		private static uint NextBelow(ref uint state, uint bound)
		{
			if (bound <= 1)
			{
				return 0u;
			}
			uint num = uint.MaxValue - uint.MaxValue % bound;
			uint num2;
			do
			{
				num2 = Next(ref state);
			}
			while (num2 >= num);
			return num2 % bound;
		}

		private static uint Next(ref uint state)
		{
			state ^= state << 13;
			state ^= state >> 17;
			state ^= state << 5;
			return state;
		}

		private static int Mod(int value, int modulus)
		{
			int num = value % modulus;
			return (num < 0) ? (num + modulus) : num;
		}
	}
	internal enum OffsetBehaviour
	{
		Auto,
		Always,
		Never
	}
	internal sealed class StraftShuffleConfig
	{
		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<int> MinimumPlayers;

		public readonly ConfigEntry<bool> OverrideTeamModes;

		public readonly ConfigEntry<float> ClusterRadius;

		public readonly ConfigEntry<OffsetBehaviour> OffsetMode;

		public readonly ConfigEntry<int> Salt;

		public readonly ConfigEntry<bool> RandomizeEachMatch;

		public readonly ConfigEntry<KeyboardShortcut> ReportKey;

		public StraftShuffleConfig(ConfigFile config)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Expected O, but got Unknown
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			Enabled = config.Bind<bool>("Shuffle", "Enabled", true, "Re-deal spawn points every round so you don't start next to the same players all match. Turn off to restore vanilla spawn order.");
			MinimumPlayers = config.Bind<int>("Shuffle", "MinimumPlayers", 3, new ConfigDescription("Leave matches smaller than this alone. Vanilla 1v1 spawn handling is already correct (the two players are simply placed apart), so the default skips it.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 10), Array.Empty<object>()));
			OverrideTeamModes = config.Bind<bool>("Shuffle", "OverrideTeamModes", false, "Also re-deal in team modes. Off by default: the game has hand-tuned 2v2 spawn placement that keeps teammates together, and shuffling would break that on purpose-built maps.");
			OffsetMode = config.Bind<OffsetBehaviour>("Shuffle", "SharedPointOffsets", OffsetBehaviour.Auto, "How players sharing one spawn point get separated.\nAuto: leave it to MoreStrafts_UISpawnAddon when that is installed, since it already offsets every spawn, and do it ourselves otherwise. This is the one you want.\nAlways: always apply our own offset. Stacks with the addon's, so players end up further apart than ClusterRadius suggests.\nNever: never offset. Players sharing a point spawn on top of each other unless something else separates them.");
			ClusterRadius = config.Bind<float>("Shuffle", "ClusterRadius", 0.6f, new ConfigDescription("Metres apart when several players share one spawn point, which happens above 4 players because maps only ship 1v1 and 4-player spawn sets. Too small and players spawn inside each other; too large and they land in walls.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			ReportKey = config.Bind<KeyboardShortcut>("Hotkeys", "Report", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Log what this plugin is doing, including the deal it would make right now. Works in a one-player lobby, where the shuffle itself does not run.");
			RandomizeEachMatch = config.Bind<bool>("Shuffle", "RandomizeEachMatch", true, "Pick a fresh arrangement for every map. Without this the shuffle is keyed only to the round number, and since that resets at the start of each match, round 1 deals exactly the same spawns every time. Turn it off only if you want reproducible results for testing.");
			Salt = config.Bind<int>("Shuffle", "Salt", 0, "Shifts the whole sequence of deals. With RandomizeEachMatch off, the same salt and round always produce the same arrangement, which makes bugs reproducible.");
		}
	}
}