Decompiled source of ArtanisAchievement v0.0.0

ArtanisAchievement.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using CatAPI;
using CustomAchievements;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[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 ArtanisAchievement
{
	internal static class Achievements
	{
		internal const int PrecisionDrone = 5102;

		internal const int AttackDrone = 5103;

		internal const int Corvette = 5111;

		internal const int Destroyer = 5112;

		internal const int ShieldTarget = 20000;

		internal const int LossTarget = 152;

		internal const int BurstKillTarget = 50;

		internal static Achievement ActivatingShields;

		internal static Achievement WellDone;

		internal static Achievement WellFought;

		internal static Achievement BattleTurned;

		internal static Achievement CouldNotHold;

		internal static Achievement GreatLosses;

		internal static Achievement MustRegroup;

		internal static Achievement EmbraceGlory;

		internal static void Register()
		{
			AchievementMod mod = AchievementRegistry.RegisterMod("https://github.com/cat-yummi-dspmod/DSP-ArtanisAchievement", AchText.En("Artanis Achievement").ZhCN("荣耀贩子"));
			ActivatingShields = Add(mod, "activating-shields", AchText.En("Activating shields!").ZhCN("激活护盾!"), AchText.En("Icarus regenerates 20,000 shield within 10 seconds.").ZhCN("伊卡洛斯10秒内恢复20000点护盾值。"), 20000L);
			WellDone = Add(mod, "well-done", AchText.En("You have done well.").ZhCN("大家做得很好"), AchText.En("Wipe out a Dark Fog space assault without touching the controls.").ZhCN("伊卡洛斯在没有任何操作的前提下,全灭一次太空黑雾的攻击。"), 1L);
			WellFought = Add(mod, "well-fought", AchText.En("Well fought.").ZhCN("漂亮的一仗"), AchText.En("Destroy a Dark Fog space core with no save loaded and no ship lost in the last 5 minutes.").ZhCN("在5分钟内没有读档且没有损失舰船的情况下,消灭一个太空黑雾核心。"), 1L);
			BattleTurned = Add(mod, "battle-turned", AchText.En("The battle has turned against my warriors.").ZhCN("战局对我们的战士太不利了"), AchText.En("Lose 152 Precision Drones within 5 minutes.").ZhCN("5分钟内阵亡152个精准无人机。"), 152L);
			CouldNotHold = Add(mod, "could-not-hold", AchText.En("My warriors could not hold.").ZhCN("我的战士们坚持不住了"), AchText.En("Lose 152 Attack Drones within 5 minutes.").ZhCN("5分钟内阵亡152个攻击无人机。"), 152L);
			GreatLosses = Add(mod, "great-losses", AchText.En("My forces have suffered great loss.").ZhCN("我的部队损失惨重"), AchText.En("Lose 152 Corvettes within 5 minutes.").ZhCN("5分钟内阵亡152个护卫舰。"), 152L);
			MustRegroup = Add(mod, "must-regroup", AchText.En("I must regroup my forces.").ZhCN("我得重新集结部队"), AchText.En("Lose 152 Destroyers within 5 minutes.").ZhCN("5分钟内阵亡152个驱逐舰。"), 152L);
			EmbraceGlory = Add(mod, "embrace-glory", AchText.En("We embrace the glory of battle.").ZhCN("拥抱战斗的荣耀"), AchText.En("Destroy 50 or more Dark Fog units with a single Energy Shell Burst.").ZhCN("用能量盾爆破一次性消灭50个以上黑雾单位。"), 50L);
		}

		private static Achievement Add(AchievementMod mod, string key, AchText name, AchText description, long target = 1L)
		{
			AchIcon val = AchIcon.FromEmbeddedPng(typeof(Achievements).Assembly, "ArtanisAchievement.icons." + key + ".png");
			return mod.Register(key, name, description, target, val, false);
		}

		internal static Achievement ForFighter(int protoId)
		{
			return (Achievement)(protoId switch
			{
				5102 => BattleTurned, 
				5103 => CouldNotHold, 
				5111 => GreatLosses, 
				5112 => MustRegroup, 
				_ => null, 
			});
		}
	}
	internal sealed class AssaultWatch
	{
		internal readonly long StartTick;

		internal bool Failed;

		internal int Killed;

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

		private readonly List<int> gone = new List<int>();

		internal bool WipedOut
		{
			get
			{
				if (!Failed && Killed > 0)
				{
					return units.Count == 0;
				}
				return false;
			}
		}

		internal AssaultWatch(long startTick)
		{
			StartTick = startTick;
		}

		internal void Scan(SpaceSector sector, EnemyDFHiveSystem hive)
		{
			EnemyData[] enemyPool = sector.enemyPool;
			EnemyUnitComponent[] array = hive.units?.buffer;
			if (enemyPool == null)
			{
				return;
			}
			if (array != null)
			{
				int cursor = hive.units.cursor;
				for (int i = 1; i < cursor && i < array.Length; i++)
				{
					if (array[i].id == i)
					{
						int enemyId = array[i].enemyId;
						if (IsAssaulting(enemyPool, enemyId))
						{
							units.Add(enemyId);
						}
					}
				}
			}
			gone.Clear();
			foreach (int unit in units)
			{
				if (!IsAssaulting(enemyPool, unit))
				{
					gone.Add(unit);
				}
			}
			foreach (int item in gone)
			{
				units.Remove(item);
				if (CombatEvents.TakeKilledAssaultUnit(item))
				{
					Killed++;
				}
				else
				{
					Failed = true;
				}
			}
		}

		private static bool IsAssaulting(EnemyData[] pool, int enemyId)
		{
			if (enemyId > 0 && enemyId < pool.Length && pool[enemyId].id == enemyId)
			{
				return ((EnemyData)(ref pool[enemyId])).isAssaultingUnit;
			}
			return false;
		}
	}
	internal static class CombatEvents
	{
		internal struct ShieldGain
		{
			public long Tick;

			public long Energy;
		}

		private static readonly object sync = new object();

		private static readonly Dictionary<int, Queue<long>> fighterLosses = new Dictionary<int, Queue<long>>
		{
			{
				5102,
				new Queue<long>()
			},
			{
				5103,
				new Queue<long>()
			},
			{
				5111,
				new Queue<long>()
			},
			{
				5112,
				new Queue<long>()
			}
		};

		private static readonly Queue<ShieldGain> shieldGains = new Queue<ShieldGain>();

		private static readonly List<int> coreKillStars = new List<int>();

		private static readonly HashSet<int> killedAssaultUnits = new HashSet<int>();

		private static long lastMechaShipLossTick = long.MinValue;

		private const long BurstWindow = 75L;

		private static long burstStartTick = long.MinValue;

		private static int burstKills;

		internal static int BurstKills
		{
			get
			{
				lock (sync)
				{
					return burstKills;
				}
			}
		}

		internal static long LastMechaShipLossTick
		{
			get
			{
				lock (sync)
				{
					return lastMechaShipLossTick;
				}
			}
		}

		internal static void Reset()
		{
			lock (sync)
			{
				foreach (Queue<long> value in fighterLosses.Values)
				{
					value.Clear();
				}
				shieldGains.Clear();
				coreKillStars.Clear();
				killedAssaultUnits.Clear();
				lastMechaShipLossTick = long.MinValue;
				burstStartTick = long.MinValue;
				burstKills = 0;
			}
		}

		internal static void OnShieldBurst()
		{
			long gameTick = GameMain.gameTick;
			lock (sync)
			{
				burstStartTick = gameTick;
				burstKills = 0;
			}
		}

		internal static void OnMechaKill()
		{
			long gameTick = GameMain.gameTick;
			lock (sync)
			{
				if (burstStartTick != long.MinValue && gameTick >= burstStartTick && gameTick <= burstStartTick + 75)
				{
					burstKills++;
				}
			}
		}

		internal static void OnFighterLost(int protoId, bool mechaSpaceShip)
		{
			long gameTick = GameMain.gameTick;
			lock (sync)
			{
				if (fighterLosses.TryGetValue(protoId, out var value))
				{
					value.Enqueue(gameTick);
				}
				if (mechaSpaceShip)
				{
					lastMechaShipLossTick = gameTick;
				}
			}
		}

		internal static void OnShieldGain(long energy)
		{
			long gameTick = GameMain.gameTick;
			lock (sync)
			{
				shieldGains.Enqueue(new ShieldGain
				{
					Tick = gameTick,
					Energy = energy
				});
			}
		}

		internal static void OnCoreKilled(int starId)
		{
			lock (sync)
			{
				coreKillStars.Add(starId);
			}
		}

		internal static void OnAssaultUnitKilled(int enemyId)
		{
			lock (sync)
			{
				killedAssaultUnits.Add(enemyId);
			}
		}

		internal static int FighterLossesSince(int protoId, long fromTick)
		{
			lock (sync)
			{
				Queue<long> queue = fighterLosses[protoId];
				while (queue.Count > 0 && queue.Peek() < fromTick)
				{
					queue.Dequeue();
				}
				return queue.Count;
			}
		}

		internal static long ShieldEnergySince(long fromTick)
		{
			lock (sync)
			{
				while (shieldGains.Count > 0 && shieldGains.Peek().Tick < fromTick)
				{
					shieldGains.Dequeue();
				}
				long num = 0L;
				foreach (ShieldGain shieldGain in shieldGains)
				{
					num += shieldGain.Energy;
				}
				return num;
			}
		}

		internal static List<int> TakeCoreKills()
		{
			lock (sync)
			{
				List<int> result = new List<int>(coreKillStars);
				coreKillStars.Clear();
				return result;
			}
		}

		internal static bool TakeKilledAssaultUnit(int enemyId)
		{
			lock (sync)
			{
				return killedAssaultUnits.Remove(enemyId);
			}
		}

		internal static void ClearKilledAssaultUnits()
		{
			lock (sync)
			{
				killedAssaultUnits.Clear();
			}
		}
	}
	[HarmonyPatch]
	internal static class CombatHooks
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(SpaceSector), "KillCraftFinal")]
		[Guard("8d331b6d165290d0")]
		public static void SpaceSector_KillCraftFinal_Prefix(SpaceSector __instance, int craftId)
		{
			RecordFighterLoss(__instance.craftPool, craftId);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(PlanetFactory), "KillCraftFinally")]
		[Guard("4438112e7d007854")]
		public static void PlanetFactory_KillCraftFinally_Prefix(PlanetFactory __instance, int craftId)
		{
			RecordFighterLoss(__instance.craftPool, craftId);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(SpaceSector), "KillEnemyFinal")]
		[Guard("cb31108bc5305076")]
		public static void SpaceSector_KillEnemyFinal_Prefix(SpaceSector __instance, int enemyId, ref CombatStat combatStat)
		{
			RecordMechaKill(__instance.enemyPool, enemyId, ref combatStat);
			EnemyData[] enemyPool = __instance.enemyPool;
			if (enemyId <= 0 || enemyPool == null || enemyId >= enemyPool.Length)
			{
				return;
			}
			ref EnemyData reference = ref enemyPool[enemyId];
			if (reference.id == enemyId)
			{
				if (((EnemyData)(ref reference)).isAssaultingUnit)
				{
					CombatEvents.OnAssaultUnitKilled(enemyId);
				}
				if (reference.dfSCoreId > 0)
				{
					CombatEvents.OnCoreKilled((__instance.GetHiveByAstroId(reference.originAstroId)?.starData?.id).GetValueOrDefault());
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(PlanetFactory), "KillEnemyFinally")]
		[Guard("e83f0731e4b18ba0")]
		public static void PlanetFactory_KillEnemyFinally_Prefix(PlanetFactory __instance, int enemyId, ref CombatStat combatStat)
		{
			RecordMechaKill(__instance.enemyPool, enemyId, ref combatStat);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(PlayerAction_Combat), "ShieldBurst")]
		[Guard("9442e06fa083a721")]
		public static void PlayerAction_Combat_ShieldBurst_Postfix(PlayerAction_Combat __instance)
		{
			if (((PlayerAction)__instance).player != null && ((PlayerAction)__instance).player == GameMain.mainPlayer)
			{
				CombatEvents.OnShieldBurst();
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Mecha), "UpdateCombatStats")]
		[Guard("e0c3d2d8aa654f22")]
		public static void Mecha_UpdateCombatStats_Prefix(Mecha __instance, out long __state)
		{
			__state = __instance.energyShieldEnergy;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Mecha), "UpdateCombatStats")]
		[Guard("e0c3d2d8aa654f22")]
		public static void Mecha_UpdateCombatStats_Postfix(Mecha __instance, long __state)
		{
			long num = __instance.energyShieldEnergy - __state;
			if (num > 0 && __instance.player != null && __instance.player == GameMain.mainPlayer)
			{
				CombatEvents.OnShieldGain(num);
			}
		}

		private static void RecordMechaKill(EnemyData[] pool, int enemyId, ref CombatStat combatStat)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Invalid comparison between Unknown and I4
			if (enemyId > 0 && pool != null && enemyId < pool.Length && pool[enemyId].id == enemyId && (int)combatStat.lastCaster.type == 15 && combatStat.lastCaster.id == 1)
			{
				CombatEvents.OnMechaKill();
			}
		}

		private static void RecordFighterLoss(CraftData[] pool, int craftId)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Invalid comparison between Unknown and I4
			if (craftId <= 0 || pool == null || craftId >= pool.Length)
			{
				return;
			}
			ref CraftData reference = ref pool[craftId];
			if (reference.id != craftId || reference.unitId <= 0 || (int)reference.prototype != 5)
			{
				return;
			}
			int owner = reference.owner;
			if (owner > 0 && owner < pool.Length)
			{
				int owner2 = pool[owner].owner;
				if (owner2 != 0)
				{
					CombatEvents.OnFighterLost(reference.protoId, owner2 < 0 && reference.isSpace);
				}
			}
		}
	}
	internal static class PatchCheck
	{
		private struct Hook
		{
			public MethodBase Target;

			public string Name;

			public string Affects;
		}

		internal static void Report(Harmony harmony)
		{
			List<Hook> list = new List<Hook>
			{
				new Hook
				{
					Target = AccessTools.Method(typeof(SpaceSector), "KillCraftFinal", (Type[])null, (Type[])null),
					Name = "SpaceSector.KillCraftFinal",
					Affects = "My forces have suffered great loss, I must regroup my forces, Well fought"
				},
				new Hook
				{
					Target = AccessTools.Method(typeof(PlanetFactory), "KillCraftFinally", (Type[])null, (Type[])null),
					Name = "PlanetFactory.KillCraftFinally",
					Affects = "The battle has turned against my warriors, My warriors could not hold"
				},
				new Hook
				{
					Target = AccessTools.Method(typeof(SpaceSector), "KillEnemyFinal", (Type[])null, (Type[])null),
					Name = "SpaceSector.KillEnemyFinal",
					Affects = "You have done well, Well fought, We embrace the glory of battle"
				},
				new Hook
				{
					Target = AccessTools.Method(typeof(PlanetFactory), "KillEnemyFinally", (Type[])null, (Type[])null),
					Name = "PlanetFactory.KillEnemyFinally",
					Affects = "We embrace the glory of battle"
				},
				new Hook
				{
					Target = AccessTools.Method(typeof(PlayerAction_Combat), "ShieldBurst", (Type[])null, (Type[])null),
					Name = "PlayerAction_Combat.ShieldBurst",
					Affects = "We embrace the glory of battle"
				},
				new Hook
				{
					Target = AccessTools.Method(typeof(Mecha), "UpdateCombatStats", (Type[])null, (Type[])null),
					Name = "Mecha.UpdateCombatStats",
					Affects = "Activating shields!"
				}
			};
			int num = 0;
			foreach (Hook item in list)
			{
				Patches val = ((item.Target == null) ? null : Harmony.GetPatchInfo(item.Target));
				if (val != null && val.Owners.Contains(harmony.Id))
				{
					num++;
					Plugin.Log.LogInfo((object)("  hooked " + item.Name));
				}
				else
				{
					Plugin.Log.LogWarning((object)("  NOT hooked " + item.Name + " -> these achievements will not progress: " + item.Affects));
				}
			}
			Plugin.Log.LogInfo((object)$"Combat hooks: {num}/{list.Count} applied");
		}
	}
	[BepInPlugin("com.yummi.dsp.artanisachievement", "Artanis Achievement", "0.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.yummi.dsp.artanisachievement";

		public const string PluginName = "Artanis Achievement";

		public const string PluginVersion = "0.0.0";

		public const string GitHubUrl = "https://github.com/cat-yummi-dspmod/DSP-ArtanisAchievement";

		private static readonly Harmony harmony = new Harmony("com.yummi.dsp.artanisachievement");

		internal static ManualLogSource Log;

		private bool trackerFailed;

		private void Awake()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Achievements.Register();
			Harness.PatchAll(harmony, typeof(CombatHooks), new PatchOptions
			{
				ModName = "Artanis Achievement"
			});
			PatchCheck.Report(harmony);
			Log.LogInfo((object)"Artanis Achievement v0.0.0 loaded");
		}

		private void Update()
		{
			if (trackerFailed)
			{
				return;
			}
			try
			{
				Tracker.Update();
			}
			catch (Exception ex)
			{
				trackerFailed = true;
				Log.LogError((object)("Achievement tracking stopped after an error: " + ex));
				Harness.Report((NoticeLevel)2, "Artanis Achievement", "成就判定出错,已停止,详情见日志", "Achievement tracking failed and stopped; see the log", (string)null, (string)null);
			}
		}

		private void OnDestroy()
		{
			harmony.UnpatchSelf();
		}
	}
	internal static class Tracker
	{
		private const long ShieldWindow = 600L;

		private const long LossWindow = 18000L;

		private const long ScanInterval = 10L;

		private const int FreshWaveTicks = 300;

		private static readonly int[] fighterTypes = new int[4] { 5102, 5103, 5111, 5112 };

		private static readonly Dictionary<EnemyDFHiveSystem, AssaultWatch> watches = new Dictionary<EnemyDFHiveSystem, AssaultWatch>();

		private static readonly HashSet<EnemyDFHiveSystem> joinedLate = new HashSet<EnemyDFHiveSystem>();

		private static readonly HashSet<EnemyDFHiveSystem> seen = new HashSet<EnemyDFHiveSystem>();

		private static readonly List<EnemyDFHiveSystem> leftBehind = new List<EnemyDFHiveSystem>();

		private static GameData trackedGame;

		private static long trackingStartTick;

		private static long nextScanTick;

		private static long lastInputTick;

		private static long lastTick;

		internal static void Update()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			GameData data = GameMain.data;
			if (!GameMain.isRunning || DSPGame.IsMenuDemo || data == null || GameMain.mainPlayer == null)
			{
				trackedGame = null;
				return;
			}
			long gameTick = GameMain.gameTick;
			if (data != trackedGame || gameTick < lastTick)
			{
				StartTracking(data);
			}
			lastTick = gameTick;
			if (Input.anyKey || Input.mouseScrollDelta != Vector2.zero)
			{
				lastInputTick = gameTick;
			}
			if (gameTick >= nextScanTick)
			{
				nextScanTick = gameTick + 10;
				CheckShield(gameTick);
				CheckLosses(gameTick);
				Raise(Achievements.EmbraceGlory, CombatEvents.BurstKills);
				CheckCoreKills(gameTick);
				CheckAssaults(gameTick, data);
			}
		}

		private static void StartTracking(GameData data)
		{
			trackedGame = data;
			trackingStartTick = GameMain.gameTick;
			nextScanTick = 0L;
			lastInputTick = long.MinValue;
			watches.Clear();
			joinedLate.Clear();
			CombatEvents.Reset();
			bool isSandboxMode = data.gameDesc.isSandboxMode;
			Plugin.Log.LogInfo((object)(isSandboxMode ? "Game started in sandbox mode; achievements will not progress" : $"Tracking achievements for this game (combat mode: {data.gameDesc.isCombatMode})"));
		}

		private static void Raise(Achievement achievement, long value)
		{
			if (achievement != null && achievement.CanProgress && value > achievement.Progress)
			{
				achievement.SetProgress(value);
			}
		}

		private static void CheckShield(long now)
		{
			long num = CombatEvents.ShieldEnergySince(now - 600);
			long energyShieldEnergyRate = GameMain.mainPlayer.mecha.energyShieldEnergyRate;
			if (energyShieldEnergyRate > 0)
			{
				Raise(Achievements.ActivatingShields, num / (energyShieldEnergyRate * 100));
			}
		}

		private static void CheckLosses(long now)
		{
			int[] array = fighterTypes;
			foreach (int protoId in array)
			{
				Raise(Achievements.ForFighter(protoId), CombatEvents.FighterLossesSince(protoId, now - 18000));
			}
		}

		private static void CheckCoreKills(long now)
		{
			List<int> list = CombatEvents.TakeCoreKills();
			if (list.Count != 0 && !Achievements.WellFought.Unlocked)
			{
				StarData localStar = GameMain.localStar;
				bool flag = now - trackingStartTick >= 18000 && CombatEvents.LastMechaShipLossTick < now - 18000;
				if (localStar != null && flag && HasSpaceFleetOut(GameMain.mainPlayer.mecha) && list.Contains(localStar.id))
				{
					Achievements.WellFought.Unlock();
				}
			}
		}

		private static bool HasSpaceFleetOut(Mecha mecha)
		{
			CombatModuleComponent spaceCombatModule = mecha.spaceCombatModule;
			if (spaceCombatModule == null || !spaceCombatModule.moduleEnabled || spaceCombatModule.moduleFleets == null)
			{
				return false;
			}
			for (int i = 0; i < spaceCombatModule.fleetCount && i < spaceCombatModule.moduleFleets.Length; i++)
			{
				if (spaceCombatModule.moduleFleets[i].fleetId > 0)
				{
					return true;
				}
			}
			return false;
		}

		private static void CheckAssaults(long now, GameData data)
		{
			SpaceSector spaceSector = data.spaceSector;
			StarData localStar = GameMain.localStar;
			Player mainPlayer = GameMain.mainPlayer;
			seen.Clear();
			if (spaceSector?.dfHives != null && localStar != null && localStar.index >= 0 && localStar.index < spaceSector.dfHives.Length)
			{
				for (EnemyDFHiveSystem val = spaceSector.dfHives[localStar.index]; val != null; val = val.nextSibling)
				{
					seen.Add(val);
					watches.TryGetValue(val, out var value);
					if (val.evolve.waveTicks > 0)
					{
						if (value == null && !joinedLate.Contains(val))
						{
							if (val.evolve.waveTicks <= 300)
							{
								value = new AssaultWatch(now);
								watches[val] = value;
							}
							else
							{
								joinedLate.Add(val);
							}
						}
						if (value != null)
						{
							Judge(value, mainPlayer);
							value.Scan(spaceSector, val);
						}
					}
					else
					{
						joinedLate.Remove(val);
						if (value != null)
						{
							Finish(val, value, spaceSector, mainPlayer);
						}
					}
				}
			}
			leftBehind.Clear();
			foreach (EnemyDFHiveSystem key in watches.Keys)
			{
				if (!seen.Contains(key))
				{
					leftBehind.Add(key);
				}
			}
			foreach (EnemyDFHiveSystem item in leftBehind)
			{
				AssaultWatch assaultWatch = watches[item];
				if (item.starData != localStar)
				{
					assaultWatch.Failed = true;
				}
				Finish(item, assaultWatch, spaceSector, mainPlayer);
			}
			joinedLate.RemoveWhere((EnemyDFHiveSystem h) => !seen.Contains(h));
			if (watches.Count == 0)
			{
				CombatEvents.ClearKilledAssaultUnits();
			}
		}

		private static void Judge(AssaultWatch watch, Player player)
		{
			if (lastInputTick >= watch.StartTick || !player.isAlive)
			{
				watch.Failed = true;
			}
		}

		private static void Finish(EnemyDFHiveSystem hive, AssaultWatch watch, SpaceSector sector, Player player)
		{
			watches.Remove(hive);
			Judge(watch, player);
			if (!watch.Failed && sector != null)
			{
				watch.Scan(sector, hive);
			}
			if (watch.WipedOut)
			{
				Achievements.WellDone.Unlock();
			}
		}
	}
}