Decompiled source of WaveDefense v0.9.1

WaveDefense.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Burst2Flame;
using Burst2Flame.Observable;
using HarmonyLib;
using MapMaker;
using Microsoft.CodeAnalysis;
using ModNet;
using Steamworks;
using Steamworks.Data;
using StolenRealmModAPI;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[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("WaveDefense")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+e541341b423e7e02f062bf9975ad907cb3f30935")]
[assembly: AssemblyProduct("WaveDefense")]
[assembly: AssemblyTitle("WaveDefense")]
[assembly: AssemblyVersion("1.0.0.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 WaveDefense
{
	internal static class Factions
	{
		private static readonly List<EnemyType> WaveTypes = new List<EnemyType>
		{
			(EnemyType)0,
			(EnemyType)1,
			(EnemyType)2,
			(EnemyType)4
		};

		internal static bool RollingWaveOne;

		private static readonly List<EnemyType> BossType = new List<EnemyType> { (EnemyType)3 };

		internal static bool On
		{
			get
			{
				if (Plugin.Enabled.Value && Plugin.AnyFaction.Value)
				{
					if (!RollingWaveOne)
					{
						return Waves.Active;
					}
					return true;
				}
				return false;
			}
		}

		internal static List<EnemyGroup> All(int level)
		{
			GlobalSettings gs = GlobalSettingsManager.instance.globalSettings;
			return (from EnemyGroup g in Enum.GetValues(typeof(EnemyGroup))
				where (int)g != 72 && (gs.GetEnemyPool(g, level, WaveTypes)?.Count ?? 0) > 0
				select g).ToList();
		}

		internal static List<CharacterInfo> AllBosses(int level)
		{
			//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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			GlobalSettings globalSettings = GlobalSettingsManager.instance.globalSettings;
			List<CharacterInfo> list = new List<CharacterInfo>();
			foreach (EnemyGroup value in Enum.GetValues(typeof(EnemyGroup)))
			{
				if ((int)value != 72)
				{
					list.AddRange(globalSettings.GetEnemyPool(value, level, BossType) ?? new List<CharacterInfo>());
				}
			}
			return list.Where((CharacterInfo i) => (Object)(object)i != (Object)null).Distinct().ToList();
		}
	}
	[HarmonyPatch(typeof(GlobalSettings), "GetPossibleEnemyGroups")]
	internal static class AnyFactionPatch
	{
		private static void Postfix(int level, ref List<EnemyGroup> __result)
		{
			if (!Factions.On)
			{
				return;
			}
			try
			{
				List<EnemyGroup> list = Factions.All(level);
				if (list.Count > 0)
				{
					__result = list;
				}
			}
			catch (Exception ex)
			{
				Plugin.ModLog.LogWarning((object)("Wave Defense: every faction: " + ex.Message + "; the map's own factions instead"));
			}
		}
	}
	[HarmonyPatch]
	internal static class TeamAlivePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(AccessTools.TypeByName("Burst2Flame.Observable.Generated.RootObservable"), "IsTeamAlive", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Root), "IsTeamAlive", (Type[])null, (Type[])null);
		}

		private static void Postfix(int teamIndex, ref bool __result)
		{
			__result = Waves.TeamAlive(teamIndex, __result);
		}
	}
	[HarmonyPatch(typeof(GameLogic), "Update")]
	internal static class TickPatch
	{
		private static void Prefix()
		{
			Waves.Tick();
		}
	}
	[HarmonyPatch]
	internal static class GameCompletePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(AccessTools.TypeByName("Burst2Flame.Observable.Generated.RootObservable"), "SendGameCompleteFromServer", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Root), "SendGameCompleteFromServer", (Type[])null, (Type[])null);
		}

		private static void Prefix(int winningTeamIndex)
		{
			if (Waves.Active)
			{
				Waves.Stop(Waves.EndReason ?? ((winningTeamIndex == 0) ? "battle won" : "battle lost"));
			}
		}
	}
	[HarmonyPatch(typeof(GUIManager), "ChangeBattleTopText")]
	internal static class TopTextPatch
	{
		private static void Prefix(ref string text)
		{
			if (Waves.OnField > 0 && text != null && !text.Contains("Wave"))
			{
				text = text + "  ·  Wave " + Titles.Roman(Waves.OnField);
			}
		}
	}
	[HarmonyPatch(typeof(PostBattleManager), "OpenPostBattleMenu")]
	internal static class ResultsTitlePatch
	{
		private static void Prefix(bool victory)
		{
			try
			{
				Titles.OnResults(victory);
			}
			catch (Exception ex)
			{
				Plugin.ModLog.LogWarning((object)("Wave Defense: results title: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(MainMenu), "Start")]
	internal static class MainMenuPatch
	{
		private static void Postfix(MainMenu __instance)
		{
			try
			{
				WaveMenu.Add(__instance);
			}
			catch (Exception arg)
			{
				Plugin.ModLog.LogWarning((object)$"Wave Defense: menu button: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(MainMenu), "ChooseNormalMode")]
	internal static class NormalModePatch
	{
		private static void Prefix()
		{
			if (!WaveMode.Entering)
			{
				WaveMode.Forget("Campaign chosen");
			}
		}
	}
	[HarmonyPatch(typeof(MainMenu), "ChooseRoguelikeMode")]
	internal static class RoguelikeModePatch
	{
		private static void Prefix()
		{
			WaveMode.Forget("Roguelike chosen");
		}
	}
	[HarmonyPatch(typeof(MainMenu), "StartSinglePlayer", new Type[] { })]
	internal static class StartSinglePlayerPatch
	{
		private static bool Prefix(MainMenu __instance)
		{
			if (!WaveMode.Chosen)
			{
				return true;
			}
			__instance.StartSinglePlayer(false);
			return false;
		}
	}
	[HarmonyPatch(typeof(MainMenu), "HostMultiplayerIP", new Type[] { })]
	internal static class HostIPPatch
	{
		private static bool Prefix(MainMenu __instance)
		{
			if (!WaveMode.Chosen)
			{
				return true;
			}
			__instance.HostMultiplayerIP(false);
			return false;
		}
	}
	[HarmonyPatch(typeof(MainMenu), "HostMultiplayerPlayFab", new Type[] { })]
	internal static class HostSteamPatch
	{
		private static bool Prefix(MainMenu __instance)
		{
			if (!WaveMode.Chosen)
			{
				return true;
			}
			__instance.HostMultiplayerPlayFab(false);
			return false;
		}
	}
	[HarmonyPatch]
	internal static class CharacterTilePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.GetDeclaredMethods(typeof(CharacterChoiceManager)).FirstOrDefault((MethodInfo m) => m.Name.StartsWith("<ExecuteRefreshCharacters>") && m.ReturnType == typeof(bool) && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Character));
		}

		private static void Postfix(Character __0, ref bool __result)
		{
			if (__result && WaveMode.Chosen && !WaveMode.Qualifies(__0))
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class CharacterCountPatch
	{
		private static void Postfix(ref List<Character> __result)
		{
			if (WaveMode.Chosen && __result != null)
			{
				__result = __result.Where(WaveMode.Qualifies).ToList();
			}
		}
	}
	[HarmonyPatch(typeof(CharacterChoiceManager), "OpenCharacterChoiceManager")]
	internal static class GuestScreenPatch
	{
		private static void Prefix()
		{
			NetworkingManager instance = NetworkingManager.Instance;
			if (!((Object)(object)instance == (Object)null) && !instance.IsServer && !instance.IsPlayingAsSinglePlayer && WaveNet.LobbySaysSo())
			{
				WaveMode.JoinHost("the host's Steam lobby says so");
			}
		}
	}
	[HarmonyPatch(typeof(CharacterChoiceManager), "AcceptButtonPressed")]
	internal static class AcceptPatch
	{
		private static bool Prefix()
		{
			if (!WaveMode.Chosen)
			{
				return true;
			}
			List<string> list = (from c in NetworkingManager.Instance.PartyCharactersUnaccepted?.Where((Character c) => c != null && c.Owned && !WaveMode.Qualifies(c))
				select c.CharacterName).ToList();
			if (list == null || list.Count == 0)
			{
				return true;
			}
			ConfirmWindow.Instance.ShowPopupMessage("Wave Defense", "Only level 30 campaign heroes can play Wave Defense. Take " + string.Join(", ", list) + " out of the party.");
			return false;
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class GameLevelPatch
	{
		private static void Postfix(ref int __result)
		{
			if (WaveMode.Chosen && (Object)(object)QuestManager.instance != (Object)null && QuestManager.instance.CurrentQuest != null)
			{
				__result = GlobalSettingsManager.instance.globalSettings.MaxLevel;
			}
		}
	}
	[HarmonyPatch]
	internal static class QuestSavePatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(GlobalSaveData)))
			{
				if (declaredMethod.Name == "SaveQuestState" || declaredMethod.Name == "SaveCampaignQuest" || declaredMethod.Name == "ClearCurrentGameModeQuestData")
				{
					yield return declaredMethod;
				}
			}
		}

		private static bool Prefix(MethodBase __originalMethod)
		{
			if (!WaveMode.Chosen)
			{
				return true;
			}
			Plugin.ModLog.LogInfo((object)("Wave Defense: quest save skipped (" + __originalMethod.Name + ")"));
			return false;
		}
	}
	[BepInPlugin("com.helvetica.wavedefense", "Wave Defense", "0.9.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : StolenRealmMod
	{
		private sealed class Runner : MonoBehaviour
		{
			private string _lastError;

			private void Update()
			{
				try
				{
					WaveMenu.Tick();
					WaveMenu.TickCharacterScreen();
					WaveMode.Tick();
				}
				catch (Exception ex)
				{
					if (!(ex.Message == _lastError))
					{
						_lastError = ex.Message;
						ModLog.LogWarning((object)$"Wave Defense: {ex}");
					}
				}
			}
		}

		public const string PluginGuid = "com.helvetica.wavedefense";

		public const string PluginName = "Wave Defense";

		public const string PluginVersion = "0.9.1";

		internal static ManualLogSource ModLog;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> AnyFaction;

		internal static ConfigEntry<bool> AnyBoss;

		private static Runner _runner;

		public static void DevUnload()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			WaveNet.Unload();
			try
			{
				Waves.Stop("hot reload");
			}
			catch (Exception arg)
			{
				Debug.LogWarning((object)$"[WaveDefense] stop: {arg}");
			}
			try
			{
				WaveMenu.Remove();
			}
			catch (Exception arg2)
			{
				Debug.LogWarning((object)$"[WaveDefense] menu: {arg2}");
			}
			if ((Object)(object)_runner != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_runner).gameObject);
			}
			new Harmony("com.helvetica.wavedefense").UnpatchSelf();
			Debug.Log((object)"[WaveDefense] Unloaded for a hot reload.");
		}

		protected override void OnModLoaded()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			ModLog = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn Wave Defense on or off.");
			AnyFaction = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AnyFaction", true, "Waves come from every enemy faction in the game, whatever the arena's terrain. Off: only the arena's own region's factions (Freewind Forest). The host's setting decides.");
			AnyBoss = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AnyBoss", true, "Every 5th wave's Boss can be any Boss in the game, whatever faction the wave is. Off: one of the wave's own faction (else the region's), and no dragon on wave 5. The host's setting decides.");
			if (Chainloader.PluginInfos.ContainsKey("com.helvetica.modnet"))
			{
				WaveNet.Init();
			}
			GameObject val = new GameObject("WaveDefense")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			_runner = val.AddComponent<Runner>();
			if ((Object)(object)MainMenu.Instance != (Object)null)
			{
				WaveMenu.Add(MainMenu.Instance);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Wave Defense v0.9.1 loaded.");
		}
	}
	public static class Records
	{
		public sealed class Hero
		{
			public string Guid;

			public string Name;
		}

		public sealed class Result
		{
			public int Wave;

			public int Killed;

			public string Why;

			public readonly List<string> Lines = new List<string>();
		}

		public const string FileName = "WaveDefenseRecords.txt";

		public static Result Last { get; private set; }

		public static string FilePath => Path.Combine(FileSystem.persistentDataPath, "WaveDefenseRecords.txt");

		internal static List<Hero> Party()
		{
			return (from c in NetworkingManager.Instance.MyPartyCharacters.ToList()
				where c != null && !string.IsNullOrEmpty(c.Guid)
				select new Hero
				{
					Guid = c.Guid,
					Name = c.CharacterName
				}).ToList();
		}

		public static int Best(string guid)
		{
			if (guid == null || !Load().TryGetValue(guid, out var value))
			{
				return 0;
			}
			return value.Key;
		}

		internal static Result Note(int wave, int killed, string why, List<Hero> heroes)
		{
			Result result = new Result
			{
				Wave = wave,
				Killed = killed,
				Why = why
			};
			Dictionary<string, KeyValuePair<int, string>> dictionary = Load();
			foreach (Hero item in heroes ?? new List<Hero>())
			{
				KeyValuePair<int, string> value;
				int num = (dictionary.TryGetValue(item.Guid, out value) ? value.Key : 0);
				if (wave > num)
				{
					dictionary[item.Guid] = new KeyValuePair<int, string>(wave, item.Name);
					result.Lines.Add((num > 0) ? $"{item.Name}: wave {wave}, a new best (was {num})" : $"{item.Name}: wave {wave}, a first record");
				}
				else
				{
					result.Lines.Add($"{item.Name}: best wave {num}");
				}
			}
			try
			{
				Save(dictionary);
			}
			catch (Exception ex)
			{
				Plugin.ModLog.LogWarning((object)("Wave Defense: couldn't save the records (" + FilePath + "): " + ex.Message));
			}
			Plugin.ModLog.LogInfo((object)("Wave Defense: records: " + string.Join("; ", result.Lines)));
			return Last = result;
		}

		private static Dictionary<string, KeyValuePair<int, string>> Load()
		{
			Dictionary<string, KeyValuePair<int, string>> dictionary = new Dictionary<string, KeyValuePair<int, string>>();
			if (!File.Exists(FilePath))
			{
				return dictionary;
			}
			string[] array = File.ReadAllLines(FilePath);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('\t');
				if (array2.Length >= 2 && array2[0].Length > 0 && int.TryParse(array2[1], out var result))
				{
					dictionary[array2[0]] = new KeyValuePair<int, string>(result, (array2.Length > 2) ? array2[2] : "");
				}
			}
			return dictionary;
		}

		private static void Save(Dictionary<string, KeyValuePair<int, string>> all)
		{
			IEnumerable<string> contents = from e in all
				orderby e.Value.Key descending
				select $"{e.Key}\t{e.Value.Key}\t{e.Value.Value.Replace('\t', ' ').Replace('\n', ' ')}";
			File.WriteAllLines(FilePath, contents);
		}
	}
	internal static class Shrines
	{
		private static readonly FieldRef<GameLogic, List<HexCell>> UsedHexes = AccessTools.FieldRefAccess<GameLogic, List<HexCell>>("usedHexes");

		private static readonly FieldRef<GameLogic, List<HexCell>> ShrineHexes = AccessTools.FieldRefAccess<GameLogic, List<HexCell>>("shrineHexes");

		internal static bool Due;

		internal static List<Character> OnField()
		{
			NetworkingManager instance = NetworkingManager.Instance;
			Root val = ((instance == null) ? null : ((Observer<Root>)(object)instance.NetworkManager)?.Root);
			if (val == null)
			{
				return new List<Character>();
			}
			return val.CharactersInBattle.Where((Character c) => c != null && (int)c.CharacterType == 1 && !c.IsDead && c.SummonMaster == null).ToList();
		}

		internal static int Clear()
		{
			Root root = ((Observer<Root>)(object)NetworkingManager.Instance.NetworkManager).Root;
			GameLogic instance = GameLogic.instance;
			List<Character> list = OnField();
			foreach (Character s in list)
			{
				try
				{
					foreach (GroundEffect item in instance.GroundEffects.Where((GroundEffect g) => g != null && g.Source == s).ToList())
					{
						instance.RemoveGroundEffect(item);
					}
				}
				catch (Exception ex)
				{
					Plugin.ModLog.LogWarning((object)("Wave Defense: couldn't remove shrine " + s.CharacterName + "'s aura: " + ex.Message));
				}
				try
				{
					root.RemoveCharacterFromGame(s, true);
				}
				catch (Exception ex2)
				{
					Plugin.ModLog.LogWarning((object)("Wave Defense: couldn't remove shrine " + s.CharacterName + ": " + ex2.Message));
				}
			}
			return list.Count;
		}

		internal static int Raise()
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			GameLogic instance = GameLogic.instance;
			MapFile current = MapBuilder.Current;
			if ((Object)(object)instance == (Object)null || current == null || !MapBuilder.Ready)
			{
				return 0;
			}
			HashSet<HexCell> spots = new HashSet<HexCell>(from s in current.party.Concat(current.enemies)
				select MapBuilder.Cell(s.q, s.r) into c
				where (Object)(object)c != (Object)null
				select c);
			List<HexCell> list = MapBuilder.Cells.Where((HexCell c) => (Object)(object)c != (Object)null && c.IsValid && c.IsWalkable && c.IsEmpty && !spots.Contains(c)).ToList();
			if (list.Count == 0)
			{
				return 0;
			}
			UsedHexes.Invoke(instance)?.Clear();
			ShrineHexes.Invoke(instance)?.Clear();
			Game instance2 = Game.Instance;
			TerrainType result;
			TerrainType val = (Enum.TryParse<TerrainType>(current.terrain, out result) ? result : instance2.CurrentRoomTerrainType);
			((MonoBehaviour)instance).StartCoroutine(instance.SpawnShrines(HexCellManager.instance, val, instance2.CurrentRoomTimeOfDay, list, new Random(), true));
			int num = Mathf.CeilToInt((float)list.Count / GlobalSettingsManager.instance.globalSettings.shrinePerNumWalkable);
			Plugin.ModLog.LogInfo((object)$"Wave Defense: raising up to {num} shrines on {list.Count} free hexes.");
			return num;
		}

		internal static void Renew()
		{
			int num = Clear();
			Plugin.ModLog.LogInfo((object)$"Wave Defense: the boss wave is over: {num} shrines removed.");
			Raise();
		}

		internal static string Status()
		{
			return string.Join(", ", from s in OnField()
				select s.CharacterName + " at " + MapBuilder.Where((object)s.Cell));
		}
	}
	internal static class Titles
	{
		internal const string Gold = "#FFE4AC";

		internal const string Red = "#FF8C83";

		private const float Speed = 0.75f;

		private const float Lift = 30f;

		private static GameObject _shown;

		private static string _victoryText;

		internal static string Roman(int n)
		{
			if (n <= 0 || n >= 4000)
			{
				return n.ToString();
			}
			int[] array = new int[13]
			{
				1000, 900, 500, 400, 100, 90, 50, 40, 10, 9,
				5, 4, 1
			};
			string[] array2 = new string[13]
			{
				"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX",
				"V", "IV", "I"
			};
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < array.Length; i++)
			{
				while (n >= array[i])
				{
					stringBuilder.Append(array2[i]);
					n -= array[i];
				}
			}
			return stringBuilder.ToString();
		}

		private static string Big(string word)
		{
			if (word.Length != 0)
			{
				return "<size=120>" + word.Substring(0, 1) + "</size>" + word.Substring(1).ToUpperInvariant();
			}
			return word;
		}

		internal static string WaveTitle(int wave, string boss)
		{
			return Big("Wave") + " <size=120>" + Roman(wave) + "</size>" + (string.IsNullOrEmpty(boss) ? "" : ("\n<size=56><color=#FF8C83>" + boss.ToUpperInvariant() + "</color></size>"));
		}

		internal static string EndTitle(int wave, bool fell)
		{
			if (!fell)
			{
				return Big("Survived") + "\n<size=44>WAVE " + Roman(wave) + "</size>";
			}
			return Big("Fallen") + "\n<size=44>ON WAVE " + Roman(wave) + "</size>";
		}

		private static GameObject Template()
		{
			if ((Object)(object)PostBattleManager.Instance == (Object)null)
			{
				PostBattleManager.LoadInstanceReference();
			}
			Animation val = (((Object)(object)PostBattleManager.Instance != (Object)null) ? PostBattleManager.Instance.VictoryAnim : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return ((Component)val).gameObject;
		}

		internal static void Show(string text)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = Template();
				GUIManager instance = GUIManager.instance;
				if ((Object)(object)val == (Object)null || (Object)(object)instance == (Object)null)
				{
					Waves.Banner(StripTags(text));
					return;
				}
				HideShown();
				GameObject val2 = new GameObject("Wave Defense Title", new Type[1] { typeof(RectTransform) });
				RectTransform val3 = (RectTransform)val2.transform;
				((Transform)val3).SetParent(((Component)instance).transform, false);
				Vector2 val4 = (val3.pivot = new Vector2(0.5f, 0.5f));
				Vector2 anchorMin = (val3.anchorMax = val4);
				val3.anchorMin = anchorMin;
				val3.sizeDelta = Vector2.zero;
				((Transform)val3).localScale = Vector3.one * 1.2f;
				float y = ((RectTransform)val.transform).anchoredPosition.y;
				val3.anchoredPosition = new Vector2(0f, 30f - y * 1.2f);
				((Transform)val3).SetAsLastSibling();
				Canvas obj = val2.AddComponent<Canvas>();
				obj.overrideSorting = true;
				obj.sortingOrder = 30000;
				CanvasGroup obj2 = val2.AddComponent<CanvasGroup>();
				obj2.blocksRaycasts = false;
				obj2.interactable = false;
				GameObject val7 = Object.Instantiate<GameObject>(val, (Transform)(object)val3, false);
				Text[] componentsInChildren = val7.GetComponentsInChildren<Text>(true);
				foreach (Text obj3 in componentsInChildren)
				{
					obj3.text = text;
					obj3.verticalOverflow = (VerticalWrapMode)1;
					((Graphic)obj3).raycastTarget = false;
					Shadow obj4 = ((Component)obj3).gameObject.AddComponent<Shadow>();
					obj4.effectColor = new Color(0f, 0f, 0f, 0.75f);
					obj4.effectDistance = new Vector2(3f, -3f);
				}
				val7.SetActive(true);
				Animation component = val7.GetComponent<Animation>();
				float num = 3f;
				if ((Object)(object)component != (Object)null && (Object)(object)component.clip != (Object)null)
				{
					component[((Object)component.clip).name].speed = 0.75f;
					component.Play(((Object)component.clip).name);
					num = component.clip.length / 0.75f;
				}
				Object.Destroy((Object)(object)val2, num + 0.1f);
				_shown = val2;
			}
			catch (Exception ex)
			{
				Plugin.ModLog.LogWarning((object)("Wave Defense: title failed: " + ex.Message));
				Waves.Banner(StripTags(text));
			}
		}

		internal static void HideShown()
		{
			if ((Object)(object)_shown != (Object)null)
			{
				Object.Destroy((Object)(object)_shown);
			}
			_shown = null;
		}

		internal static void OnResults(bool victory)
		{
			HideShown();
			GameObject val = Template();
			Text val2 = (((Object)(object)val != (Object)null) ? val.GetComponentInChildren<Text>(true) : null);
			if (!((Object)(object)val2 == (Object)null))
			{
				if (_victoryText == null)
				{
					_victoryText = val2.text;
				}
				string text = Waves.TakeEndTitle();
				val2.verticalOverflow = (VerticalWrapMode)1;
				val2.text = ((victory && text != null) ? text : _victoryText);
				if (victory && text != null)
				{
					Plugin.ModLog.LogInfo((object)("Wave Defense: results title '" + StripTags(text) + "'"));
				}
			}
		}

		private static string StripTags(string s)
		{
			return Regex.Replace(s, "<[^>]+>", "").Replace("\n", " ");
		}
	}
	public static class WaveMenu
	{
		private sealed class Entry
		{
			public GameObject Page;

			public Button Button;

			public TextMeshProUGUI Text;

			public GameObject Reason;

			public Color TextColor;
		}

		public const string ButtonName = "Button (Wave Defense)";

		public const string Label = "Wave Defense";

		public const string Why = "Needs a level 30 campaign hero";

		private static readonly List<Entry> Entries = new List<Entry>();

		private static bool _onFirstPage;

		private static float _nextCheck;

		private static bool _qualifies;

		private static bool _hidCreate;

		public static void Add(MainMenu menu)
		{
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Expected O, but got Unknown
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Expected O, but got Unknown
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			Entries.RemoveAll((Entry e) => (Object)(object)e.Button == (Object)null);
			GameObject[] array = (GameObject[])(object)new GameObject[3] { menu.gameModeControlsSinglePlayer, menu.gameModeControlsOnline, menu.gameModeControlsIP };
			foreach (GameObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Transform val2 = val.transform.Find("Buttons");
				if ((Object)(object)val2 == (Object)null)
				{
					Plugin.ModLog.LogWarning((object)("Wave Defense: menu page " + ((Object)val).name + " has no Buttons group"));
				}
				else
				{
					if ((Object)(object)val2.Find("Button (Wave Defense)") != (Object)null)
					{
						continue;
					}
					List<Button> source = (from t in Enumerable.Range(0, val2.childCount).Select((Func<int, Transform>)val2.GetChild)
						select ((Component)t).GetComponent<Button>() into b
						where (Object)(object)b != (Object)null
						select b).ToList();
					Button campaign = ((IEnumerable<Button>)source).FirstOrDefault((Func<Button, bool>)((Button b) => FirstHandler(b) == "ChooseNormalMode"));
					Button val3 = ((IEnumerable<Button>)source).FirstOrDefault((Func<Button, bool>)((Button b) => FirstHandler(b) == "ChooseRoguelikeMode"));
					if ((Object)(object)campaign == (Object)null)
					{
						Plugin.ModLog.LogWarning((object)("Wave Defense: no Campaign button on " + ((Object)val).name));
						continue;
					}
					GameObject val4 = Object.Instantiate<GameObject>(((Component)campaign).gameObject, val2);
					((Object)val4).name = "Button (Wave Defense)";
					val4.transform.SetSiblingIndex(((Component)(((Object)(object)val3 != (Object)null) ? val3 : campaign)).transform.GetSiblingIndex() + 1);
					Button component = val4.GetComponent<Button>();
					component.onClick = new ButtonClickedEvent();
					((UnityEvent)component.onClick).AddListener((UnityAction)delegate
					{
						Enter(campaign);
					});
					TextMeshProUGUI componentInChildren = val4.GetComponentInChildren<TextMeshProUGUI>(true);
					TextMeshProUGUI[] componentsInChildren = val4.GetComponentsInChildren<TextMeshProUGUI>(true);
					for (int num2 = 0; num2 < componentsInChildren.Length; num2++)
					{
						((TMP_Text)componentsInChildren[num2]).text = "Wave Defense";
					}
					Entries.Add(new Entry
					{
						Page = val,
						Button = component,
						Text = componentInChildren,
						TextColor = (((Object)(object)componentInChildren != (Object)null) ? ((Graphic)componentInChildren).color : Color.white),
						Reason = (((Object)(object)componentInChildren != (Object)null) ? AddReason(val4, componentInChildren) : null)
					});
				}
			}
			Plugin.ModLog.LogInfo((object)$"Wave Defense: button added to {Entries.Count} menu page(s)");
		}

		public static void Remove()
		{
			foreach (Entry entry in Entries)
			{
				if (!((Object)(object)entry.Button == (Object)null))
				{
					GameObject gameObject = ((Component)entry.Button).gameObject;
					((Object)gameObject).name = ((Object)gameObject).name + " (removed)";
					gameObject.SetActive(false);
					Object.Destroy((Object)(object)gameObject);
				}
			}
			Entries.Clear();
		}

		private static GameObject AddReason(GameObject button, TextMeshProUGUI label)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = Object.Instantiate<GameObject>(((Component)label).gameObject, button.transform);
			((Object)obj).name = "Wave Defense Reason";
			RectTransform val = (RectTransform)obj.transform;
			val.anchorMin = new Vector2(0f, 0f);
			val.anchorMax = new Vector2(1f, 0f);
			val.pivot = new Vector2(0.5f, 1f);
			val.anchoredPosition = new Vector2(0f, -2f);
			val.sizeDelta = new Vector2(60f, 24f);
			TextMeshProUGUI component = obj.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).enableAutoSizing = false;
			((TMP_Text)component).fontSize = Mathf.Max(12f, ((TMP_Text)label).fontSize * 0.5f);
			((TMP_Text)component).alignment = (TextAlignmentOptions)258;
			((Graphic)component).raycastTarget = false;
			((Graphic)component).color = new Color(1f, 0.85f, 0.6f, 0.9f);
			((TMP_Text)component).text = "Needs a level 30 campaign hero";
			obj.SetActive(false);
			return obj;
		}

		private static string FirstHandler(Button b)
		{
			if (((UnityEventBase)b.onClick).GetPersistentEventCount() <= 0)
			{
				return null;
			}
			return ((UnityEventBase)b.onClick).GetPersistentMethodName(0);
		}

		private static void Enter(Button campaign)
		{
			if (!Plugin.Enabled.Value || !WaveMode.AnyQualifies())
			{
				return;
			}
			WaveMode.Choose();
			WaveMode.Entering = true;
			try
			{
				((UnityEvent)campaign.onClick).Invoke();
			}
			catch (Exception arg)
			{
				Plugin.ModLog.LogWarning((object)$"Wave Defense: the menu step failed: {arg}");
			}
			finally
			{
				WaveMode.Entering = false;
			}
		}

		internal static void Tick()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Invalid comparison between Unknown and I4
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			MainMenu instance = MainMenu.Instance;
			int num;
			int num2;
			if ((Object)(object)instance != (Object)null && (Object)(object)GUIManager.instance != (Object)null)
			{
				num = (((int)GUIManager.instance.CurrentGuiState == 5) ? 1 : 0);
				if (num != 0 && (Object)(object)instance.mainControls != (Object)null)
				{
					num2 = (instance.mainControls.activeInHierarchy ? 1 : 0);
					if (num2 != 0 && !_onFirstPage)
					{
						WaveMode.Forget("back on the main menu");
					}
					goto IL_005f;
				}
			}
			else
			{
				num = 0;
			}
			num2 = 0;
			goto IL_005f;
			IL_005f:
			_onFirstPage = (byte)num2 != 0;
			if (num == 0 || Entries.Count == 0)
			{
				return;
			}
			if (Time.realtimeSinceStartup >= _nextCheck)
			{
				_qualifies = WaveMode.AnyQualifies();
				_nextCheck = Time.realtimeSinceStartup + 1f;
			}
			bool value = Plugin.Enabled.Value;
			foreach (Entry entry in Entries)
			{
				if (!((Object)(object)entry.Button == (Object)null) && entry.Page.activeInHierarchy)
				{
					if (((Component)entry.Button).gameObject.activeSelf != value)
					{
						((Component)entry.Button).gameObject.SetActive(value);
					}
					if (((Selectable)entry.Button).interactable != _qualifies)
					{
						((Selectable)entry.Button).interactable = _qualifies;
					}
					if ((Object)(object)entry.Text != (Object)null)
					{
						((Graphic)entry.Text).color = (Color)(_qualifies ? entry.TextColor : new Color(entry.TextColor.r, entry.TextColor.g, entry.TextColor.b, 0.4f));
					}
					if ((Object)(object)entry.Reason != (Object)null && entry.Reason.activeSelf == _qualifies)
					{
						entry.Reason.SetActive(!_qualifies);
					}
				}
			}
		}

		internal static void TickCharacterScreen()
		{
			CharacterChoiceManager instance = CharacterChoiceManager.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.createCharacterBtn == (Object)null)
			{
				return;
			}
			GameObject gameObject = ((Component)instance.createCharacterBtn).gameObject;
			if (WaveMode.Chosen && CharacterChoiceManager.IsNotNullAndIsActive)
			{
				if (gameObject.activeSelf)
				{
					gameObject.SetActive(false);
					_hidCreate = true;
				}
			}
			else if (_hidCreate)
			{
				_hidCreate = false;
				gameObject.SetActive(true);
			}
		}
	}
	public static class WaveMode
	{
		public enum Step
		{
			Off,
			ToTown,
			ToIsland,
			InArena,
			Over,
			Guest
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Func<Character, bool> <0>__Qualifies;

			public static ConfirmAction <1>__Leave;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Func<Character, bool> <>9__18_0;

			public static Func<Character, string> <>9__18_1;

			public static Func<Character, bool> <>9__33_0;

			public static Func<Character, string> <>9__33_1;

			public static ConfirmAction <>9__33_2;

			internal bool <WarnGuest>b__18_0(Character c)
			{
				if (c != null)
				{
					return !Qualifies(c);
				}
				return false;
			}

			internal string <WarnGuest>b__18_1(Character c)
			{
				return c.CharacterName;
			}

			internal bool <TownPrompt>b__33_0(Character c)
			{
				return c != null;
			}

			internal string <TownPrompt>b__33_1(Character c)
			{
				if (!Qualifies(c))
				{
					return c.CharacterName + " (not a level 30 campaign hero)";
				}
				return c.CharacterName;
			}

			internal void <TownPrompt>b__33_2()
			{
				_promptUp = false;
				_startAsked = true;
			}
		}

		internal static bool Entering;

		private static bool _busy;

		private static bool _resultsShown;

		private static bool _left;

		private static bool _startPressed;

		private static bool _startAsked;

		private static bool _promptUp;

		private static bool _warned;

		private static string _promptText;

		private static float _promptAt;

		public static bool Chosen { get; private set; }

		public static Step Phase { get; private set; }

		public static string ArenaPath => Path.Combine(ModFolder, "maps", "arena.json");

		private static string ModFolder
		{
			get
			{
				string location = typeof(WaveMode).Assembly.Location;
				if (string.IsNullOrEmpty(location) && Chainloader.PluginInfos.TryGetValue("com.helvetica.wavedefense", out var value))
				{
					location = value.Location;
				}
				return Path.GetDirectoryName(location);
			}
		}

		public static bool Qualifies(Character c)
		{
			if (c != null && !c.IsDeleted && !c.IsRoguelikeCharacter && !c.HardcoreDeath)
			{
				return c.IsMaxLevel;
			}
			return false;
		}

		public static bool AnyQualifies()
		{
			return (((Object)(object)GameLogic.instance != (Object)null) ? GameLogic.instance.AllMyCharacters : null)?.Any(Qualifies) ?? false;
		}

		internal static void Choose()
		{
			Chosen = true;
			Phase = Step.ToTown;
			_resultsShown = (_left = (_startPressed = (_startAsked = (_promptUp = (_warned = false)))));
			_promptText = null;
			Plugin.ModLog.LogInfo((object)"Wave Defense: chosen on the main menu");
		}

		internal static void JoinHost(string how)
		{
			if (!Chosen || (Phase != Step.Guest && Phase != Step.Over))
			{
				Chosen = true;
				Phase = Step.Guest;
				_resultsShown = (_left = (_warned = false));
				Plugin.ModLog.LogInfo((object)("Wave Defense: joined a Wave Defense game (" + how + ")"));
			}
			WarnGuest();
		}

		private static void WarnGuest()
		{
			if (!_warned && !((Object)(object)ConfirmWindow.Instance == (Object)null) && !((Component)ConfirmWindow.Instance).gameObject.activeInHierarchy)
			{
				NetworkingManager instance = NetworkingManager.Instance;
				List<string> list = (from c in ((instance == null) ? null : instance.MyPartyCharacters?.ToList())?.Where((Character c) => c != null && !Qualifies(c))
					select c.CharacterName).ToList();
				if (list != null && list.Count != 0)
				{
					_warned = true;
					Plugin.ModLog.LogInfo((object)("Wave Defense: told this player that " + string.Join(", ", list) + " can't play"));
					ConfirmWindow.Instance.ShowPopupMessage("Wave Defense", "This game is Wave Defense: only level 30 campaign heroes can play, not " + string.Join(", ", list) + ". Go back to the main menu and join again with one who can.");
				}
			}
		}

		internal static void GuestRunOver()
		{
			Phase = Step.Over;
			_resultsShown = false;
		}

		internal static void HostLeft()
		{
			if (Chosen && !_left)
			{
				Plugin.ModLog.LogInfo((object)"Wave Defense: the host went back to the main menu");
				Leave();
			}
		}

		internal static void Forget(string why)
		{
			if (Chosen)
			{
				Chosen = false;
				Phase = Step.Off;
				if (Waves.Armed || Waves.Active)
				{
					Waves.Stop(why);
				}
				Waves.ClearShown();
				Plugin.ModLog.LogInfo((object)("Wave Defense: mode off (" + why + ")"));
			}
		}

		internal static void Tick()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Invalid comparison between Unknown and I4
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Invalid comparison between Unknown and I4
			if (!Chosen || _busy || !Plugin.Enabled.Value)
			{
				return;
			}
			NetworkingManager instance = NetworkingManager.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)GUIManager.instance == (Object)null || Loading())
			{
				return;
			}
			GUIState currentGuiState = GUIManager.instance.CurrentGuiState;
			if (!instance.IsServer)
			{
				GuestTick(currentGuiState);
				return;
			}
			if (!instance.IsPlayingAsSinglePlayer)
			{
				WaveNet.Advertise();
			}
			if (Phase == Step.ToTown && (int)currentGuiState == 4 && (Object)(object)LoadableUIWindow<QuestSelectManager>.Instance != (Object)null)
			{
				if (!WaveNet.Multiplayer || _startPressed)
				{
					Run(StartQuest(), "quest start");
				}
				else
				{
					TownPrompt();
				}
			}
			else if (Phase == Step.ToIsland && (int)currentGuiState == 3 && IslandReady() && MapBattle.CannotStart() == null)
			{
				Run(EnterArena(), "arena start");
			}
			else if (Phase == Step.InArena && !Waves.Armed && !Waves.Active && !MapBattle.Active && (int)currentGuiState == 3)
			{
				Phase = Step.Over;
				Plugin.ModLog.LogInfo((object)("Wave Defense: the run is over (" + Waves.LastResult + "); the party is back on the island"));
				ShowResults();
			}
			else if (Phase == Step.Over && _resultsShown && !_left && !ResultsOpen())
			{
				Leave();
			}
		}

		private static void GuestTick(GUIState state)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Invalid comparison between Unknown and I4
			if (Phase == Step.Guest && (int)state == 4)
			{
				WarnGuest();
			}
			if (Phase == Step.Over && !_left)
			{
				if (!_resultsShown && (int)state == 3 && !Waves.InFight() && !ResultsOpen())
				{
					ShowResults();
				}
				else if (_resultsShown && !ResultsOpen())
				{
					Leave();
				}
			}
		}

		private static void TownPrompt()
		{
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Expected O, but got Unknown
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			ConfirmWindow instance = ConfirmWindow.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			if (_startAsked)
			{
				_startAsked = false;
				string text = WaveNet.CannotStart();
				if (text == null)
				{
					_startPressed = true;
					Plugin.ModLog.LogInfo((object)"Wave Defense: the host starts the run");
				}
				else
				{
					Plugin.ModLog.LogInfo((object)("Wave Defense: can't start yet: " + text));
					instance.ShowPopupMessage("Wave Defense", text);
				}
				return;
			}
			bool activeInHierarchy = ((Component)instance).gameObject.activeInHierarchy;
			if (activeInHierarchy && !_promptUp)
			{
				return;
			}
			if (!activeInHierarchy && _promptUp)
			{
				_promptUp = false;
				_promptAt = Time.unscaledTime + 2f;
				return;
			}
			string text2 = string.Join(", ", from c in NetworkingManager.Instance.PartyCharacters
				where c != null
				select (!Qualifies(c)) ? (c.CharacterName + " (not a level 30 campaign hero)") : c.CharacterName);
			string text3 = "Wait in town until everyone has joined with a level 30 campaign hero, then start the run.\n\nHeroes here: " + text2;
			if ((activeInHierarchy && text3 == _promptText) || (!activeInHierarchy && Time.unscaledTime < _promptAt))
			{
				return;
			}
			_promptText = text3;
			_promptUp = true;
			object obj = <>O.<1>__Leave;
			if (obj == null)
			{
				ConfirmAction val = Leave;
				<>O.<1>__Leave = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__33_2;
			if (obj2 == null)
			{
				ConfirmAction val2 = delegate
				{
					_promptUp = false;
					_startAsked = true;
				};
				<>c.<>9__33_2 = val2;
				obj2 = (object)val2;
			}
			instance.ShowConfirmMessage("Wave Defense", text3, (ConfirmAction)obj, (ConfirmAction)obj2, "Start", "Main Menu", (List<GameObject>)null, (List<GameObject>)null);
		}

		private static void ShowResults()
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected O, but got Unknown
			_left = false;
			ConfirmWindow instance = ConfirmWindow.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Leave();
				return;
			}
			Records.Result last = Records.Last;
			string text = ((last == null) ? ("Run over: " + Waves.LastResult + ".") : ((last.Why == "the party fell") ? $"The party fell on wave {last.Wave}." : $"The run ended on wave {last.Wave} ({last.Why})."));
			string text2 = ((last == null) ? "" : string.Format("\n{0} {1} defeated\n\n{2}", last.Killed, (last.Killed == 1) ? "enemy" : "enemies", string.Join("\n", last.Lines)));
			string text3 = text + text2;
			object obj = <>O.<1>__Leave;
			if (obj == null)
			{
				ConfirmAction val = Leave;
				<>O.<1>__Leave = val;
				obj = (object)val;
			}
			instance.ShowConfirmMessage("Wave Defense", text3, (ConfirmAction)null, (ConfirmAction)obj, "Main Menu", (string)null, (List<GameObject>)null, (List<GameObject>)null);
			_resultsShown = true;
		}

		private static bool ResultsOpen()
		{
			ConfirmWindow instance = ConfirmWindow.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				return ((Component)instance).gameObject.activeInHierarchy;
			}
			return false;
		}

		private static void Leave()
		{
			if (!_left)
			{
				_left = true;
				_resultsShown = (_promptUp = false);
				NetworkingManager instance = NetworkingManager.Instance;
				if (instance.IsServer && WaveNet.Multiplayer)
				{
					WaveNet.SendLeave();
					Plugin.ModLog.LogInfo((object)"Wave Defense: back to the main menu, the other players first");
					Plugin.Run(LeaveSoon(), "Wave Defense leave", true);
				}
				else
				{
					Plugin.ModLog.LogInfo((object)"Wave Defense: back to the main menu");
					instance.ExitToMainMenu();
				}
			}
		}

		private static IEnumerator LeaveSoon()
		{
			yield return (object)new WaitForSecondsRealtime(1.5f);
			NetworkingManager.Instance.ExitToMainMenu();
		}

		private static IEnumerator StartQuest()
		{
			QuestSelectManager qsm = LoadableUIWindow<QuestSelectManager>.Instance;
			qsm.Initialize();
			((UIWindow)qsm).OpenWindow();
			qsm.UpdateQuestNodes();
			float until = Time.realtimeSinceStartup + 10f;
			QuestNode target = null;
			while ((Object)(object)target == (Object)null)
			{
				target = ((IEnumerable<QuestNode>)(((object)qsm.QuestNodes) ?? ((object)new QuestNode[0]))).FirstOrDefault((Func<QuestNode, bool>)((QuestNode n) => (Object)(object)n != (Object)null && ((int)n.questNodeType == 0 || (int)n.questNodeType == 2) && Navigable(qsm, n) && n.CurrentQuestInstance != null));
				if ((Object)(object)target != (Object)null)
				{
					break;
				}
				if (Time.realtimeSinceStartup > until)
				{
					Plugin.ModLog.LogWarning((object)"Wave Defense: no quest to start from town; the run can't begin");
					Phase = Step.Over;
					yield break;
				}
				yield return null;
			}
			qsm.SelectQuestNode(target);
			while (Loading())
			{
				yield return null;
			}
			Phase = Step.ToIsland;
			qsm.ActivateSelectedQuestNode();
			Plugin.ModLog.LogInfo((object)("Wave Defense: quest '" + ((Object)target).name + "' started, the arena is next to its island"));
		}

		private static IEnumerator EnterArena()
		{
			Phase = Step.InArena;
			if (!File.Exists(ArenaPath))
			{
				throw new FileNotFoundException("the arena map is missing: " + ArenaPath);
			}
			Waves.Arm();
			Plugin.ModLog.LogInfo((object)("Wave Defense: building the arena (" + ArenaPath + ")"));
			BattleInfo battle = GlobalSettingsManager.instance.globalSettings.testBattle;
			int level = battle.mLvl;
			battle.mLvl = Waves.LevelOf(1);
			Factions.RollingWaveOne = true;
			try
			{
				IEnumerator fight = MapBattle.Fight(ArenaPath, false, false);
				while (fight.MoveNext())
				{
					yield return fight.Current;
				}
			}
			finally
			{
				battle.mLvl = level;
				Factions.RollingWaveOne = false;
			}
		}

		private static bool Navigable(QuestSelectManager qsm, QuestNode n)
		{
			try
			{
				return qsm.CanNavigateToQuestNode(n);
			}
			catch
			{
				return false;
			}
		}

		private static bool IslandReady()
		{
			PortalManager instance = PortalManager.Instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.CurrentIsland != (Object)null && instance.CompletedIslandGeneration)
			{
				return !instance.OpeningIsland;
			}
			return false;
		}

		private static bool Loading()
		{
			LoadingScreen instance = LoadingScreen.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				if (!instance.MainFadeActive)
				{
					return instance.IsLoading;
				}
				return true;
			}
			return false;
		}

		private static void Run(IEnumerator routine, string what)
		{
			_busy = true;
			Plugin.Run(Guard(routine, what), "Wave Defense " + what, true);
		}

		private static IEnumerator Guard(IEnumerator routine, string what)
		{
			try
			{
				while (true)
				{
					object current;
					try
					{
						if (!routine.MoveNext())
						{
							break;
						}
						current = routine.Current;
					}
					catch (Exception arg)
					{
						Plugin.ModLog.LogWarning((object)$"Wave Defense: {what} failed: {arg}");
						Phase = Step.Over;
						if (Waves.Armed)
						{
							Waves.Stop(what + " failed");
						}
						break;
					}
					yield return current;
				}
			}
			finally
			{
				_busy = false;
			}
		}
	}
	public static class WaveNet
	{
		private static class Bridge
		{
			internal static bool Ready => ModNetApi.Ready;

			internal static void Hook()
			{
				ModNetApi.Register("wavedefense", (Action<NetMessage>)delegate(NetMessage m)
				{
					Handle(m.Payload);
				});
				ModNetApi.BecameReady += Announce;
				ModNetApi.PlayerHello += delegate
				{
					Announce();
				};
			}

			internal static bool Send(string payload)
			{
				return ModNetApi.Send("wavedefense", payload, (Character)null);
			}

			internal static List<string> WithoutTheMod()
			{
				return (from p in ModNetApi.Players
					where !p.IsMe && !p.Has("com.helvetica.wavedefense", "0.9.1")
					select p.Name).ToList();
			}
		}

		public const string Channel = "wavedefense";

		private const string LobbyKey = "srm_wavedefense";

		private static bool _unloaded;

		private static float _nextAdvert;

		private static ulong _advertised;

		public static bool Available { get; private set; }

		public static bool Multiplayer
		{
			get
			{
				NetworkingManager instance = NetworkingManager.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					return !instance.IsPlayingAsSinglePlayer;
				}
				return false;
			}
		}

		private static bool IsHost
		{
			get
			{
				if ((Object)(object)NetworkingManager.Instance != (Object)null)
				{
					return NetworkingManager.Instance.IsServer;
				}
				return false;
			}
		}

		internal static void Init()
		{
			Available = true;
			Bridge.Hook();
		}

		internal static void Unload()
		{
			_unloaded = true;
		}

		internal static void SendMode()
		{
			Send("mode|0.9.1");
		}

		internal static void SendWave(int n, string boss)
		{
			Send(string.Format("wave|{0}|{1}", n, (boss ?? "").Replace('|', '/')));
		}

		internal static void SendLeave()
		{
			Send("leave");
		}

		internal static void SendEnd(int wave, int killed, string why, IEnumerable<Character> removed)
		{
			IEnumerable<string> values = from s in (removed ?? Enumerable.Empty<Character>()).Select(NameOf)
				where s != null
				select s;
			Send(string.Format("end|{0}|{1}|{2}|{3}", wave, killed, (why ?? "").Replace('|', '/'), string.Join(",", values)));
		}

		private static void Send(string payload)
		{
			if (Available && Multiplayer && IsHost && !Bridge.Send(payload))
			{
				Plugin.ModLog.LogWarning((object)("Wave Defense: couldn't send '" + payload + "' (ModNet not ready?)"));
			}
		}

		private static void Announce()
		{
			if (!_unloaded && IsHost && WaveMode.Chosen)
			{
				SendMode();
			}
		}

		internal static string CannotStart()
		{
			if (!Multiplayer)
			{
				return null;
			}
			if (!Available)
			{
				return "Wave Defense needs ModNet to be played with others.";
			}
			if (!Bridge.Ready)
			{
				return "Multiplayer isn't ready yet: wait a few seconds.";
			}
			List<string> list = Bridge.WithoutTheMod();
			if (list.Count > 0)
			{
				return "Not everyone is ready: " + string.Join(", ", list) + " (still joining, or without Wave Defense 0.9.1).";
			}
			List<string> list2 = (from c in NetworkingManager.Instance.PartyCharacters
				where c != null && !WaveMode.Qualifies(c)
				select c.CharacterName).ToList();
			if (list2.Count > 0)
			{
				return "Only level 30 campaign heroes can play Wave Defense, not " + string.Join(", ", list2) + ".";
			}
			return null;
		}

		internal static void Advertise()
		{
			//IL_0063: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if (Time.unscaledTime < _nextAdvert)
			{
				return;
			}
			_nextAdvert = Time.unscaledTime + 1f;
			try
			{
				SteamManager instance = SteamManager.instance;
				Lobby? val = (((Object)(object)instance != (Object)null) ? instance.CurrentLobby : ((Lobby?)null));
				if (!val.HasValue || !IsHost || !SteamClient.IsValid)
				{
					_advertised = 0uL;
					return;
				}
				Lobby value = val.Value;
				if (((Lobby)(ref value)).Id.Value != _advertised && ((Lobby)(ref value)).IsOwnedBy(SteamClient.SteamId) && ((Lobby)(ref value)).SetData("srm_wavedefense", "1"))
				{
					_advertised = ((Lobby)(ref value)).Id.Value;
					Plugin.ModLog.LogInfo((object)"Wave Defense: the Steam lobby says this game is Wave Defense");
				}
			}
			catch (Exception ex)
			{
				_advertised = 0uL;
				Plugin.ModLog.LogWarning((object)("Wave Defense: lobby: " + ex.Message));
			}
		}

		internal static bool LobbySaysSo()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SteamManager instance = SteamManager.instance;
				Lobby? val = (((Object)(object)instance != (Object)null) ? instance.CurrentLobby : ((Lobby?)null));
				int result;
				if (val.HasValue)
				{
					Lobby value = val.Value;
					result = ((((Lobby)(ref value)).GetData("srm_wavedefense") == "1") ? 1 : 0);
				}
				else
				{
					result = 0;
				}
				return (byte)result != 0;
			}
			catch
			{
				return false;
			}
		}

		private static void Handle(string payload)
		{
			if (_unloaded || IsHost || string.IsNullOrEmpty(payload))
			{
				return;
			}
			string[] array = payload.Split('|');
			switch (array[0])
			{
			case "mode":
				WaveMode.JoinHost("the host runs " + ((array.Length > 1) ? array[1] : "?"));
				break;
			case "wave":
			{
				if (array.Length > 1 && int.TryParse(array[1], out var result3))
				{
					WaveMode.JoinHost("a wave began");
					Waves.ShowWave(result3, (array.Length > 2) ? array[2] : null);
				}
				break;
			}
			case "end":
			{
				if (array.Length > 3 && int.TryParse(array[1], out var result) && int.TryParse(array[2], out var result2))
				{
					int num = ((array.Length > 4) ? FlagRemoved(array[4]) : 0);
					Plugin.ModLog.LogInfo((object)($"Wave Defense: the host says the run is over ({array[3]}): wave {result}, {result2} killed, " + $"{num} enemies removed without rewards"));
					WaveMode.JoinHost("the run ended");
					Waves.EndShown(result, array[3]);
					try
					{
						Records.Note(result, result2, array[3], Records.Party());
					}
					catch (Exception arg)
					{
						Plugin.ModLog.LogWarning((object)$"Wave Defense: records: {arg}");
					}
					WaveMode.GuestRunOver();
				}
				break;
			}
			case "leave":
				WaveMode.HostLeft();
				break;
			}
		}

		private static string NameOf(Character c)
		{
			NetworkingManager instance = NetworkingManager.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				NetworkManager networkManager = instance.NetworkManager;
				if (networkManager == null)
				{
					obj = null;
				}
				else
				{
					Root root = ((Observer<Root>)(object)networkManager).Root;
					obj = ((root != null) ? root.AllCharacters : null);
				}
			}
			ObservableList<Character> val = (ObservableList<Character>)obj;
			int num = ((val != null && c != null) ? val.IndexOf(c) : (-1));
			if (num < 0)
			{
				return null;
			}
			string text = c.CharacterInfoGuidString ?? "";
			return $"{num}:{((text.Length > 8) ? text.Substring(0, 8) : text)}";
		}

		private static int FlagRemoved(string ids)
		{
			NetworkingManager instance = NetworkingManager.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				NetworkManager networkManager = instance.NetworkManager;
				if (networkManager == null)
				{
					obj = null;
				}
				else
				{
					Root root = ((Observer<Root>)(object)networkManager).Root;
					obj = ((root != null) ? root.AllCharacters : null);
				}
			}
			ObservableList<Character> val = (ObservableList<Character>)obj;
			if (val == null || string.IsNullOrEmpty(ids))
			{
				return 0;
			}
			int num = 0;
			string[] array = ids.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(':');
				if (array2.Length == 2 && int.TryParse(array2[0], out var result) && result >= 0 && result < val.Count)
				{
					Character val2 = val[result];
					if (val2 != null && (val2.CharacterInfoGuidString ?? "").StartsWith(array2[1]))
					{
						val2.DespawnedNoRewards = true;
						num++;
					}
				}
			}
			return num;
		}
	}
	public static class Waves
	{
		internal const float PointsGrowth = 0.15f;

		internal const int LevelStep = 2;

		internal const float MixChance = 0.5f;

		internal const int MaxAlive = 24;

		internal const int BossEvery = 5;

		internal const int LateBossLevel = 20;

		private static int _shown;

		private const float CloseDelay = 1f;

		private static float _closeAt;

		private static readonly List<Character> Foes = new List<Character>();

		private static CharacterInfo _lastBoss;

		private static List<Records.Hero> _heroes = new List<Records.Hero>();

		private static float _beganAt;

		private static string _endTitle;

		public static bool Armed { get; private set; }

		public static bool Active { get; private set; }

		public static bool Ending { get; private set; }

		public static int Wave { get; private set; }

		public static int Killed { get; private set; }

		public static string LastResult { get; private set; } = "";

		public static string EndReason { get; private set; }

		public static int OnField
		{
			get
			{
				if (!Active)
				{
					return _shown;
				}
				return Wave;
			}
		}

		public static string Arm()
		{
			Armed = true;
			return "armed: the next battle is a wave defense run";
		}

		public static string Begin()
		{
			if (!NetworkingManager.Instance.IsServer)
			{
				return "only the host runs the waves";
			}
			if (!InFight())
			{
				return "no battle on";
			}
			if (!MapBuilder.Ready || MapBuilder.Current == null || MapBuilder.Current.enemies.Length == 0)
			{
				return "the battle isn't on a MapMaker map with enemy spots";
			}
			Armed = false;
			Active = true;
			Ending = false;
			Wave = 1;
			Killed = 0;
			Foes.Clear();
			LastResult = "";
			EndReason = null;
			_heroes = Records.Party();
			_beganAt = Time.unscaledTime;
			_endTitle = null;
			Shrines.Due = true;
			Plugin.ModLog.LogInfo((object)("Wave Defense: run begins on '" + MapBuilder.Current.name + "', wave 1 is the map's fight " + $"({LivingEnemies().Count} enemies)."));
			Titles.Show(Titles.WaveTitle(1, null));
			WaveNet.SendWave(1, null);
			return "wave 1";
		}

		public static string Stop(string why)
		{
			if (!Active && !Armed)
			{
				return "no run";
			}
			if (Active)
			{
				Plugin.ModLog.LogInfo((object)$"Wave Defense: run over ({why}): wave {Wave} reached, {Killed} enemies killed.");
				if (why != "hot reload")
				{
					try
					{
						Records.Note(Wave, Killed, why, _heroes);
					}
					catch (Exception arg)
					{
						Plugin.ModLog.LogWarning((object)$"Wave Defense: records: {arg}");
					}
				}
			}
			Armed = false;
			Active = false;
			Ending = false;
			Shrines.Due = false;
			Foes.Clear();
			return LastResult = $"wave {Wave} reached, {Killed} killed ({why})";
		}

		public static string Status()
		{
			return $"armed={Armed} active={Active} ending={Ending} wave={Wave} killed={Killed} alive={LivingEnemies().Count} last='{LastResult}' " + "shrines=[" + Shrines.Status() + "]";
		}

		public static int LevelOf(int wave)
		{
			return GlobalSettingsManager.instance.globalSettings.MaxLevel + (wave - 1) / 2;
		}

		private static string PartyState()
		{
			IEnumerable<string> values = from c in NetworkingManager.Instance.PartyCharacters
				where c != null
				select $"{c.CharacterName} {Mathf.RoundToInt(c.Health)}/{Mathf.RoundToInt(c.MaxHealth)} HP " + $"{Mathf.RoundToInt(c.Mana)}/{Mathf.RoundToInt(c.MaxMana)} mana";
			Root val = ((Observer<Root>)(object)NetworkingManager.Instance.NetworkManager)?.Root;
			return string.Format("{0} s, turn {1}: {2}", Mathf.RoundToInt(Time.unscaledTime - _beganAt), (val != null) ? val.TurnNumber : 0, string.Join("; ", values));
		}

		public static int PointsOf(int wave)
		{
			GlobalSettings globalSettings = GlobalSettingsManager.instance.globalSettings;
			float num = globalSettings.GeneratedBattlePointTotal;
			return Mathf.RoundToInt(num * (1f + 0.15f * (float)(wave - 1)) + num * (float)GameLogic.instance.CurrentDifficulty.AdditionalBattlePointsPerc / 100f + (float)(Math.Max(0, NetworkingManager.Instance.PartyCharacters.Count - 1) * globalSettings.AdditionalBattlePointsPerPlayer));
		}

		internal static void Tick()
		{
			if (Armed && !Active && InFight() && NetworkingManager.Instance.IsServer && MapBattle.Active)
			{
				Begin();
			}
			if (Shrines.Due && Active && !Ending && InFight() && !((Observer<Root>)(object)NetworkingManager.Instance.NetworkManager).Root.SpawnPlacementActive)
			{
				Shrines.Due = false;
				try
				{
					Shrines.Raise();
				}
				catch (Exception arg)
				{
					Plugin.ModLog.LogWarning((object)$"Wave Defense: shrines: {arg}");
				}
			}
		}

		internal static bool TeamAlive(int team, bool real)
		{
			if (!Active || !InFight())
			{
				return real;
			}
			switch (team)
			{
			case 0:
				if (Ending)
				{
					return true;
				}
				if (!real)
				{
					BeginEnding();
				}
				return true;
			case 1:
				if (Ending)
				{
					return Time.unscaledTime < _closeAt;
				}
				if (real)
				{
					return true;
				}
				CountDead();
				if (SpawnWave(Wave + 1))
				{
					return true;
				}
				Plugin.ModLog.LogWarning((object)$"Wave Defense: wave {Wave + 1} couldn't be spawned; the run ends on wave {Wave}.");
				Ending = true;
				EndReason = $"wave {Wave} cleared, no wave {Wave + 1} could be raised";
				LastResult = $"cleared wave {Wave}";
				_endTitle = Titles.EndTitle(Wave, fell: false);
				_closeAt = 0f;
				WaveNet.SendEnd(Wave, Killed, EndReason, null);
				return false;
			default:
				return real;
			}
		}

		private static void BeginEnding()
		{
			Ending = true;
			EndReason = "the party fell";
			List<Character> list = LivingEnemies();
			CountDead();
			LastResult = $"fell on wave {Wave}";
			_endTitle = Titles.EndTitle(Wave, fell: true);
			Plugin.ModLog.LogInfo((object)($"Wave Defense: the party fell on wave {Wave} ({Killed} killed) after {PartyState()}; " + $"{list.Count} enemies left standing are removed and the battle closes as won."));
			WaveNet.SendEnd(Wave, Killed, EndReason, list);
			_closeAt = Time.unscaledTime + (WaveNet.Multiplayer ? 1f : 0f);
			foreach (Character item in list)
			{
				try
				{
					item.DespawnedNoRewards = true;
					item.Kill();
				}
				catch (Exception ex)
				{
					Plugin.ModLog.LogWarning((object)("Wave Defense: couldn't remove " + item.CharacterName + ": " + ex.Message));
				}
			}
			Banner("Fallen on wave " + Titles.Roman(Wave));
		}

		private static bool SpawnWave(int n)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: 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_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0380: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			GlobalSettings globalSettings = GlobalSettingsManager.instance.globalSettings;
			Game instance = Game.Instance;
			MapFile current = MapBuilder.Current;
			int maxLevel = globalSettings.MaxLevel;
			int num = LevelOf(n);
			int num2 = PointsOf(n);
			TerrainType result;
			TerrainType val = (Enum.TryParse<TerrainType>(current.terrain, out result) ? result : instance.CurrentRoomTerrainType);
			List<EnemyGroup> possibleEnemyGroups = globalSettings.GetPossibleEnemyGroups(val, instance.CurrentRoomTimeOfDay, maxLevel);
			if (possibleEnemyGroups == null || possibleEnemyGroups.Count == 0)
			{
				Plugin.ModLog.LogWarning((object)$"Wave Defense: no enemy factions for {val}");
				return false;
			}
			List<EnemyGroup> list = possibleEnemyGroups.OrderBy((EnemyGroup _) => Random.value).ToList();
			bool flag = n >= 3 && list.Count > 1 && Random.value < 0.5f;
			List<EnemyGroup> list2 = list.Take((!flag) ? 1 : 2).ToList();
			CharacterInfo val2 = ((n % 5 == 0) ? BossFor(list2[0], val, maxLevel, n == 5) : null);
			int num3 = (((Object)(object)val2 != (Object)null) ? Math.Max(0, num2 - Mathf.RoundToInt(val2.EnemyBasePointValue)) : num2);
			List<CharacterInfo> list3 = new List<CharacterInfo>();
			foreach (EnemyGroup item in list2)
			{
				list3.AddRange(GameLogic.GetEnemiesByGroupAndPoints(new List<EnemyGroup> { item }, flag ? ((float)num3 / 2f) : ((float)num3), maxLevel));
			}
			if ((Object)(object)val2 != (Object)null)
			{
				list3.Insert(0, val2);
			}
			else if (n % 5 == 0)
			{
				List<CharacterInfo> enemiesByGroupAndPoints = GameLogic.GetEnemiesByGroupAndPoints(new List<EnemyGroup> { list2[0] }, (float)num2, maxLevel, new List<EnemyType>
				{
					(EnemyType)2,
					(EnemyType)4
				});
				list3.InsertRange(0, enemiesByGroupAndPoints.OrderBy((CharacterInfo _) => Random.value).Take(1 + n / 10));
			}
			int num4 = 24 - LivingEnemies().Count;
			if (list3.Count > num4)
			{
				list3 = list3.Take(Math.Max(0, num4)).ToList();
			}
			if (list3.Count == 0)
			{
				return false;
			}
			List<HexCell> list4 = (from s in current.enemies
				select MapBuilder.Cell(s.q, s.r) into _
				where (Object)(object)_ != (Object)null
				orderby Random.value
				select _).ToList();
			HashSet<HexCell> hashSet = new HashSet<HexCell>();
			List<string> list5 = new List<string>();
			Character val3 = null;
			int num5 = 0;
			foreach (CharacterInfo item2 in list3)
			{
				HexCell val4 = null;
				int num6 = 0;
				while (num6 < list4.Count && (Object)(object)val4 == (Object)null)
				{
					HexCell val5 = list4[num5 % list4.Count];
					if (val5.Player == null && !hashSet.Contains(val5))
					{
						val4 = val5;
					}
					else
					{
						HexCell validEmptyNeighbor = val5.ValidEmptyNeighbor;
						if (validEmptyNeighbor != null && !hashSet.Contains(validEmptyNeighbor))
						{
							val4 = validEmptyNeighbor;
						}
					}
					num6++;
					num5++;
				}
				if ((Object)(object)val4 == (Object)null)
				{
					break;
				}
				hashSet.Add(val4);
				try
				{
					Character val6 = GameLogic.instance.CreateAI(item2, 1, num, item2.enemyType, val4, "", (QuestAIType)0, false, true, (Vector3?)null);
					if (val6 != null)
					{
						Foes.Add(val6);
						if ((Object)(object)item2 == (Object)(object)val2)
						{
							val3 = val6;
						}
						list5.Add(((Object)item2).name + " at " + MapBuilder.Where((object)val4));
					}
				}
				catch (Exception ex)
				{
					Plugin.ModLog.LogWarning((object)("Wave Defense: couldn't spawn " + ((Object)item2).name + ": " + ex.Message));
				}
			}
			if (list5.Count == 0)
			{
				return false;
			}
			Plugin.ModLog.LogInfo((object)$"Wave Defense: wave {n - 1} cleared after {PartyState()}");
			Wave = n;
			string text = ((val3 != null) ? val3.CharacterName : null);
			if (val3 != null)
			{
				_lastBoss = val2;
			}
			Plugin.ModLog.LogInfo((object)(string.Format("Wave Defense: wave {0} (level {1}, {2} points, {3}", n, num, num2, string.Join(" + ", list2)) + ((val3 != null) ? $", boss {text} ({val3.MaxHealth} HP) + {num3} points" : ((n % 5 == 0) ? ", no boss: elites" : "")) + "): " + string.Format("{0} enemies: {1}", list5.Count, string.Join("; ", list5))));
			if ((n - 1) % 5 == 0)
			{
				try
				{
					Shrines.Renew();
				}
				catch (Exception arg)
				{
					Plugin.ModLog.LogWarning((object)$"Wave Defense: shrines: {arg}");
				}
			}
			ShowWave(n, text);
			WaveNet.SendWave(n, text);
			return true;
		}

		internal static CharacterInfo BossFor(EnemyGroup group, TerrainType terrain, int top, bool first)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			GlobalSettings globalSettings = GlobalSettingsManager.instance.globalSettings;
			Func<IEnumerable<CharacterInfo>, List<CharacterInfo>> func = (IEnumerable<CharacterInfo> enumerable) => (enumerable ?? Enumerable.Empty<CharacterInfo>()).Where((CharacterInfo i) => (Object)(object)i != (Object)null && (!first || i.MinLevel < 20)).ToList();
			List<CharacterInfo> list = (Plugin.AnyBoss.Value ? Factions.AllBosses(top) : new List<CharacterInfo>());
			if (list.Count == 0)
			{
				list = func(globalSettings.GetEnemyPool(group, top, new List<EnemyType> { (EnemyType)3 }));
			}
			if (list.Count == 0)
			{
				list = func(globalSettings.GetPossibleBossCharacters(terrain, Game.Instance.CurrentRoomTimeOfDay, top));
			}
			List<CharacterInfo> list2 = list.Where((CharacterInfo i) => (Object)(object)i != (Object)(object)_lastBoss).ToList();
			if (list2.Count == 0)
			{
				list2 = list;
			}
			if (list2.Count != 0)
			{
				return list2[Random.Range(0, list2.Count)];
			}
			return null;
		}

		internal static void ShowWave(int n, string boss)
		{
			if (!Active)
			{
				_shown = n;
			}
			Titles.Show(Titles.WaveTitle(n, boss));
		}

		internal static void ClearShown()
		{
			_shown = 0;
			_endTitle = null;
		}

		internal static void EndShown(int wave, string why)
		{
			_shown = 0;
			LastResult = $"wave {wave} reached ({why})";
			bool flag = why == "the party fell";
			_endTitle = Titles.EndTitle(wave, flag);
			if (flag)
			{
				Banner("Fallen on wave " + Titles.Roman(wave));
			}
		}

		internal static string TakeEndTitle()
		{
			string endTitle = _endTitle;
			_endTitle = null;
			return endTitle;
		}

		private static void CountDead()
		{
			Killed = ((Observer<Root>)(object)NetworkingManager.Instance.NetworkManager).Root.CharactersInBattle.Count((Character c) => c != null && c.TeamIndex == 1 && c.IsDead && !c.DespawnedNoRewards);
		}

		private static List<Character> LivingEnemies()
		{
			NetworkingManager instance = NetworkingManager.Instance;
			Root val = ((instance == null) ? null : ((Observer<Root>)(object)instance.NetworkManager)?.Root);
			if (val == null)
			{
				return new List<Character>();
			}
			return val.CharactersInBattle.Where((Character c) => c != null && c.TeamIndex == 1 && !c.IsDead).ToList();
		}

		internal static bool InFight()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			if ((Object)(object)GameLogic.instance != (Object)null && GameLogic.instance.GameStarted && (Object)(object)GUIManager.instance != (Object)null)
			{
				return (int)GUIManager.instance.CurrentGuiState == 2;
			}
			return false;
		}

		internal static void Banner(string text)
		{
			//IL_0014: 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)
			GUIManager instance = GUIManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.ChangeBattleTopText(text, instance.TopBattleTextActiveBG, ColorUtility.ToHtmlStringRGBA(Color.white));
			}
			catch (Exception ex)
			{
				Plugin.ModLog.LogWarning((object)("Wave Defense: banner failed: " + ex.Message));
			}
		}
	}
}