Decompiled source of Forsaken Ascension v0.0.38

BepInEx/plugins/ForsakenAscension/ForsakenAscension.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Xml;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ForsakenAscension.Core;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using SoftReferenceableAssets;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ForsakenAscension.Core
{
	internal static class AtomicFile
	{
		internal static void Write(string path, byte[] data)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
			string text = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
			try
			{
				using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					fileStream.Write(data, 0, data.Length);
					fileStream.Flush(flushToDisk: true);
				}
				if (File.Exists(path))
				{
					File.Replace(text, path, path + ".bak");
				}
				else
				{
					File.Move(text, path);
				}
			}
			finally
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
			}
		}
	}
	public enum CombatObservationKind : byte
	{
		BossDamage,
		DefenseHealthLoss,
		DefenseBlocked,
		Healing
	}
	public sealed class BossDamagePacket
	{
		public const int Size = 73;

		public CombatObservationKind Kind;

		public long World;

		public long Session;

		public long Sequence;

		public long BossCreator;

		public long AttackerCreator;

		public uint BossId;

		public uint AttackerId;

		public long RecipientCreator;

		public uint RecipientId;

		public float Before;

		public float After;

		private void Validate()
		{
			if (!Enum.IsDefined(typeof(CombatObservationKind), Kind))
			{
				throw new ArgumentException("Invalid combat observation kind");
			}
			if (Session == 0 || Sequence <= 0 || BossCreator == 0 || BossId == 0 || AttackerCreator == 0 || AttackerId == 0 || float.IsNaN(Before) || float.IsInfinity(Before) || float.IsNaN(After) || float.IsInfinity(After))
			{
				throw new ArgumentException("Invalid combat observation");
			}
			if (Kind == CombatObservationKind.Healing)
			{
				if (Before <= 0f || After <= Before || RecipientCreator == 0 || RecipientId == 0 || (RecipientCreator == AttackerCreator && RecipientId == AttackerId))
				{
					throw new ArgumentException("Invalid healing observation");
				}
			}
			else if (Before <= 0f || After < 0f || After >= Before || RecipientCreator != 0 || RecipientId != 0)
			{
				throw new ArgumentException("Invalid boss damage observation");
			}
		}

		public byte[] Encode()
		{
			Validate();
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(4);
			binaryWriter.Write(World);
			binaryWriter.Write(Session);
			binaryWriter.Write(Sequence);
			binaryWriter.Write(BossCreator);
			binaryWriter.Write(BossId);
			binaryWriter.Write(AttackerCreator);
			binaryWriter.Write(AttackerId);
			binaryWriter.Write(Before);
			binaryWriter.Write(After);
			binaryWriter.Write((byte)Kind);
			binaryWriter.Write(RecipientCreator);
			binaryWriter.Write(RecipientId);
			return memoryStream.ToArray();
		}

		public static BossDamagePacket Decode(byte[] bytes, long world)
		{
			if (bytes == null || bytes.Length != 73)
			{
				throw new InvalidDataException("Invalid damage packet size");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			if (binaryReader.ReadInt32() != 4)
			{
				throw new InvalidDataException("Unknown damage protocol");
			}
			BossDamagePacket bossDamagePacket = new BossDamagePacket();
			bossDamagePacket.World = binaryReader.ReadInt64();
			bossDamagePacket.Session = binaryReader.ReadInt64();
			bossDamagePacket.Sequence = binaryReader.ReadInt64();
			bossDamagePacket.BossCreator = binaryReader.ReadInt64();
			bossDamagePacket.BossId = binaryReader.ReadUInt32();
			bossDamagePacket.AttackerCreator = binaryReader.ReadInt64();
			bossDamagePacket.AttackerId = binaryReader.ReadUInt32();
			bossDamagePacket.Before = binaryReader.ReadSingle();
			bossDamagePacket.After = binaryReader.ReadSingle();
			BossDamagePacket bossDamagePacket2 = bossDamagePacket;
			bossDamagePacket2.Kind = (CombatObservationKind)binaryReader.ReadByte();
			bossDamagePacket2.RecipientCreator = binaryReader.ReadInt64();
			bossDamagePacket2.RecipientId = binaryReader.ReadUInt32();
			if (bossDamagePacket2.World != world)
			{
				throw new InvalidDataException("Wrong observation world");
			}
			bossDamagePacket2.Validate();
			return bossDamagePacket2;
		}
	}
	public sealed class BossDamageAck
	{
		public long World;

		public long Session;

		public long Sequence;

		public bool Accepted;

		public byte[] Encode()
		{
			if (Session == 0 || Sequence <= 0)
			{
				throw new ArgumentException("Invalid damage acknowledgement");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1);
			binaryWriter.Write(World);
			binaryWriter.Write(Session);
			binaryWriter.Write(Sequence);
			binaryWriter.Write(Accepted);
			return memoryStream.ToArray();
		}

		public static BossDamageAck Decode(byte[] bytes, long world, long session)
		{
			if (bytes == null || bytes.Length != 29)
			{
				throw new InvalidDataException("Invalid damage acknowledgement size");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown acknowledgement protocol");
			}
			BossDamageAck bossDamageAck = new BossDamageAck();
			bossDamageAck.World = binaryReader.ReadInt64();
			bossDamageAck.Session = binaryReader.ReadInt64();
			bossDamageAck.Sequence = binaryReader.ReadInt64();
			BossDamageAck bossDamageAck2 = bossDamageAck;
			byte b = binaryReader.ReadByte();
			if (bossDamageAck2.World != world || bossDamageAck2.Session != session || bossDamageAck2.Session == 0 || bossDamageAck2.Sequence <= 0 || b > 1)
			{
				throw new InvalidDataException("Invalid damage acknowledgement identity");
			}
			bossDamageAck2.Accepted = b == 1;
			return bossDamageAck2;
		}
	}
	public enum BossMove : byte
	{
		Rootbreaker = 1,
		SplinterFan,
		BrambleMaze,
		HeartwoodWard,
		ForestGuard,
		DoubleBackhand,
		RotTrail,
		BoneVolley,
		CarrionCall,
		PutridDetonation,
		ClawCombo,
		RakingBreath,
		FrozenCrown,
		MountainGust,
		ShatteringWinter,
		CinderRing,
		SweepingDoom,
		MeteorMarch,
		ScorchedBrand,
		EmberRitual,
		RendingFollowup,
		AcidFan,
		PredatorsRush,
		BurrowAmbush,
		RoyalGuard,
		ScorchingCleave,
		ForkedFissures,
		EmberBreath,
		CinderGate,
		AshenJudgment
	}
	public enum HazardShape : byte
	{
		Projectile = 1,
		Cone,
		Pool,
		Root,
		Ward,
		Crystal,
		Stone,
		ExpandingRing,
		Meteor,
		Brand,
		Ritual,
		Wall,
		ContractingRing,
		Trail,
		Combo,
		Rush,
		Burrow,
		Summon,
		SweepingCone,
		Strip
	}
	public static class BossMoves
	{
		public static Boss BossOf(BossMove move)
		{
			Check(move);
			return (Boss)(1 + (int)(move - 1) / 5);
		}

		public static int Star(BossMove move)
		{
			Check(move);
			return 1 + (int)(move - 1) % 5;
		}

		public static bool Allows(Boss boss, int tier, int version, BossMove move)
		{
			if (version == 4 && tier >= 1 && tier <= 5 && Enum.IsDefined(typeof(BossMove), move) && BossOf(move) == boss)
			{
				return Star(move) <= tier;
			}
			return false;
		}

		private static void Check(BossMove move)
		{
			if (!Enum.IsDefined(typeof(BossMove), move))
			{
				throw new ArgumentException("Unknown boss move");
			}
		}

		public static float Warning(BossMove move)
		{
			Check(move);
			return move switch
			{
				BossMove.ScorchedBrand => 3f, 
				BossMove.AshenJudgment => 3f, 
				BossMove.EmberRitual => 6f, 
				_ => 1.6f, 
			};
		}

		public static float Cooldown(BossMove move)
		{
			Check(move);
			if (Star(move) != 5)
			{
				if (Star(move) != 4)
				{
					if (Star(move) != 3)
					{
						return 18f;
					}
					return 24f;
				}
				return 30f;
			}
			return 40f;
		}

		public static float BaseDamage(BossMove move)
		{
			return BossOf(move) switch
			{
				Boss.Elder => 18f, 
				Boss.Bonemass => 25f, 
				Boss.Moder => 30f, 
				Boss.Yagluth => 35f, 
				Boss.Queen => 40f, 
				_ => 45f, 
			};
		}

		public static bool IsProp(HazardShape shape)
		{
			if (shape != HazardShape.Root && shape != HazardShape.Ward && shape != HazardShape.Crystal)
			{
				return shape == HazardShape.Stone;
			}
			return true;
		}

		public static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		public static PulsePosition Lerp(PulsePosition a, PulsePosition b, float t)
		{
			return new PulsePosition
			{
				X = a.X + (b.X - a.X) * t,
				Y = a.Y + (b.Y - a.Y) * t,
				Z = a.Z + (b.Z - a.Z) * t
			};
		}

		public static bool Circle(PulsePosition point, PulsePosition center, float radius)
		{
			if (ValidPoint(point) && ValidPoint(center) && Math.Abs(point.Y - center.Y) <= 3f)
			{
				return Square(point.X - center.X) + Square(point.Z - center.Z) <= radius * radius;
			}
			return false;
		}

		public static bool Cone(PulsePosition point, PulsePosition origin, PulsePosition end, float radius, float halfAngle)
		{
			if (!Circle(point, origin, radius))
			{
				return false;
			}
			float num = point.X - origin.X;
			float num2 = point.Z - origin.Z;
			float num3 = end.X - origin.X;
			float num4 = end.Z - origin.Z;
			double num5 = Math.Sqrt(num * num + num2 * num2);
			double num6 = Math.Sqrt(num3 * num3 + num4 * num4);
			if (num6 > 0.01)
			{
				if (!(num5 < 0.01))
				{
					return (double)(num * num3 + num2 * num4) / (num5 * num6) >= Math.Cos((double)halfAngle * Math.PI / 180.0);
				}
				return true;
			}
			return false;
		}

		public static bool Ring(PulsePosition point, PulsePosition center, float radius, float width)
		{
			if (!ValidPoint(point) || !ValidPoint(center) || Math.Abs(point.Y - center.Y) > 3f)
			{
				return false;
			}
			double num = Math.Sqrt(Square(point.X - center.X) + Square(point.Z - center.Z));
			return Math.Abs(num - (double)radius) <= (double)width;
		}

		public static PulsePosition Rotate(PulsePosition origin, PulsePosition end, float degrees)
		{
			double num = (double)degrees * Math.PI / 180.0;
			float num2 = end.X - origin.X;
			float num3 = end.Z - origin.Z;
			return new PulsePosition
			{
				X = origin.X + num2 * (float)Math.Cos(num) + num3 * (float)Math.Sin(num),
				Y = end.Y,
				Z = origin.Z - num2 * (float)Math.Sin(num) + num3 * (float)Math.Cos(num)
			};
		}

		public static bool Wall(PulsePosition point, PulsePosition a, PulsePosition b, float t, float halfLength, float gap, float width)
		{
			if (!ValidPoint(point) || !ValidPoint(a) || !ValidPoint(b))
			{
				return false;
			}
			PulsePosition pulsePosition = Lerp(a, b, t);
			float num = b.X - a.X;
			float num2 = b.Z - a.Z;
			double num3 = Math.Sqrt(num * num + num2 * num2);
			if (num3 < 0.01 || Math.Abs(point.Y - pulsePosition.Y) > 3f)
			{
				return false;
			}
			double value = (double)((point.X - pulsePosition.X) * num + (point.Z - pulsePosition.Z) * num2) / num3;
			double value2 = (double)((0f - (point.X - pulsePosition.X)) * num2 + (point.Z - pulsePosition.Z) * num) / num3;
			if (Math.Abs(value) <= (double)width && Math.Abs(value2) >= (double)gap)
			{
				return Math.Abs(value2) <= (double)halfLength;
			}
			return false;
		}

		private static float Square(float value)
		{
			return value * value;
		}

		internal static bool ValidPoint(PulsePosition p)
		{
			if (Finite(p.X) && Finite(p.Y) && Finite(p.Z) && Math.Abs(p.X) < 100000f && Math.Abs(p.Y) < 10000f)
			{
				return Math.Abs(p.Z) < 100000f;
			}
			return false;
		}
	}
	public sealed class BossHazard
	{
		public int Id;

		public int Group;

		public BossMove Move;

		public HazardShape Shape;

		public long Due;

		public long End;

		public long Owner;

		public long TargetCreator;

		public uint TargetId;

		public PulsePosition A;

		public PulsePosition B;

		public float Radius;

		public float Width;

		public float Health;

		public float Parameter;

		public void Validate()
		{
			if (Id <= 0 || Group <= 0 || !Enum.IsDefined(typeof(BossMove), Move) || !Enum.IsDefined(typeof(HazardShape), Shape) || Due <= 0 || End < Due || (double)(End - Due) / 10000000.0 > 40.0 || Owner == 0 || !BossMoves.ValidPoint(A) || !BossMoves.ValidPoint(B) || !BossMoves.Finite(Radius) || Radius < 0f || Radius > 45f || !BossMoves.Finite(Width) || Width < 0f || Width > 20f || !BossMoves.Finite(Health) || Health < 0f || Health > 100000f || !BossMoves.Finite(Parameter) || Math.Abs(Parameter) > 360f || TargetCreator == 0 != (TargetId == 0))
			{
				throw new InvalidDataException("Invalid boss hazard");
			}
		}

		public float Progress(long now)
		{
			if (End != Due)
			{
				return Math.Max(0f, Math.Min(1f, (float)((double)(now - Due) / (double)(End - Due))));
			}
			return 1f;
		}
	}
	public static class BossHazardPacket
	{
		public const int MaximumCount = 32;

		public const int MaximumBytes = 4096;

		public static byte[] Encode(IList<BossHazard> hazards)
		{
			if (hazards == null || hazards.Count > 32)
			{
				throw new InvalidDataException("Too many boss hazards");
			}
			HashSet<int> hashSet = new HashSet<int>();
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1);
			binaryWriter.Write((byte)hazards.Count);
			foreach (BossHazard hazard in hazards)
			{
				if (hazard == null)
				{
					throw new InvalidDataException("Null boss hazard");
				}
				hazard.Validate();
				if (!hashSet.Add(hazard.Id))
				{
					throw new InvalidDataException("Duplicate boss hazard");
				}
				binaryWriter.Write(hazard.Id);
				binaryWriter.Write(hazard.Group);
				binaryWriter.Write((byte)hazard.Move);
				binaryWriter.Write((byte)hazard.Shape);
				binaryWriter.Write(hazard.Due);
				binaryWriter.Write(hazard.End);
				binaryWriter.Write(hazard.Owner);
				binaryWriter.Write(hazard.TargetCreator);
				binaryWriter.Write(hazard.TargetId);
				Write(binaryWriter, hazard.A);
				Write(binaryWriter, hazard.B);
				binaryWriter.Write(hazard.Radius);
				binaryWriter.Write(hazard.Width);
				binaryWriter.Write(hazard.Health);
				binaryWriter.Write(hazard.Parameter);
			}
			return memoryStream.ToArray();
		}

		public static List<BossHazard> Decode(byte[] bytes, Boss boss, int tier)
		{
			if (bytes == null || bytes.Length < 5 || bytes.Length > 4096)
			{
				throw new InvalidDataException("Invalid boss hazard size");
			}
			using MemoryStream input = new MemoryStream(bytes);
			using BinaryReader binaryReader = new BinaryReader(input);
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown boss hazard protocol");
			}
			int num = binaryReader.ReadByte();
			if (num > 32 || bytes.Length != 5 + num * 86)
			{
				throw new InvalidDataException("Invalid boss hazard count");
			}
			List<BossHazard> list = new List<BossHazard>();
			HashSet<int> hashSet = new HashSet<int>();
			for (int i = 0; i < num; i++)
			{
				BossHazard bossHazard = new BossHazard();
				bossHazard.Id = binaryReader.ReadInt32();
				bossHazard.Group = binaryReader.ReadInt32();
				bossHazard.Move = (BossMove)binaryReader.ReadByte();
				bossHazard.Shape = (HazardShape)binaryReader.ReadByte();
				bossHazard.Due = binaryReader.ReadInt64();
				bossHazard.End = binaryReader.ReadInt64();
				bossHazard.Owner = binaryReader.ReadInt64();
				bossHazard.TargetCreator = binaryReader.ReadInt64();
				bossHazard.TargetId = binaryReader.ReadUInt32();
				bossHazard.A = Read(binaryReader);
				bossHazard.B = Read(binaryReader);
				bossHazard.Radius = binaryReader.ReadSingle();
				bossHazard.Width = binaryReader.ReadSingle();
				bossHazard.Health = binaryReader.ReadSingle();
				bossHazard.Parameter = binaryReader.ReadSingle();
				BossHazard bossHazard2 = bossHazard;
				bossHazard2.Validate();
				if (!hashSet.Add(bossHazard2.Id) || !BossMoves.Allows(boss, tier, 4, bossHazard2.Move))
				{
					throw new InvalidDataException("Unexpected boss hazard");
				}
				list.Add(bossHazard2);
			}
			return list;
		}

		private static void Write(BinaryWriter writer, PulsePosition p)
		{
			writer.Write(p.X);
			writer.Write(p.Y);
			writer.Write(p.Z);
		}

		private static PulsePosition Read(BinaryReader reader)
		{
			return new PulsePosition
			{
				X = reader.ReadSingle(),
				Y = reader.ReadSingle(),
				Z = reader.ReadSingle()
			};
		}
	}
	public sealed class ChallengeDamage
	{
		private readonly double[][] values;

		public ChallengeDamage(double[][] source)
		{
			if (source == null || source.Length != 7)
			{
				throw new ArgumentException("Expected damage curves for seven bosses");
			}
			values = new double[7][];
			for (int i = 0; i < 7; i++)
			{
				if (source[i] == null || source[i].Length != 6)
				{
					throw new ArgumentException("Expected six damage multipliers");
				}
				values[i] = (double[])source[i].Clone();
				double[] array = values[i];
				foreach (double num in array)
				{
					if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.01 || num > 1000.0)
					{
						throw new ArgumentException("Damage multipliers must be between 0.01 and 1000");
					}
				}
			}
		}

		public static ChallengeDamage Defaults()
		{
			return new ChallengeDamage((from i in Enumerable.Range(0, 7)
				select new double[6] { 1.0, 1.508, 1.664, 1.82, 1.976, 2.145 }).ToArray());
		}

		public float Multiplier(Boss boss, int tier)
		{
			Progression.CheckBoss(boss);
			Progression.CheckTier(tier);
			return (float)values[(int)boss][tier];
		}
	}
	public sealed class ChallengeHealth
	{
		private readonly double[][] values;

		public ChallengeHealth(double[][] source)
		{
			if (source == null || source.Length != 7)
			{
				throw new ArgumentException("Expected health curves for seven bosses");
			}
			values = new double[7][];
			for (int i = 0; i < 7; i++)
			{
				if (source[i] == null || source[i].Length != 6)
				{
					throw new ArgumentException("Expected six health multipliers");
				}
				values[i] = (double[])source[i].Clone();
				double[] array = values[i];
				foreach (double value in array)
				{
					ValidateMultiplier(value);
				}
			}
		}

		public static ChallengeHealth Defaults()
		{
			return new ChallengeHealth((from i in Enumerable.Range(0, 7)
				select new double[6] { 1.0, 1.45, 1.95, 2.55, 3.25, 4.0 }).ToArray());
		}

		public float Multiplier(Boss boss, int tier)
		{
			Progression.CheckBoss(boss);
			Progression.CheckTier(tier);
			return (float)values[(int)boss][tier];
		}

		public static float Maximum(float baseHealth, float multiplier)
		{
			if (float.IsNaN(multiplier) || float.IsInfinity(multiplier) || multiplier < 0.01f || multiplier > 1000f)
			{
				throw new ArgumentException("Health multipliers must be between 0.01 and 1000");
			}
			double num = (double)baseHealth * (double)multiplier;
			if (float.IsNaN(baseHealth) || float.IsInfinity(baseHealth) || baseHealth <= 0f || num > 3.4028234663852886E+38 || (float)num <= 0f)
			{
				throw new ArgumentException("Invalid challenge base health");
			}
			return (float)num;
		}

		private static void ValidateMultiplier(double value)
		{
			if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.01 || value > 1000.0)
			{
				throw new ArgumentException("Health multipliers must be between 0.01 and 1000");
			}
		}
	}
	[DataContract]
	public sealed class ProgressionScalingSnapshot
	{
		[DataMember]
		public int Stage;

		[DataMember]
		public bool Enabled;

		[DataMember]
		public float Strength;

		[DataMember]
		public float HealthBaseline;

		[DataMember]
		public float DamageBaseline;

		public void Validate()
		{
			ChallengeProgression.CheckStage(Stage);
			ChallengeProgression.CheckStrength(Strength);
			float[] array = new float[2] { HealthBaseline, DamageBaseline };
			foreach (float num in array)
			{
				if (float.IsNaN(num) || float.IsInfinity(num) || num < 1f || num > 20f)
				{
					throw new ArgumentException("Invalid progression baseline snapshot");
				}
			}
			if (!Enabled && (HealthBaseline != 1f || DamageBaseline != 1f))
			{
				throw new ArgumentException("Disabled progression cannot retain an increased baseline");
			}
		}

		public ProgressionScalingSnapshot Copy()
		{
			Validate();
			ProgressionScalingSnapshot progressionScalingSnapshot = new ProgressionScalingSnapshot();
			progressionScalingSnapshot.Stage = Stage;
			progressionScalingSnapshot.Enabled = Enabled;
			progressionScalingSnapshot.Strength = Strength;
			progressionScalingSnapshot.HealthBaseline = HealthBaseline;
			progressionScalingSnapshot.DamageBaseline = DamageBaseline;
			return progressionScalingSnapshot;
		}

		public static bool Same(ProgressionScalingSnapshot a, ProgressionScalingSnapshot b)
		{
			if (a != null)
			{
				if (b != null && a.Stage == b.Stage && a.Enabled == b.Enabled && a.Strength == b.Strength && a.HealthBaseline == b.HealthBaseline)
				{
					return a.DamageBaseline == b.DamageBaseline;
				}
				return false;
			}
			return b == null;
		}

		public string Description()
		{
			Validate();
			return (Enabled ? (ChallengeProgression.StageName(Stage) + " scaling") : "Progression scaling disabled") + " — challenge strength " + Strength.ToString("0.##", CultureInfo.InvariantCulture) + "×";
		}
	}
	public sealed class ChallengeProgression
	{
		private static readonly float[] health = new float[8] { 1f, 1.45f, 2.1f, 3.05f, 4.4f, 6.4f, 9.3f, 13.5f };

		private static readonly float[] damage = new float[8] { 1f, 1.25f, 1.6f, 2f, 2.55f, 3.2f, 4f, 5f };

		private static readonly string[] names = new string[8] { "Meadows", "Black Forest", "Swamp", "Mountains", "Plains", "Mistlands", "Ashlands", "Deep North" };

		private readonly bool enabled;

		private readonly float strength;

		private readonly int stageOverride;

		public ChallengeProgression(bool enabled = true, float strength = 1f, int stageOverride = -1)
		{
			CheckStrength(strength);
			if (stageOverride != -1)
			{
				CheckStage(stageOverride);
			}
			this.enabled = enabled;
			this.strength = strength;
			this.stageOverride = stageOverride;
		}

		public static void CheckStage(int stage)
		{
			if (stage < 0 || stage > 7)
			{
				throw new ArgumentOutOfRangeException("stage");
			}
		}

		public static void CheckStrength(float strength)
		{
			if (float.IsNaN(strength) || float.IsInfinity(strength) || strength < 0.5f || strength > 3f)
			{
				throw new ArgumentOutOfRangeException("strength");
			}
		}

		public static string StageName(int stage)
		{
			CheckStage(stage);
			return names[stage];
		}

		public static int StageAfter(Boss boss)
		{
			Progression.CheckBoss(boss);
			return (int)(boss + 1);
		}

		public FightScaling For(Boss boss, int tier, int worldStage, float starHealth, float starDamage)
		{
			Progression.CheckBoss(boss);
			Progression.CheckTier(tier);
			CheckStage(worldStage);
			FightScaling fightScaling = new FightScaling();
			fightScaling.Health = starHealth;
			fightScaling.Damage = starDamage;
			FightScaling fightScaling2 = fightScaling;
			fightScaling2.Validate();
			if (tier == 0)
			{
				return fightScaling2;
			}
			int num = Math.Max((int)boss, (stageOverride < 0) ? worldStage : stageOverride);
			int num2 = (int)(enabled ? (num - boss) : Boss.Eikthyr);
			fightScaling2.Progression = new ProgressionScalingSnapshot
			{
				Stage = num,
				Enabled = enabled,
				Strength = strength,
				HealthBaseline = health[num2],
				DamageBaseline = damage[num2]
			};
			fightScaling2.Health *= health[num2] * strength;
			fightScaling2.Damage *= damage[num2] * strength;
			fightScaling2.Validate();
			return fightScaling2;
		}
	}
	public enum EscrowDecision
	{
		AwaitHost,
		Consume,
		Return
	}
	[DataContract]
	public sealed class EscrowEntry
	{
		[DataMember]
		public string Operation;

		[DataMember]
		public string Receipt;

		[DataMember]
		public string Prefab;

		[DataMember]
		public byte[] ItemBytes;

		[DataMember]
		public byte[] RequestBytes;

		[DataMember]
		public EscrowDecision Decision;

		[DataMember]
		public bool InventorySaved;

		[DataMember]
		public bool RemovalStarted;

		[DataMember(EmitDefaultValue = false)]
		public int MarkerVersion;
	}
	[DataContract]
	public sealed class EscrowState
	{
		[DataMember]
		public int Schema = 2;

		[DataMember]
		public long World;

		[DataMember]
		public long Player;

		[DataMember]
		public List<EscrowEntry> Entries = new List<EscrowEntry>();
	}
	public sealed class ClientEscrow
	{
		private readonly string path;

		private EscrowState state;

		public ClientEscrow(string savePath, long world, long player)
		{
			if (player <= 0)
			{
				throw new ArgumentOutOfRangeException("player");
			}
			path = savePath;
			state = (File.Exists(path) ? Decode(File.ReadAllBytes(path)) : new EscrowState
			{
				World = world,
				Player = player
			});
			if (state.World != world || state.Player != player)
			{
				throw new InvalidDataException("Escrow belongs to another world or character");
			}
		}

		public EscrowEntry[] Pending()
		{
			return Clone().Entries.Where((EscrowEntry e) => !e.InventorySaved).ToArray();
		}

		public EscrowEntry Read(string operation)
		{
			return Find(Clone(), operation);
		}

		public void Store(string operation, TrophyIdentity identity, string prefab, byte[] serializedItem, byte[] requestBytes = null)
		{
			Progression.CheckId(operation);
			if (identity == null || identity.WorldId != state.World || prefab != TrophyIdentity.PrefabName(identity.Boss, identity.Tier))
			{
				throw new ArgumentException("Invalid escrow identity");
			}
			identity.Write(new Dictionary<string, string>());
			if (serializedItem == null || serializedItem.Length == 0 || serializedItem.Length > 1048576)
			{
				throw new ArgumentException("Invalid item snapshot");
			}
			if (requestBytes != null)
			{
				ValidateRequest(requestBytes, state.World, operation, identity.Receipt, prefab);
			}
			Commit(delegate(EscrowState next)
			{
				EscrowEntry escrowEntry = next.Entries.Find((EscrowEntry e) => e.Operation == operation);
				if (escrowEntry != null)
				{
					if (escrowEntry.Receipt != identity.Receipt || escrowEntry.Prefab != prefab || !escrowEntry.ItemBytes.SequenceEqual(serializedItem))
					{
						throw new InvalidOperationException("Escrow replay changed item");
					}
					if (escrowEntry.RequestBytes == null != (requestBytes == null) || (requestBytes != null && !escrowEntry.RequestBytes.SequenceEqual(requestBytes)))
					{
						throw new InvalidOperationException("Escrow replay changed request");
					}
					if (escrowEntry.InventorySaved || escrowEntry.Decision != EscrowDecision.AwaitHost)
					{
						throw new InvalidOperationException("Resolved escrow cannot remove an item again");
					}
				}
				else
				{
					if (next.Entries.Any((EscrowEntry e) => e.Receipt == identity.Receipt && !e.InventorySaved))
					{
						throw new InvalidOperationException("Trophy already escrowed");
					}
					next.Entries.Add(new EscrowEntry
					{
						Operation = operation,
						Receipt = identity.Receipt,
						Prefab = prefab,
						ItemBytes = (byte[])serializedItem.Clone(),
						RequestBytes = ((requestBytes == null) ? null : ((byte[])requestBytes.Clone())),
						MarkerVersion = 1
					});
				}
			});
		}

		public void BeginRemoval(string operation)
		{
			Commit(delegate(EscrowState next)
			{
				EscrowEntry escrowEntry = Find(next, operation);
				if (escrowEntry.InventorySaved || escrowEntry.Decision != EscrowDecision.AwaitHost)
				{
					throw new InvalidOperationException("Resolved escrow cannot be removed");
				}
				escrowEntry.RemovalStarted = true;
			});
		}

		public void EnableCharacterMarker(string operation)
		{
			Commit(delegate(EscrowState next)
			{
				EscrowEntry escrowEntry = Find(next, operation);
				if (escrowEntry.MarkerVersion != 1)
				{
					if (escrowEntry.InventorySaved || escrowEntry.RemovalStarted || escrowEntry.Decision == EscrowDecision.Consume)
					{
						throw new InvalidOperationException("Legacy removal requires recovery review");
					}
					escrowEntry.MarkerVersion = 1;
				}
			});
		}

		public void RecordHostOutcome(string operation, OfferPhase phase)
		{
			if (!Enum.IsDefined(typeof(OfferPhase), phase))
			{
				throw new ArgumentException("Unknown host outcome");
			}
			Commit(delegate(EscrowState next)
			{
				EscrowEntry escrowEntry = Find(next, operation);
				if (phase != OfferPhase.Prepared)
				{
					EscrowDecision escrowDecision = ((phase == OfferPhase.Committed) ? EscrowDecision.Consume : EscrowDecision.Return);
					if (escrowEntry.Decision != EscrowDecision.AwaitHost && escrowEntry.Decision != escrowDecision)
					{
						throw new InvalidOperationException("Host outcome conflicts with durable outcome");
					}
					escrowEntry.Decision = escrowDecision;
				}
			});
		}

		public void ConfirmInventorySaved(string operation)
		{
			Commit(delegate(EscrowState next)
			{
				EscrowEntry escrowEntry = Find(next, operation);
				if (escrowEntry.Decision == EscrowDecision.AwaitHost)
				{
					throw new InvalidOperationException("Cannot settle without a host decision");
				}
				escrowEntry.InventorySaved = true;
			});
		}

		private static EscrowEntry Find(EscrowState value, string operation)
		{
			Progression.CheckId(operation);
			EscrowEntry escrowEntry = value.Entries.Find((EscrowEntry e) => e.Operation == operation);
			if (escrowEntry == null)
			{
				throw new InvalidOperationException("Unknown escrow operation");
			}
			return escrowEntry;
		}

		private EscrowState Clone()
		{
			return Decode(Encode(state));
		}

		private void Commit(Action<EscrowState> transition)
		{
			EscrowState escrowState = Clone();
			transition(escrowState);
			AtomicFile.Write(path, Encode(escrowState));
			state = escrowState;
		}

		private static byte[] Encode(EscrowState value)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Validate(value);
			using MemoryStream memoryStream = new MemoryStream();
			((XmlObjectSerializer)new DataContractJsonSerializer(typeof(EscrowState))).WriteObject((Stream)memoryStream, (object)value);
			return memoryStream.ToArray();
		}

		private static EscrowState Decode(byte[] bytes)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			using MemoryStream memoryStream = new MemoryStream(bytes);
			EscrowState escrowState = (EscrowState)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(EscrowState))).ReadObject((Stream)memoryStream);
			if (escrowState != null && escrowState.Schema == 1)
			{
				escrowState.Schema = 2;
			}
			Validate(escrowState);
			return escrowState;
		}

		private static void Validate(EscrowState value)
		{
			if (value == null || value.Schema != 2 || value.Player <= 0 || value.Entries == null || value.Entries.Any((EscrowEntry e) => e == null))
			{
				throw new InvalidDataException("Invalid escrow file");
			}
			if ((from e in value.Entries
				group e by e.Operation).Any((IGrouping<string, EscrowEntry> g) => g.Count() != 1) || (from e in value.Entries
				where !e.InventorySaved
				group e by e.Receipt).Any((IGrouping<string, EscrowEntry> g) => g.Count() != 1))
			{
				throw new InvalidDataException("Duplicate escrow record");
			}
			foreach (EscrowEntry entry in value.Entries)
			{
				Progression.CheckId(entry.Operation);
				if (!Guid.TryParseExact(entry.Receipt, "N", out var _) || string.IsNullOrEmpty(entry.Prefab) || entry.ItemBytes == null || entry.ItemBytes.Length == 0 || entry.ItemBytes.Length > 1048576 || !Enum.IsDefined(typeof(EscrowDecision), entry.Decision) || (entry.InventorySaved && entry.Decision == EscrowDecision.AwaitHost) || entry.MarkerVersion < 0 || entry.MarkerVersion > 1)
				{
					throw new InvalidDataException("Invalid escrow entry");
				}
				if (entry.RequestBytes != null)
				{
					ValidateRequest(entry.RequestBytes, value.World, entry.Operation, entry.Receipt, entry.Prefab);
				}
			}
		}

		private static void ValidateRequest(byte[] bytes, long world, string operation, string receipt, string prefab)
		{
			OfferingRequest offeringRequest = OfferingRequest.Decode(bytes, world);
			if (offeringRequest.Action != OfferingAction.Prepare || offeringRequest.Operation != operation || offeringRequest.Receipt != receipt || TrophyIdentity.PrefabName(offeringRequest.Boss, offeringRequest.Tier) != prefab)
			{
				throw new InvalidDataException("Escrow request does not match saved item");
			}
		}
	}
	public sealed class CombatDrainBarrier
	{
		private readonly HashSet<long> peers = new HashSet<long>();

		private readonly HashSet<long> ready = new HashSet<long>();

		public long Token { get; private set; }

		public bool Complete
		{
			get
			{
				if (peers.Count > 0)
				{
					return peers.SetEquals(ready);
				}
				return false;
			}
		}

		public CombatDrainBarrier(IEnumerable<long> connected)
		{
			NewRound();
			Refresh(connected);
		}

		private void NewRound()
		{
			long token = Token;
			do
			{
				Token = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0);
			}
			while (Token == 0 || Token == token);
			ready.Clear();
		}

		public void Refresh(IEnumerable<long> connected)
		{
			if (connected == null)
			{
				throw new ArgumentNullException("connected");
			}
			HashSet<long> hashSet = new HashSet<long>(connected);
			if (hashSet.Count == 0 || hashSet.Count > 128 || hashSet.Contains(0L))
			{
				throw new ArgumentException("Invalid combat drain peers");
			}
			if (hashSet.Except(peers).Any())
			{
				NewRound();
			}
			peers.Clear();
			peers.UnionWith(hashSet);
			ready.IntersectWith(peers);
		}

		public bool ObserveReport(long peer)
		{
			if (!peers.Contains(peer))
			{
				return false;
			}
			NewRound();
			return true;
		}

		public bool Acknowledge(long peer, long token)
		{
			if (token != Token || !peers.Contains(peer))
			{
				return false;
			}
			ready.Add(peer);
			return true;
		}

		public long[] Pending()
		{
			return (from p in peers.Except(ready)
				orderby p
				select p).ToArray();
		}
	}
	public sealed class CombatDrainPacket
	{
		public const int Size = 32;

		public long World;

		public long BossCreator;

		public long Token;

		public uint BossId;

		public byte[] Encode()
		{
			Validate();
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1);
			binaryWriter.Write(World);
			binaryWriter.Write(BossCreator);
			binaryWriter.Write(BossId);
			binaryWriter.Write(Token);
			return memoryStream.ToArray();
		}

		public static CombatDrainPacket Decode(byte[] bytes, long world)
		{
			if (bytes == null || bytes.Length != 32)
			{
				throw new InvalidDataException("Invalid combat drain packet size");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown combat drain protocol");
			}
			CombatDrainPacket combatDrainPacket = new CombatDrainPacket();
			combatDrainPacket.World = binaryReader.ReadInt64();
			combatDrainPacket.BossCreator = binaryReader.ReadInt64();
			combatDrainPacket.BossId = binaryReader.ReadUInt32();
			combatDrainPacket.Token = binaryReader.ReadInt64();
			CombatDrainPacket combatDrainPacket2 = combatDrainPacket;
			combatDrainPacket2.Validate();
			if (combatDrainPacket2.World != world)
			{
				throw new InvalidDataException("Wrong combat drain world");
			}
			return combatDrainPacket2;
		}

		private void Validate()
		{
			if (World == 0 || BossCreator == 0 || BossId == 0 || Token == 0)
			{
				throw new ArgumentException("Invalid combat drain identity");
			}
		}
	}
	public static class CombatReportSource
	{
		public static string For(long reporter, long session)
		{
			if (reporter < 0 || session == 0)
			{
				throw new ArgumentException("Invalid combat report source");
			}
			return ((reporter == 0) ? "host" : ("player:" + reporter.ToString(CultureInfo.InvariantCulture))) + ":" + session.ToString(CultureInfo.InvariantCulture);
		}
	}
	public sealed class CombatReportJournal : IDisposable
	{
		private sealed class State
		{
			internal long World;

			internal long Reporter;

			internal long Session;

			internal long Sequence;

			internal List<BossDamagePacket> Pending = new List<BossDamagePacket>();

			internal List<BossDamagePacket> Rejected = new List<BossDamagePacket>();
		}

		public const int MaximumPending = 4096;

		public const int MaximumRejected = 256;

		private const int HeaderSize = 44;

		private const int DigestSize = 32;

		public const int MaximumBytes = 317772;

		private readonly string path;

		private readonly Action<string, byte[]> write;

		private FileStream lease;

		private State state;

		public long World => state.World;

		public long Reporter => state.Reporter;

		public long Session => state.Session;

		public int PendingCount => state.Pending.Count;

		public int RejectedCount => state.Rejected.Count;

		public CombatReportJournal(string path, long world, long reporter)
			: this(path, world, reporter, AtomicFile.Write)
		{
		}

		internal CombatReportJournal(string path, long world, long reporter, Action<string, byte[]> writer)
		{
			if (string.IsNullOrWhiteSpace(path) || world == 0 || reporter < 0 || writer == null)
			{
				throw new ArgumentException("Invalid combat journal identity");
			}
			this.path = Path.GetFullPath(path);
			write = writer;
			Directory.CreateDirectory(Path.GetDirectoryName(this.path));
			try
			{
				lease = new FileStream(this.path + ".lock", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
				if (File.Exists(this.path))
				{
					state = ReadState(this.path);
					if (state.World != world || state.Reporter != reporter)
					{
						throw new InvalidDataException("Combat recovery belongs to another world or reporter");
					}
					return;
				}
				long num;
				do
				{
					num = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0);
				}
				while (num == 0);
				State value = new State
				{
					World = world,
					Reporter = reporter,
					Session = num
				};
				write(this.path, Encode(value));
				state = value;
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		public BossDamagePacket First()
		{
			Alive();
			if (state.Pending.Count != 0)
			{
				return Copy(state.Pending[0], state.World);
			}
			return null;
		}

		public BossDamagePacket[] Rejected()
		{
			Alive();
			return state.Rejected.Select((BossDamagePacket p) => Copy(p, state.World)).ToArray();
		}

		public bool HasPending(long creator, uint id)
		{
			Alive();
			return state.Pending.Any((BossDamagePacket p) => p.BossCreator == creator && p.BossId == id);
		}

		public static CombatReportSnapshot Inspect(string path)
		{
			State value = ReadState(Path.GetFullPath(path));
			CombatReportSnapshot combatReportSnapshot = new CombatReportSnapshot();
			combatReportSnapshot.World = value.World;
			combatReportSnapshot.Reporter = value.Reporter;
			combatReportSnapshot.Session = value.Session;
			combatReportSnapshot.LastSequence = value.Sequence;
			combatReportSnapshot.Pending = value.Pending.Select((BossDamagePacket p) => Copy(p, value.World)).ToArray();
			combatReportSnapshot.Rejected = value.Rejected.Select((BossDamagePacket p) => Copy(p, value.World)).ToArray();
			return combatReportSnapshot;
		}

		private static State ReadState(string path)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
			if (fileStream.Length < 76 || fileStream.Length > 317772)
			{
				throw new InvalidDataException("Combat recovery file exceeds bounds");
			}
			byte[] array = new byte[(int)fileStream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = fileStream.Read(array, i, array.Length - i);
				if (num == 0)
				{
					throw new InvalidDataException("Truncated combat recovery file");
				}
			}
			if (fileStream.ReadByte() != -1)
			{
				throw new InvalidDataException("Combat recovery file changed size during read");
			}
			return Decode(array);
		}

		public BossDamagePacket Enqueue(BossDamagePacket observation)
		{
			Alive();
			if (observation == null || observation.World != this.state.World || observation.Session != 0 || observation.Sequence != 0)
			{
				throw new ArgumentException("Observation already has a transport identity or wrong world");
			}
			if (this.state.Pending.Count >= 4096)
			{
				throw new InvalidOperationException("Unacknowledged combat queue is full");
			}
			BossDamagePacket bossDamagePacket = new BossDamagePacket();
			bossDamagePacket.World = observation.World;
			bossDamagePacket.Session = this.state.Session;
			bossDamagePacket.Sequence = checked(this.state.Sequence + 1);
			bossDamagePacket.BossCreator = observation.BossCreator;
			bossDamagePacket.BossId = observation.BossId;
			bossDamagePacket.AttackerCreator = observation.AttackerCreator;
			bossDamagePacket.AttackerId = observation.AttackerId;
			bossDamagePacket.Before = observation.Before;
			bossDamagePacket.After = observation.After;
			bossDamagePacket.Kind = observation.Kind;
			bossDamagePacket.RecipientCreator = observation.RecipientCreator;
			bossDamagePacket.RecipientId = observation.RecipientId;
			BossDamagePacket packet = bossDamagePacket;
			packet = Copy(packet, this.state.World);
			State state = Clone();
			state.Sequence = packet.Sequence;
			state.Pending.Add(packet);
			Commit(state);
			return Copy(packet, this.state.World);
		}

		public bool Acknowledge(BossDamageAck ack)
		{
			Alive();
			if (ack == null || ack.World != this.state.World || ack.Session != this.state.Session || this.state.Pending.Count == 0 || ack.Sequence != this.state.Pending[0].Sequence)
			{
				return false;
			}
			State state = Clone();
			if (!ack.Accepted)
			{
				if (state.Rejected.Count >= 256)
				{
					throw new InvalidOperationException("Combat rejection audit is full; saved observation retained for review");
				}
				state.Rejected.Add(state.Pending[0]);
			}
			state.Pending.RemoveAt(0);
			Commit(state);
			return true;
		}

		private void Alive()
		{
			if (lease == null)
			{
				throw new InvalidOperationException("Combat journal is closed");
			}
		}

		private State Clone()
		{
			return Decode(Encode(state));
		}

		private void Commit(State next)
		{
			byte[] arg = Encode(next);
			write(path, arg);
			state = next;
		}

		private static BossDamagePacket Copy(BossDamagePacket packet, long world)
		{
			return BossDamagePacket.Decode(packet.Encode(), world);
		}

		private static byte[] Encode(State value)
		{
			Validate(value);
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1);
			binaryWriter.Write(value.World);
			binaryWriter.Write(value.Reporter);
			binaryWriter.Write(value.Session);
			binaryWriter.Write(value.Sequence);
			binaryWriter.Write(value.Pending.Count);
			binaryWriter.Write(value.Rejected.Count);
			foreach (BossDamagePacket item in value.Pending.Concat(value.Rejected))
			{
				binaryWriter.Write(item.Encode());
			}
			binaryWriter.Flush();
			byte[] buffer = memoryStream.ToArray();
			using (SHA256 sHA = SHA256.Create())
			{
				binaryWriter.Write(sHA.ComputeHash(buffer));
			}
			return memoryStream.ToArray();
		}

		private static State Decode(byte[] bytes)
		{
			if (bytes == null || bytes.Length < 76 || bytes.Length > 317772)
			{
				throw new InvalidDataException("Invalid combat recovery size");
			}
			using (SHA256 sHA = SHA256.Create())
			{
				if (!sHA.ComputeHash(bytes, 0, bytes.Length - 32).SequenceEqual(bytes.Skip(bytes.Length - 32)))
				{
					throw new InvalidDataException("Combat recovery checksum mismatch");
				}
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown combat recovery schema");
			}
			State state = new State();
			state.World = binaryReader.ReadInt64();
			state.Reporter = binaryReader.ReadInt64();
			state.Session = binaryReader.ReadInt64();
			state.Sequence = binaryReader.ReadInt64();
			State state2 = state;
			int num = binaryReader.ReadInt32();
			int num2 = binaryReader.ReadInt32();
			if (num < 0 || num > 4096 || num2 < 0 || num2 > 256 || bytes.Length != 44 + (num + num2) * 73 + 32)
			{
				throw new InvalidDataException("Invalid combat recovery counts");
			}
			for (int i = 0; i < num + num2; i++)
			{
				BossDamagePacket item = BossDamagePacket.Decode(binaryReader.ReadBytes(73), state2.World);
				((i < num) ? state2.Pending : state2.Rejected).Add(item);
			}
			Validate(state2);
			return state2;
		}

		private static void Validate(State value)
		{
			if (value.World == 0 || value.Reporter < 0 || value.Session == 0 || value.Sequence < 0 || value.Pending.Count > 4096 || value.Rejected.Count > 256)
			{
				throw new InvalidDataException("Invalid combat recovery identity");
			}
			HashSet<long> hashSet = new HashSet<long>();
			List<BossDamagePacket>[] array = new List<BossDamagePacket>[2] { value.Pending, value.Rejected };
			foreach (List<BossDamagePacket> list in array)
			{
				long num = 0L;
				foreach (BossDamagePacket item in list)
				{
					item.Encode();
					if (item.World != value.World || item.Session != value.Session || item.Sequence <= num || item.Sequence > value.Sequence || !hashSet.Add(item.Sequence))
					{
						throw new InvalidDataException("Changed or unordered combat recovery observation");
					}
					num = item.Sequence;
				}
			}
			if (value.Pending.Count > 0 && value.Rejected.Any((BossDamagePacket p) => p.Sequence >= value.Pending[0].Sequence))
			{
				throw new InvalidDataException("Combat rejection bypassed an earlier pending report");
			}
		}

		public void Dispose()
		{
			if (lease != null)
			{
				lease.Dispose();
				lease = null;
			}
		}
	}
	public sealed class CombatReportSnapshot
	{
		public long World;

		public long Reporter;

		public long Session;

		public long LastSequence;

		public BossDamagePacket[] Pending;

		public BossDamagePacket[] Rejected;
	}
	public static class DotDamageAllocation
	{
		public static Dictionary<string, double> Allocate(double loss, double total, double[] channels, IDictionary<string, double>[] fractions)
		{
			Validate(loss);
			Validate(total);
			if (channels == null || fractions == null || channels.Length != fractions.Length)
			{
				throw new ArgumentException("Invalid dot channels");
			}
			Dictionary<string, double> dictionary = new Dictionary<string, double>();
			double num = 0.0;
			foreach (double num2 in channels)
			{
				Validate(num2);
				num += num2;
			}
			Validate(num);
			double num3 = Math.Max(total, num);
			for (int j = 0; j < channels.Length; j++)
			{
				IDictionary<string, double> dictionary2 = fractions[j];
				if (dictionary2 == null)
				{
					continue;
				}
				double num4 = 0.0;
				foreach (KeyValuePair<string, double> item in dictionary2)
				{
					Validate(item.Value);
					if (string.IsNullOrEmpty(item.Key) || item.Value > 1.0)
					{
						throw new ArgumentException("Invalid dot source fraction");
					}
					num4 += item.Value;
				}
				if (num4 > 1.000000001)
				{
					throw new ArgumentException("Dot fractions exceed available damage");
				}
				if (num3 == 0.0 || loss == 0.0 || channels[j] == 0.0)
				{
					continue;
				}
				double num5 = loss * (channels[j] / num3) / Math.Max(1.0, num4);
				foreach (KeyValuePair<string, double> item2 in dictionary2)
				{
					dictionary.TryGetValue(item2.Key, out var value);
					dictionary[item2.Key] = value + num5 * item2.Value;
				}
			}
			return dictionary;
		}

		private static void Validate(double value)
		{
			if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0)
			{
				throw new ArgumentException("Invalid applied dot damage");
			}
		}
	}
	public sealed class DotDamagePool
	{
		private readonly Dictionary<string, double> remaining = new Dictionary<string, double>();

		public double Total { get; private set; }

		public void Synchronize(double nativeRemaining)
		{
			Validate(nativeRemaining);
			if (nativeRemaining == 0.0)
			{
				remaining.Clear();
				Total = 0.0;
			}
			else if (Total > nativeRemaining)
			{
				double num = nativeRemaining / Total;
				foreach (string item in new List<string>(remaining.Keys))
				{
					remaining[item] *= num;
				}
				Total = nativeRemaining;
			}
			else if (Total < nativeRemaining)
			{
				Add("", nativeRemaining - Total);
			}
		}

		public void RecordAddition(string source, double before, double after)
		{
			Validate(before);
			Validate(after);
			Synchronize(before);
			if (after > before)
			{
				Add(source ?? "", after - before);
			}
			else
			{
				Synchronize(after);
			}
		}

		public void RecordReplacement(string source, double amount, double before, double after)
		{
			Validate(amount);
			Validate(before);
			Validate(after);
			Synchronize(before);
			if (amount >= before && after == amount)
			{
				remaining.Clear();
				Total = 0.0;
				Add(source ?? "", after);
			}
			else
			{
				Synchronize(after);
			}
		}

		public Dictionary<string, double> Take(double amount, double nativeRemaining)
		{
			Validate(amount);
			Synchronize(nativeRemaining);
			Dictionary<string, double> dictionary = new Dictionary<string, double>();
			if (Total == 0.0 || amount == 0.0)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, double> item in remaining)
			{
				if (item.Key.Length != 0)
				{
					dictionary.Add(item.Key, item.Value / Total);
				}
			}
			Synchronize(Math.Max(0.0, Total - amount));
			return dictionary;
		}

		private void Add(string source, double amount)
		{
			if (amount != 0.0)
			{
				remaining.TryGetValue(source, out var value);
				remaining[source] = value + amount;
				Total += amount;
			}
		}

		private static void Validate(double value)
		{
			if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0)
			{
				throw new ArgumentException("Invalid native damage pool");
			}
		}
	}
	public enum EikthyrPulse : byte
	{
		Echo = 1,
		Storm,
		Charge,
		Fork,
		Call,
		Sweep
	}
	public static class EikthyrMoves
	{
		public const float SweepHalfAngle = 75f;

		public static bool Allows(int tier, EikthyrPulse kind, int moveset = 2)
		{
			if (tier < 0 || tier > 5)
			{
				return false;
			}
			switch (moveset)
			{
			case 1:
				return kind switch
				{
					EikthyrPulse.Storm => tier >= 5, 
					EikthyrPulse.Echo => tier >= 3, 
					_ => false, 
				};
			default:
				return false;
			case 2:
			case 3:
				switch (kind)
				{
				case EikthyrPulse.Charge:
					if (moveset == 2)
					{
						return tier >= 1;
					}
					return false;
				case EikthyrPulse.Sweep:
					if (moveset == 3)
					{
						return tier >= 1;
					}
					return false;
				case EikthyrPulse.Fork:
					return tier >= 2;
				case EikthyrPulse.Echo:
					return tier >= 3;
				case EikthyrPulse.Storm:
					return tier >= 4;
				case EikthyrPulse.Call:
					return tier == 5;
				default:
					return false;
				}
			}
		}

		public static float Delay(EikthyrPulse kind)
		{
			switch (kind)
			{
			default:
				return 1.8f;
			case EikthyrPulse.Echo:
				return 1.4f;
			case EikthyrPulse.Fork:
				return 1.2f;
			case EikthyrPulse.Charge:
			case EikthyrPulse.Sweep:
				return 1.3f;
			}
		}

		public static float Radius(EikthyrPulse kind)
		{
			return kind switch
			{
				EikthyrPulse.Call => 1.5f, 
				EikthyrPulse.Echo => 9f, 
				EikthyrPulse.Fork => 0.65f, 
				EikthyrPulse.Charge => 1.8f, 
				EikthyrPulse.Sweep => 6f, 
				_ => 2.5f, 
			};
		}

		public static float Cooldown(EikthyrPulse kind)
		{
			switch (kind)
			{
			default:
				return 14f;
			case EikthyrPulse.Call:
				return 40f;
			case EikthyrPulse.Echo:
				return 8f;
			case EikthyrPulse.Fork:
				return 12f;
			case EikthyrPulse.Charge:
			case EikthyrPulse.Sweep:
				return 16f;
			}
		}

		public static float BaseDamage(EikthyrPulse kind)
		{
			switch (kind)
			{
			default:
				return 12f;
			case EikthyrPulse.Call:
				return 0f;
			case EikthyrPulse.Echo:
				return 9.75f;
			case EikthyrPulse.Fork:
				return 7.5f;
			case EikthyrPulse.Charge:
			case EikthyrPulse.Sweep:
				return 12f;
			}
		}

		public static int MaximumPositions(EikthyrPulse kind)
		{
			switch (kind)
			{
			default:
				return 3;
			case EikthyrPulse.Charge:
			case EikthyrPulse.Call:
			case EikthyrPulse.Sweep:
				return 2;
			case EikthyrPulse.Fork:
				return 4;
			case EikthyrPulse.Echo:
				return 1;
			}
		}

		public static float LightningMultiplier(int tier)
		{
			if (tier < 2 || tier > 5)
			{
				return 1f;
			}
			return 1.15f;
		}

		public static string Description(int tier, int moveset = 2)
		{
			if (moveset == 2 || moveset == 3)
			{
				return ((tier < 1) ? "Native attacks" : ((moveset == 3) ? "Antler sweep" : "Antler rush")) + ((tier >= 2) ? "; forked lightning" : "") + ((tier >= 3) ? "; echo stomp" : "") + ((tier >= 4) ? "; storm marks" : "") + ((tier >= 5) ? "; call of the forest" : "") + ".";
			}
			return "Lightning attacks: +15% damage" + ((tier >= 3) ? "; echo stomp (1.4s warning)" : "") + ((tier >= 5) ? "; storm marks (1.8s warning)" : "") + ".";
		}

		public static bool Contains(float dx, float dy, float dz, EikthyrPulse kind)
		{
			if (Finite(dx) && Finite(dy) && Finite(dz) && Math.Abs(dy) <= 3f)
			{
				return dx * dx + dz * dz <= Radius(kind) * Radius(kind);
			}
			return false;
		}

		public static bool ContainsSegment(PulsePosition point, PulsePosition start, PulsePosition end, float radius)
		{
			if (!Valid(point) || !Valid(start) || !Valid(end) || !Finite(radius) || radius < 0f)
			{
				return false;
			}
			float num = end.X - start.X;
			float num2 = end.Z - start.Z;
			float num3 = num * num + num2 * num2;
			float num4 = ((num3 < 0.0001f) ? 0f : Math.Max(0f, Math.Min(1f, ((point.X - start.X) * num + (point.Z - start.Z) * num2) / num3)));
			float num5 = point.X - start.X - num4 * num;
			float num6 = point.Z - start.Z - num4 * num2;
			if (Math.Abs(point.Y - start.Y - num4 * (end.Y - start.Y)) <= 3f)
			{
				return num5 * num5 + num6 * num6 <= radius * radius;
			}
			return false;
		}

		internal static bool Valid(PulsePosition p)
		{
			if (Finite(p.X) && Finite(p.Y))
			{
				return Finite(p.Z);
			}
			return false;
		}

		public static bool ContainsSweep(PulsePosition point, PulsePosition origin, PulsePosition facing)
		{
			if (!Valid(point) || !Valid(origin) || !Valid(facing) || Math.Abs(point.Y - origin.Y) > 3f)
			{
				return false;
			}
			float num = point.X - origin.X;
			float num2 = point.Z - origin.Z;
			float num3 = facing.X - origin.X;
			float num4 = facing.Z - origin.Z;
			float num5 = num * num + num2 * num2;
			float num6 = num3 * num3 + num4 * num4;
			if (num5 > Radius(EikthyrPulse.Sweep) * Radius(EikthyrPulse.Sweep) || num6 < 1f)
			{
				return false;
			}
			if (num5 < 1E-06f)
			{
				return true;
			}
			return (double)(num * num3 + num2 * num4) / Math.Sqrt(num5 * num6) >= Math.Cos(Math.PI * 5.0 / 12.0) - 1E-06;
		}

		internal static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public struct PulsePosition
	{
		public float X;

		public float Y;

		public float Z;
	}
	public sealed class EikthyrPulsePacket
	{
		public int Sequence;

		public long DueTicks;

		public EikthyrPulse Kind;

		public PulsePosition[] Positions;

		public void Validate()
		{
			if (Sequence > 0 && DueTicks > 0)
			{
				long dueTicks = DueTicks;
				DateTime maxValue = DateTime.MaxValue;
				if (dueTicks <= maxValue.Ticks && Enum.IsDefined(typeof(EikthyrPulse), Kind) && Positions != null && Positions.Length >= 1 && Positions.Length <= EikthyrMoves.MaximumPositions(Kind) && ((Kind != EikthyrPulse.Charge && Kind != EikthyrPulse.Call && Kind != EikthyrPulse.Fork && Kind != EikthyrPulse.Sweep) || Positions.Length == EikthyrMoves.MaximumPositions(Kind)))
				{
					PulsePosition[] positions = Positions;
					for (int i = 0; i < positions.Length; i++)
					{
						PulsePosition pulsePosition = positions[i];
						if (!EikthyrMoves.Finite(pulsePosition.X) || !EikthyrMoves.Finite(pulsePosition.Y) || !EikthyrMoves.Finite(pulsePosition.Z))
						{
							throw new InvalidDataException("Invalid Eikthyr pulse position");
						}
					}
					if (Kind != EikthyrPulse.Charge && Kind != EikthyrPulse.Fork && Kind != EikthyrPulse.Sweep)
					{
						return;
					}
					for (int j = 1; j < Positions.Length; j++)
					{
						float num = Positions[j].X - Positions[0].X;
						float num2 = Positions[j].Z - Positions[0].Z;
						if (num * num + num2 * num2 < 1f || num * num + num2 * num2 > 225f || Math.Abs(Positions[j].Y - Positions[0].Y) > 5f)
						{
							throw new InvalidDataException("Invalid Eikthyr corridor");
						}
					}
					return;
				}
			}
			throw new InvalidDataException("Invalid Eikthyr pulse");
		}

		public byte[] Encode()
		{
			Validate();
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(3);
			binaryWriter.Write(Sequence);
			binaryWriter.Write(DueTicks);
			binaryWriter.Write((byte)Kind);
			binaryWriter.Write((byte)Positions.Length);
			PulsePosition[] positions = Positions;
			for (int i = 0; i < positions.Length; i++)
			{
				PulsePosition pulsePosition = positions[i];
				binaryWriter.Write(pulsePosition.X);
				binaryWriter.Write(pulsePosition.Y);
				binaryWriter.Write(pulsePosition.Z);
			}
			return memoryStream.ToArray();
		}

		public static EikthyrPulsePacket Decode(byte[] bytes)
		{
			if (bytes == null || bytes.Length < 30 || bytes.Length > 66)
			{
				throw new InvalidDataException("Invalid pulse size");
			}
			using MemoryStream input = new MemoryStream(bytes);
			using BinaryReader binaryReader = new BinaryReader(input);
			int num = binaryReader.ReadInt32();
			if (num < 1 || num > 3)
			{
				throw new InvalidDataException("Unknown pulse protocol");
			}
			EikthyrPulsePacket eikthyrPulsePacket = new EikthyrPulsePacket();
			eikthyrPulsePacket.Sequence = binaryReader.ReadInt32();
			eikthyrPulsePacket.DueTicks = binaryReader.ReadInt64();
			eikthyrPulsePacket.Kind = (EikthyrPulse)binaryReader.ReadByte();
			EikthyrPulsePacket eikthyrPulsePacket2 = eikthyrPulsePacket;
			int num2 = binaryReader.ReadByte();
			if (num2 < 1 || num2 > ((num == 1) ? 3 : 4) || bytes.Length != 18 + 12 * num2 || (num == 1 && eikthyrPulsePacket2.Kind != EikthyrPulse.Echo && eikthyrPulsePacket2.Kind != EikthyrPulse.Storm) || (num < 3 && eikthyrPulsePacket2.Kind == EikthyrPulse.Sweep))
			{
				throw new InvalidDataException("Invalid pulse length");
			}
			eikthyrPulsePacket2.Positions = new PulsePosition[num2];
			for (int i = 0; i < num2; i++)
			{
				ref PulsePosition reference = ref eikthyrPulsePacket2.Positions[i];
				reference = new PulsePosition
				{
					X = binaryReader.ReadSingle(),
					Y = binaryReader.ReadSingle(),
					Z = binaryReader.ReadSingle()
				};
			}
			eikthyrPulsePacket2.Validate();
			return eikthyrPulsePacket2;
		}
	}
	public static class EscrowScope
	{
		private static readonly HashSet<string> trophies = CreateTrophies();

		private static HashSet<string> CreateTrophies()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < 7; i++)
			{
				for (int j = 0; j < 6; j++)
				{
					hashSet.Add(TrophyIdentity.PrefabName((Boss)i, j));
				}
			}
			return hashSet;
		}

		public static bool Trophy(string prefab, string hold)
		{
			if (prefab != null && trophies.Contains(prefab))
			{
				return !string.IsNullOrEmpty(hold);
			}
			return false;
		}

		public static string ProtectionLock(string standLock, string prefab, string trophyHold)
		{
			if (string.IsNullOrEmpty(standLock))
			{
				if (!Trophy(prefab, trophyHold))
				{
					return "";
				}
				return trophyHold;
			}
			return standLock;
		}
	}
	[DataContract]
	public sealed class MigrationPlan
	{
		[DataMember(IsRequired = true)]
		public int Schema = 1;

		[DataMember(IsRequired = true)]
		public string Id;

		[DataMember(IsRequired = true)]
		public long WorldId;

		[DataMember(IsRequired = true)]
		public string Reason;

		[DataMember(IsRequired = true)]
		public int[] Altars = PlayerProgress.Fresh();

		[DataMember(IsRequired = true)]
		public List<PlayerProgress> Players = new List<PlayerProgress>();

		public void Validate()
		{
			if (Schema != 1 || WorldId == 0 || !Guid.TryParseExact(Id, "N", out var result) || result == Guid.Empty || result.ToString("N") != Id || string.IsNullOrWhiteSpace(Reason) || Reason.Length > 512 || Reason.Any(char.IsControl))
			{
				throw new InvalidDataException("Invalid migration identity or reason");
			}
			if (Altars == null || Altars.Length != 7 || Altars.Any((int t) => t < -1 || t > 5) || Players == null || Players.Count > 128 || Players.Any((PlayerProgress p) => p == null) || (from p in Players
				group p by p.PlayerId).Any((IGrouping<long, PlayerProgress> g) => g.Count() != 1))
			{
				throw new InvalidDataException("Invalid migration altar/player list");
			}
			foreach (PlayerProgress player in Players)
			{
				if (player.PlayerId <= 0 || player.Eligible == null || player.Claimed == null || player.Eligible.Length != 7 || player.Claimed.Length != 7)
				{
					throw new InvalidDataException("Invalid migration character");
				}
				for (int num = 0; num < 7; num++)
				{
					if (player.Eligible[num] < -1 || player.Eligible[num] > 5 || player.Claimed[num] < -1 || player.Claimed[num] > player.Eligible[num] || player.Claimed[num] > Altars[num])
					{
						throw new InvalidDataException("Migration claims must fit explicit victories and altars");
					}
				}
			}
			if (Altars.All((int t) => t == -1) && Players.All((PlayerProgress p) => p.Eligible.All((int t) => t == -1)))
			{
				throw new InvalidDataException("Migration plan contains no grants");
			}
		}

		public MigrationPlan Copy()
		{
			Validate();
			MigrationPlan migrationPlan = new MigrationPlan();
			migrationPlan.Schema = Schema;
			migrationPlan.Id = Id;
			migrationPlan.WorldId = WorldId;
			migrationPlan.Reason = Reason;
			migrationPlan.Altars = (int[])Altars.Clone();
			migrationPlan.Players = (from p in Players
				orderby p.PlayerId
				select new PlayerProgress
				{
					PlayerId = p.PlayerId,
					Eligible = (int[])p.Eligible.Clone(),
					Claimed = (int[])p.Claimed.Clone()
				}).ToList();
			return migrationPlan;
		}

		public byte[] Encode()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			MigrationPlan migrationPlan = Copy();
			using MemoryStream memoryStream = new MemoryStream();
			((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationPlan))).WriteObject((Stream)memoryStream, (object)migrationPlan);
			return memoryStream.ToArray();
		}

		public string Fingerprint()
		{
			return Hash(Encode());
		}

		internal static string Hash(byte[] bytes)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant();
		}

		internal static bool IsHash(string hash)
		{
			if (hash != null && hash.Length == 64)
			{
				return hash.All((char c) => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
			}
			return false;
		}

		public static MigrationPlan Decode(byte[] bytes, long expectedWorld)
		{
			//IL_0114: Expected O, but got Unknown
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if (bytes == null || bytes.Length == 0 || bytes.Length > 262144)
			{
				throw new InvalidDataException("Migration plan file exceeds bounds");
			}
			if (bytes.Length >= 3 && bytes[0] == 239 && bytes[1] == 187 && bytes[2] == 191)
			{
				bytes = bytes.Skip(3).ToArray();
			}
			StrictJson.Validate(bytes);
			try
			{
				XmlDictionaryReaderQuotas val = new XmlDictionaryReaderQuotas();
				val.MaxDepth = 16;
				val.MaxArrayLength = 2048;
				val.MaxStringContentLength = 4096;
				val.MaxBytesPerRead = 4096;
				val.MaxNameTableCharCount = 4096;
				XmlDictionaryReaderQuotas val2 = val;
				XmlDictionaryReader val3 = JsonReaderWriterFactory.CreateJsonReader(bytes, val2);
				try
				{
					MigrationPlan migrationPlan = (MigrationPlan)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationPlan))).ReadObject(val3);
					if (((XmlReader)val3).Read())
					{
						throw new InvalidDataException("Trailing migration plan content");
					}
					if (migrationPlan == null)
					{
						throw new InvalidDataException("Missing migration plan");
					}
					migrationPlan.Validate();
					if (migrationPlan.WorldId != expectedWorld)
					{
						throw new InvalidDataException("Migration plan belongs to another world");
					}
					return migrationPlan.Copy();
				}
				finally
				{
					((IDisposable)val3)?.Dispose();
				}
			}
			catch (SerializationException innerException)
			{
				throw new InvalidDataException("Invalid migration plan JSON", innerException);
			}
			catch (XmlException ex)
			{
				XmlException innerException2 = ex;
				throw new InvalidDataException("Invalid migration plan JSON", (Exception?)(object)innerException2);
			}
		}
	}
	[DataContract]
	public sealed class MigrationRecord
	{
		[DataMember]
		public MigrationPlan Plan;

		[DataMember]
		public string Fingerprint;

		[DataMember]
		public string AppliedUtc;

		[DataMember]
		public string BeforeHash;

		public void Validate(long world)
		{
			if (Plan == null)
			{
				throw new InvalidDataException("Missing migration receipt plan");
			}
			Plan.Validate();
			if (Plan.WorldId != world || Fingerprint != Plan.Fingerprint() || !MigrationPlan.IsHash(BeforeHash) || !DateTime.TryParseExact(AppliedUtc, "yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var _))
			{
				throw new InvalidDataException("Invalid migration receipt");
			}
		}
	}
	public sealed class MigrationPlayerPreview
	{
		public long PlayerId;

		public int[] BeforeEligible;

		public int[] AfterEligible;

		public int[] BeforeClaimed;

		public int[] AfterClaimed;
	}
	public sealed class MigrationPreview
	{
		public string Id;

		public string Fingerprint;

		public bool AlreadyApplied;

		public int[] BeforeAltars;

		public int[] AfterAltars;

		public MigrationPlayerPreview[] Players;
	}
	public static class ExistingWorldMigration
	{
		public static MigrationPreview Preview(WorldProgress state, MigrationPlan plan)
		{
			MigrationPlan migrationPlan = plan.Copy();
			WorldProgress after = WorldStore.Decode(WorldStore.Encode(state), state.WorldId);
			bool flag = Apply(after, migrationPlan);
			MigrationPreview migrationPreview = new MigrationPreview();
			migrationPreview.Id = migrationPlan.Id;
			migrationPreview.Fingerprint = migrationPlan.Fingerprint();
			migrationPreview.AlreadyApplied = !flag;
			migrationPreview.BeforeAltars = (int[])state.Altars.Clone();
			migrationPreview.AfterAltars = (int[])after.Altars.Clone();
			migrationPreview.Players = migrationPlan.Players.Select(delegate(PlayerProgress p)
			{
				PlayerProgress playerProgress = state.Players.Find((PlayerProgress q) => q.PlayerId == p.PlayerId);
				PlayerProgress playerProgress2 = after.Players.Single((PlayerProgress q) => q.PlayerId == p.PlayerId);
				return new MigrationPlayerPreview
				{
					PlayerId = p.PlayerId,
					BeforeEligible = ((playerProgress == null) ? PlayerProgress.Fresh() : ((int[])playerProgress.Eligible.Clone())),
					AfterEligible = (int[])playerProgress2.Eligible.Clone(),
					BeforeClaimed = ((playerProgress == null) ? PlayerProgress.Fresh() : ((int[])playerProgress.Claimed.Clone())),
					AfterClaimed = (int[])playerProgress2.Claimed.Clone()
				};
			}).ToArray();
			return migrationPreview;
		}

		public static bool Apply(WorldProgress state, MigrationPlan plan, string beforeHash = null)
		{
			WorldStore.Validate(state);
			MigrationPlan accepted = plan.Copy();
			if (accepted.WorldId != state.WorldId)
			{
				throw new InvalidOperationException("Migration world changed");
			}
			string text = accepted.Fingerprint();
			MigrationRecord migrationRecord = state.Migrations.Find((MigrationRecord m) => m.Plan.Id == accepted.Id);
			if (migrationRecord != null)
			{
				if (migrationRecord.Fingerprint != text)
				{
					throw new InvalidOperationException("Applied migration ID cannot be reused for changed grants");
				}
				return false;
			}
			if (state.Migrations.Count >= 1024)
			{
				throw new InvalidOperationException("Migration receipt limit reached");
			}
			if (beforeHash == null)
			{
				beforeHash = MigrationPlan.Hash(WorldStore.Encode(state));
			}
			if (!MigrationPlan.IsHash(beforeHash))
			{
				throw new InvalidDataException("Invalid migration baseline hash");
			}
			for (int num = 0; num < 7; num++)
			{
				state.Altars[num] = Math.Max(state.Altars[num], accepted.Altars[num]);
			}
			foreach (PlayerProgress player in accepted.Players)
			{
				PlayerProgress playerProgress = Progression.Player(state, player.PlayerId);
				for (int num2 = 0; num2 < 7; num2++)
				{
					playerProgress.Eligible[num2] = Math.Max(playerProgress.Eligible[num2], player.Eligible[num2]);
					playerProgress.Claimed[num2] = Math.Max(playerProgress.Claimed[num2], player.Claimed[num2]);
				}
			}
			state.Migrations.Add(new MigrationRecord
			{
				Plan = accepted,
				Fingerprint = text,
				BeforeHash = beforeHash,
				AppliedUtc = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture)
			});
			return true;
		}
	}
	[DataContract]
	public sealed class FightRewards
	{
		[DataMember]
		public float Materials;

		[DataMember]
		public int EpicRolls;

		[DataMember]
		public int EpicLevel;

		public void Validate()
		{
			if (float.IsNaN(Materials) || float.IsInfinity(Materials) || Materials < 0f || Materials > 10f || EpicRolls < 0 || EpicRolls > 10 || EpicLevel < 1 || EpicLevel > 6)
			{
				throw new ArgumentException("Invalid challenge reward snapshot");
			}
		}

		public FightRewards Copy()
		{
			Validate();
			FightRewards fightRewards = new FightRewards();
			fightRewards.Materials = Materials;
			fightRewards.EpicRolls = EpicRolls;
			fightRewards.EpicLevel = EpicLevel;
			return fightRewards;
		}

		public static bool Same(FightRewards a, FightRewards b)
		{
			if (!object.ReferenceEquals(a, b))
			{
				if (a != null && b != null && a.Materials == b.Materials && a.EpicRolls == b.EpicRolls)
				{
					return a.EpicLevel == b.EpicLevel;
				}
				return false;
			}
			return true;
		}

		public int MaterialCount(int nativeCount)
		{
			Validate();
			if (nativeCount < 0 || nativeCount > 100000)
			{
				throw new ArgumentException("Invalid native material count");
			}
			return checked((int)Math.Floor((double)nativeCount * (double)Materials + 0.5));
		}

		public string Encode()
		{
			Validate();
			return "1|" + Materials.ToString("R", CultureInfo.InvariantCulture) + "|" + EpicRolls.ToString(CultureInfo.InvariantCulture) + "|" + EpicLevel.ToString(CultureInfo.InvariantCulture);
		}

		public static FightRewards Decode(string text)
		{
			if (text == null || text.Length > 80)
			{
				throw new ArgumentException("Invalid reward marker");
			}
			string[] array = text.Split('|');
			if (array.Length != 4 || array[0] != "1" || !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || !int.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result3))
			{
				throw new ArgumentException("Invalid reward marker");
			}
			FightRewards fightRewards = new FightRewards();
			fightRewards.Materials = result;
			fightRewards.EpicRolls = result2;
			fightRewards.EpicLevel = result3;
			FightRewards fightRewards2 = fightRewards;
			fightRewards2.Validate();
			if (fightRewards2.Encode() != text)
			{
				throw new ArgumentException("Noncanonical reward marker");
			}
			return fightRewards2;
		}
	}
	public sealed class ChallengeRewards
	{
		private readonly FightRewards[][] values;

		public ChallengeRewards(double[][] materials, double[][] rolls, double[][] levels)
		{
			if (materials == null || rolls == null || levels == null || materials.Length != 7 || rolls.Length != 7 || levels.Length != 7)
			{
				throw new ArgumentException("Every boss needs a reward table");
			}
			values = new FightRewards[7][];
			for (int i = 0; i < 7; i++)
			{
				if (materials[i] == null || rolls[i] == null || levels[i] == null || materials[i].Length != 6 || rolls[i].Length != 6 || levels[i].Length != 6)
				{
					throw new ArgumentException("Every reward table needs six tiers");
				}
				values[i] = new FightRewards[6];
				for (int j = 0; j < 6; j++)
				{
					double num = materials[i][j];
					double num2 = rolls[i][j];
					double num3 = levels[i][j];
					if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0 || num > 10.0 || double.IsNaN(num2) || num2 < 0.0 || num2 > 10.0 || num2 != Math.Truncate(num2) || double.IsNaN(num3) || num3 < 1.0 || num3 > 6.0 || num3 != Math.Truncate(num3))
					{
						throw new ArgumentException("Invalid reward tier value");
					}
					values[i][j] = new FightRewards
					{
						Materials = (float)num,
						EpicRolls = (int)num2,
						EpicLevel = (int)num3
					};
					values[i][j].Validate();
				}
			}
		}

		public FightRewards For(Boss boss, int tier)
		{
			Progression.CheckBoss(boss);
			Progression.CheckTier(tier);
			return values[(int)boss][tier].Copy();
		}

		public static ChallengeRewards Defaults()
		{
			return new ChallengeRewards((from i in Enumerable.Range(0, 7)
				select new double[6] { 1.0, 1.0, 1.25, 1.5, 1.75, 2.0 }).ToArray(), (from i in Enumerable.Range(0, 7)
				select new double[6] { 1.0, 1.0, 1.0, 2.0, 2.0, 3.0 }).ToArray(), (from i in Enumerable.Range(0, 7)
				select new double[6] { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }).ToArray());
		}
	}
	[DataContract]
	public sealed class FightScaling
	{
		[DataMember]
		public float Health;

		[DataMember]
		public float Damage;

		[DataMember(EmitDefaultValue = false)]
		public int Moveset;

		[DataMember(EmitDefaultValue = false)]
		public ProgressionScalingSnapshot Progression;

		public void Validate()
		{
			float[] array = new float[2] { Health, Damage };
			foreach (float num in array)
			{
				if (float.IsNaN(num) || float.IsInfinity(num) || num < 0.01f || num > 1000f)
				{
					throw new ArgumentException("Invalid saved challenge scaling");
				}
			}
			if (Progression != null)
			{
				Progression.Validate();
			}
			if (Moveset < 0 || Moveset > 4)
			{
				throw new ArgumentException("Unknown saved challenge moveset");
			}
		}

		public FightScaling Copy()
		{
			Validate();
			FightScaling fightScaling = new FightScaling();
			fightScaling.Health = Health;
			fightScaling.Damage = Damage;
			fightScaling.Moveset = Moveset;
			fightScaling.Progression = ((Progression == null) ? null : Progression.Copy());
			return fightScaling;
		}

		public void ValidateFor(Boss boss, int tier)
		{
			Validate();
			ForsakenAscension.Core.Progression.CheckBoss(boss);
			ForsakenAscension.Core.Progression.CheckTier(tier);
			if (Progression != null && (tier == 0 || Progression.Stage < (int)boss))
			{
				throw new ArgumentException("Progression snapshot is not valid for this challenge");
			}
			if (Moveset != 0 && (tier < ((Moveset != 1) ? 1 : 2) || ((Moveset <= 3) ? ((byte)boss != 0) : (boss == Boss.Eikthyr))))
			{
				throw new ArgumentException("Moveset is not valid for this challenge");
			}
		}

		public bool Matches(float health, float damage)
		{
			if (Health == health)
			{
				return Damage == damage;
			}
			return false;
		}

		public static bool Same(FightScaling a, FightScaling b)
		{
			if (a != null)
			{
				if (b != null && a.Moveset == b.Moveset && a.Matches(b.Health, b.Damage))
				{
					return ProgressionScalingSnapshot.Same(a.Progression, b.Progression);
				}
				return false;
			}
			return b == null;
		}
	}
	public sealed class GameplaySettings
	{
		private readonly List<Benefit> benefits;

		public bool Simultaneous { get; private set; }

		public GameplaySettings(bool simultaneous, IEnumerable<Benefit> source)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			benefits = new List<Benefit>();
			List<Benefit> list = PassiveCatalog.Defaults();
			foreach (Benefit b in source)
			{
				if (b == null || b.Values == null || b.Values.Length != 6 || !list.Any((Benefit e) => e.Key == b.Key && e.Boss == b.Boss) || benefits.Any((Benefit e) => e.Key == b.Key))
				{
					throw new ArgumentException("Unknown, duplicate or incomplete passive setting");
				}
				double[] array = (double[])b.Values.Clone();
				double[] array2 = array;
				foreach (double value in array2)
				{
					Dictionary<string, double> dictionary = new Dictionary<string, double>();
					dictionary.Add(b.Key, value);
					new PassiveSnapshot(dictionary);
				}
				benefits.Add(new Benefit(b.Boss, b.Key, array));
			}
			if (benefits.Count != list.Count)
			{
				throw new ArgumentException("Settings must contain every passive");
			}
			benefits = list.Select((Benefit e) => benefits.Single((Benefit benefit) => benefit.Key == e.Key)).ToList();
			Simultaneous = simultaneous;
		}

		public static GameplaySettings Defaults()
		{
			return new GameplaySettings(simultaneous: true, PassiveCatalog.Defaults());
		}

		public List<Benefit> Export()
		{
			return benefits.Select((Benefit b) => new Benefit(b.Boss, b.Key, (double[])b.Values.Clone())).ToList();
		}

		public PassiveSnapshot Evaluate(PlayerView player, Boss selected)
		{
			ProgressPacket.ValidateView(player);
			Progression.CheckBoss(selected);
			Dictionary<string, double> dictionary = new Dictionary<string, double>();
			foreach (Benefit benefit in benefits)
			{
				int num = player.Effective[(int)benefit.Boss];
				if (num >= 0 && (Simultaneous || benefit.Boss == selected))
				{
					dictionary.Add(benefit.Key, benefit.Values[num]);
				}
			}
			return new PassiveSnapshot(dictionary);
		}

		public static double[] ParseTiers(string text)
		{
			if (text == null || text.Length > 256)
			{
				throw new ArgumentException("Invalid tier list");
			}
			string[] array = text.Split(',');
			if (array.Length != 6)
			{
				throw new ArgumentException("Expected six comma-separated values, normal through five stars");
			}
			double[] array2 = new double[6];
			for (int i = 0; i < 6; i++)
			{
				if (!double.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i]))
				{
					throw new ArgumentException("Invalid tier number");
				}
			}
			return array2;
		}
	}
	public sealed class InputBlockLease
	{
		private readonly Action<bool> change;

		private bool held;

		public InputBlockLease(Action<bool> change)
		{
			if (change == null)
			{
				throw new ArgumentNullException("change");
			}
			this.change = change;
		}

		public void Set(bool block)
		{
			if (held != block)
			{
				change(block);
				held = block;
			}
		}
	}
	[DataContract]
	public sealed class MigrationBackup
	{
		[DataMember]
		public int Schema = 1;

		[DataMember]
		public MigrationPlan Plan;

		[DataMember]
		public WorldProgress Before;

		[DataMember]
		public string BeforeHash;

		private void Validate()
		{
			if (Schema != 1 || Plan == null || Before == null)
			{
				throw new InvalidDataException("Invalid migration baseline backup");
			}
			Plan.Validate();
			WorldStore.Validate(Before);
			if (Plan.WorldId != Before.WorldId || Before.Migrations.Any((MigrationRecord m) => m.Plan.Id == Plan.Id) || BeforeHash != MigrationPlan.Hash(WorldStore.Encode(Before)))
			{
				throw new InvalidDataException("Migration baseline backup identity or contents changed");
			}
		}

		public byte[] Encode()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Validate();
			using MemoryStream memoryStream = new MemoryStream();
			((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationBackup))).WriteObject((Stream)memoryStream, (object)this);
			return memoryStream.ToArray();
		}

		public static MigrationBackup Decode(byte[] bytes, MigrationPlan expected)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				MigrationBackup migrationBackup;
				using (MemoryStream memoryStream = new MemoryStream(bytes))
				{
					migrationBackup = (MigrationBackup)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationBackup))).ReadObject((Stream)memoryStream);
				}
				if (migrationBackup == null)
				{
					throw new InvalidDataException("Missing migration baseline backup");
				}
				migrationBackup.Validate();
				if (migrationBackup.Plan.Fingerprint() != expected.Fingerprint() || !bytes.SequenceEqual(migrationBackup.Encode()))
				{
					throw new InvalidDataException("Migration backup policy or canonical bytes changed");
				}
				return migrationBackup;
			}
			catch (SerializationException innerException)
			{
				throw new InvalidDataException("Invalid migration baseline backup JSON", innerException);
			}
		}

		internal static string PathFor(string savePath, MigrationPlan plan)
		{
			plan.Validate();
			return savePath + ".migration-" + plan.Id + "-before.json";
		}

		internal static string Ensure(string savePath, WorldProgress state, MigrationPlan plan)
		{
			string text = PathFor(savePath, plan);
			if (File.Exists(text))
			{
				return Decode(File.ReadAllBytes(text), plan).BeforeHash;
			}
			WorldProgress worldProgress = WorldStore.Decode(WorldStore.Encode(state), state.WorldId);
			MigrationBackup migrationBackup = new MigrationBackup();
			migrationBackup.Plan = plan.Copy();
			migrationBackup.Before = worldProgress;
			migrationBackup.BeforeHash = MigrationPlan.Hash(WorldStore.Encode(worldProgress));
			MigrationBackup migrationBackup2 = migrationBackup;
			byte[] array = migrationBackup2.Encode();
			string text2 = text + "." + Guid.NewGuid().ToString("N") + ".tmp";
			Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(text)));
			try
			{
				using (FileStream fileStream = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					fileStream.Write(array, 0, array.Length);
					fileStream.Flush(flushToDisk: true);
				}
				try
				{
					File.Move(text2, text);
				}
				catch (IOException)
				{
					if (!File.Exists(text))
					{
						throw;
					}
				}
				return Decode(File.ReadAllBytes(text), plan).BeforeHash;
			}
			finally
			{
				if (File.Exists(text2))
				{
					File.Delete(text2);
				}
			}
		}
	}
	[DataContract]
	public sealed class NaturalQueenSource
	{
		public const string RoomName = "dvergr_new_bossroom_ENTRANCE02";

		[DataMember]
		public string DungeonToken;

		[DataMember]
		public float BirthX;

		[DataMember]
		public float BirthY;

		[DataMember]
		public float BirthZ;

		public void Validate()
		{
			if (DungeonToken == null || DungeonToken.Length != 32 || !Guid.TryParseExact(DungeonToken, "N", out var result) || result == Guid.Empty || result.ToString("N") != DungeonToken)
			{
				throw new ArgumentException("Invalid native Queen dungeon token");
			}
			if (!Coordinate(BirthX) || !Coordinate(BirthY) || !Coordinate(BirthZ))
			{
				throw new ArgumentException("Invalid native Queen birth position");
			}
		}

		internal static bool Coordinate(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Abs(value) <= 1000000f;
			}
			return false;
		}

		public NaturalQueenSource Copy()
		{
			Validate();
			NaturalQueenSource naturalQueenSource = new NaturalQueenSource();
			naturalQueenSource.DungeonToken = DungeonToken;
			naturalQueenSource.BirthX = BirthX;
			naturalQueenSource.BirthY = BirthY;
			naturalQueenSource.BirthZ = BirthZ;
			return naturalQueenSource;
		}

		public bool SameAs(NaturalQueenSource other)
		{
			if (other != null && DungeonToken == other.DungeonToken && BirthX == other.BirthX && BirthY == other.BirthY)
			{
				return BirthZ == other.BirthZ;
			}
			return false;
		}
	}
	public sealed class NativeRoomPlacement
	{
		public int Hash;

		public float X;

		public float Y;

		public float Z;

		public float RX;

		public float RY;

		public float RZ;
	}
	public static class NativeDungeonRooms
	{
		public static NativeRoomPlacement[] Decode(byte[] data)
		{
			if (data == null || data.Length < 4 || data.Length > 114692)
			{
				throw new ArgumentException("Invalid native dungeon room data");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(data, writable: false));
			int num = binaryReader.ReadInt32();
			if (num < 1 || num > 4096 || data.Length != 4 + num * 28)
			{
				throw new ArgumentException("Invalid native dungeon room count");
			}
			NativeRoomPlacement[] array = new NativeRoomPlacement[num];
			for (int i = 0; i < num; i++)
			{
				NativeRoomPlacement nativeRoomPlacement = new NativeRoomPlacement();
				nativeRoomPlacement.Hash = binaryReader.ReadInt32();
				nativeRoomPlacement.X = binaryReader.ReadSingle();
				nativeRoomPlacement.Y = binaryReader.ReadSingle();
				nativeRoomPlacement.Z = binaryReader.ReadSingle();
				nativeRoomPlacement.RX = binaryReader.ReadSingle();
				nativeRoomPlacement.RY = binaryReader.ReadSingle();
				nativeRoomPlacement.RZ = binaryReader.ReadSingle();
				NativeRoomPlacement nativeRoomPlacement2 = nativeRoomPlacement;
				if (!NaturalQueenSource.Coordinate(nativeRoomPlacement2.X) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.Y) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.Z) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.RX) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.RY) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.RZ))
				{
					throw new ArgumentException("Invalid native dungeon placement");
				}
				array[i] = nativeRoomPlacement2;
			}
			return array;
		}
	}
	public static class NaturalQueenEncounter
	{
		public static string FightId(NaturalQueenSource source)
		{
			source.Validate();
			return "native-queen-" + source.DungeonToken;
		}

		public static FightRecord Register(WorldProgress world, NaturalQueenSource source, long creator, uint number, double health, ParticipationRules rules, double now, bool previousSpawnAbsent)
		{
			if (source == null || rules == null)
			{
				throw new ArgumentNullException("Native Queen evidence");
			}
			source.Validate();
			rules.Validate();
			if (creator == 0 || number == 0 || double.IsNaN(health) || double.IsInfinity(health) || health <= 0.0 || double.IsNaN(now) || double.IsInfinity(now) || now < 0.0)
			{
				throw new ArgumentException("Invalid native Queen encounter");
			}
			string id = FightId(source);
			FightRecord fightRecord = world.Fights.SingleOrDefault((FightRecord f) => f.Id == id);
			if (fightRecord != null)
			{
				if (fightRecord.Boss != Boss.Queen || fightRecord.Tier != 0 || fightRecord.NaturalQueen == null || !fightRecord.NaturalQueen.SameAs(source) || fightRecord.Scaling != null || fightRecord.Rewards != null)
				{
					throw new InvalidOperationException("Native Queen evidence conflicts with the saved encounter");
				}
				if (fightRecord.SpawnCreator == creator && fightRecord.SpawnId == number)
				{
					return fightRecord;
				}
				if (!previousSpawnAbsent || world.Fights.Any((FightRecord f) => f.Id != id && f.SpawnCreator == creator && f.SpawnId == number))
				{
					throw new InvalidOperationException("Another boss still occupies this encounter");
				}
				fightRecord.SpawnCreator = creator;
				fightRecord.SpawnId = number;
				return fightRecord;
			}
			if (world.Fights.Any((FightRecord f) => f.SpawnCreator == creator && f.SpawnId == number))
			{
				throw new InvalidOperationException("Queen already belongs to another fight");
			}
			ParticipationLedger participationLedger = new ParticipationLedger(health, rules, now);
			FightRecord fightRecord2 = Progression.BeginFight(world, id, Boss.Queen, 0);
			fightRecord2.NaturalQueen = source.Copy();
			fightRecord2.ReservedRules = rules.Copy();
			fightRecord2.Participation = participationLedger.Export();
			Progression.BindSpawn(world, id, creator, number);
			return fightRecord2;
		}
	}
	public enum OfferPhase
	{
		Prepared,
		Committed,
		Cancelled
	}
	[DataContract]
	public sealed class OfferTransaction
	{
		[DataMember]
		public string OperationId;

		[DataMember]
		public string TrophyId;

		[DataMember]
		public long PlayerId;

		[DataMember]
		public OfferPhase Phase;
	}
	public static class OfferingJournal
	{
		public static OfferPhase Prepare(WorldProgress state, long player, TrophyIdentity trophy, string prefab, string operation)
		{
			Progression.CheckId(operation);
			if (trophy == null)
			{
				throw new ArgumentNullException("trophy");
			}
			OfferTransaction previous = state.Transactions.Find((OfferTransaction t) => t.OperationId == operation);
			if (previous != null)
			{
				VerifyActor(previous, player);
				TrophyRecord record = state.Trophies.Find((TrophyRecord t) => t.Id == previous.TrophyId);
				if (trophy.Receipt != previous.TrophyId || !trophy.Matches(state.WorldId, record, prefab))
				{
					throw new InvalidOperationException("Offering request changed during replay");
				}
				return previous.Phase;
			}
			if (state.Offerings.Any((OfferingRecord o) => o.OperationId == operation))
			{
				throw new InvalidOperationException("Operation already used by an earlier offering");
			}
			TrophyRecord record2 = Progression.ValidateOffering(state, player, trophy.Receipt, operation);
			if (!trophy.Matches(state.WorldId, record2, prefab))
			{
				throw new InvalidOperationException("Trophy identity does not match victory");
			}
			state.Transactions.Add(new OfferTransaction
			{
				OperationId = operation,
				TrophyId = trophy.Receipt,
				PlayerId = player,
				Phase = OfferPhase.Prepared
			});
			return OfferPhase.Prepared;
		}

		public static OfferPhase Commit(WorldProgress state, long player, string operation)
		{
			OfferTransaction transaction = Find(state, player, operation);
			if (transaction.Phase != OfferPhase.Prepared)
			{
				return transaction.Phase;
			}
			TrophyRecord trophyRecord = state.Trophies.Single((TrophyRecord t) => t.Id == transaction.TrophyId);
			if (trophyRecord.Tier <= state.Altars[(int)trophyRecord.Boss])
			{
				transaction.Phase = OfferPhase.Cancelled;
				return transaction.Phase;
			}
			Progression.Offer(state, player, transaction.TrophyId, operation);
			transaction.Phase = OfferPhase.Committed;
			return transaction.Phase;
		}

		public static OfferPhase Cancel(WorldProgress state, long player, string operation)
		{
			OfferTransaction offerTransaction = Find(state, player, operation);
			if (offerTransaction.Phase == OfferPhase.Prepared)
			{
				offerTransaction.Phase = OfferPhase.Cancelled;
			}
			return offerTransaction.Phase;
		}

		public static OfferPhase Query(WorldProgress state, long player, string operation)
		{
			return Find(state, player, operation).Phase;
		}

		public static OfferPhase CancelBound(WorldProgress state, long player, TrophyIdentity identity, string prefab, string operation)
		{
			Progression.CheckId(operation);
			if (identity == null)
			{
				throw new ArgumentNullException("identity");
			}
			TrophyRecord record = state.Trophies.Find((TrophyRecord t) => t.Id == identity.Receipt);
			if (!identity.Matches(state.WorldId, record, prefab) || !state.Players.Any((PlayerProgress p) => p.PlayerId == player && p.Eligible[(int)identity.Boss] >= identity.Tier))
			{
				throw new InvalidOperationException("Invalid cancellation identity");
			}
			OfferTransaction offerTransaction = state.Transactions.Find((OfferTransaction t) => t.OperationId == operation);
			if (offerTransaction != null)
			{
				VerifyActor(offerTransaction, player);
				if (offerTransaction.TrophyId != identity.Receipt)
				{
					throw new InvalidOperationException("Cancellation receipt changed");
				}
				return Cancel(state, player, operation);
			}
			if (state.Offerings.Any((OfferingRecord o) => o.OperationId == operation))
			{
				throw new InvalidOperationException("Operation already belongs to an offering");
			}
			state.Transactions.Add(new OfferTransaction
			{
				OperationId = operation,
				TrophyId = identity.Receipt,
				PlayerId = player,
				Phase = OfferPhase.Cancelled
			});
			return OfferPhase.Cancelled;
		}

		private static OfferTransaction Find(WorldProgress state, long player, string operation)
		{
			Progression.CheckId(operation);
			OfferTransaction offerTransaction = state.Transactions.Find((OfferTransaction t) => t.OperationId == operation);
			if (offerTransaction == null)
			{
				throw new InvalidOperationException("Unknown offering transaction");
			}
			VerifyActor(offerTransaction, player);
			return offerTransaction;
		}

		private static void VerifyActor(OfferTransaction transaction, long player)
		{
			if (player <= 0 || transaction.PlayerId != player)
			{
				throw new InvalidOperationException("Offering belongs to a different player");
			}
		}
	}
	public enum OfferingAction : byte
	{
		Prepare,
		Commit,
		Cancel,
		Query
	}
	public sealed class OfferingRequest
	{
		public long World;

		public OfferingAction Action;

		public Boss Boss;

		public int Tier;

		public string Operation;

		public string Receipt;

		public long AltarOwner;

		public uint AltarId;

		public byte[] Encode()
		{
			Progression.CheckBoss(Boss);
			Progression.CheckTier(Tier);
			if (!Enum.IsDefined(typeof(OfferingAction), Action))
			{
				throw new ArgumentException("Unknown offering action");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1);
			binaryWriter.Write(World);
			binaryWriter.Write((byte)Action);
			binaryWriter.Write((byte)Boss);
			binaryWriter.Write((byte)Tier);
			binaryWriter.Write(Guid.ParseExact(Operation, "N").ToByteArray());
			binaryWriter.Write(Guid.ParseExact(Receipt, "N").ToByteArray());
			binaryWriter.Write(AltarOwner);
			binaryWriter.Write(AltarId);
			return memoryStream.ToArray();
		}

		public static OfferingRequest Decode(byte[] bytes, long expectedWorld)
		{
			if (bytes == null || bytes.Length != 59)
			{
				throw new InvalidDataException("Invalid offering packet size");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown offering protocol");
			}
			OfferingRequest offeringRequest = new OfferingRequest();
			offeringRequest.World = binaryReader.ReadInt64();
			offeringRequest.Action = (OfferingAction)binaryReader.ReadByte();
			offeringRequest.Boss = (Boss)binaryReader.ReadByte();
			offeringRequest.Tier = binaryReader.ReadByte();
			offeringRequest.Operation = new Guid(binaryReader.ReadBytes(16)).ToString("N");
			offeringRequest.Receipt = new Guid(binaryReader.ReadBytes(16)).ToString("N");
			offeringRequest.AltarOwner = binaryReader.ReadInt64();
			offeringRequest.AltarId = binaryReader.ReadUInt32();
			OfferingRequest offeringRequest2 = offeringRequest;
			if (offeringRequest2.World != expectedWorld || !Enum.IsDefined(typeof(OfferingAction), offeringRequest2.Action))
			{
				throw new InvalidDataException("Invalid offering world or action");
			}
			Progression.CheckBoss(offeringRequest2.Boss);
			Progression.CheckTier(offeringRequest2.Tier);
			return offeringRequest2;
		}
	}
	public sealed class OfferingReply
	{
		public long World;

		public string Operation;

		public bool Accepted;

		public OfferPhase Phase;

		public byte[] Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1);
			binaryWriter.Write(World);
			binaryWriter.Write(Guid.ParseExact(Operation, "N").ToByteArray());
			binaryWriter.Write(Accepted);
			binaryWriter.Write((byte)Phase);
			return memoryStream.ToArray();
		}

		public static OfferingReply Decode(byte[] bytes, long world)
		{
			if (bytes == null || bytes.Length != 30)
			{
				throw new InvalidDataException("Invalid offering reply size");
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown offering reply protocol");
			}
			OfferingReply offeringReply = new OfferingReply();
			offeringReply.World = binaryReader.ReadInt64();
			offeringReply.Operation = new Guid(binaryReader.ReadBytes(16)).ToString("N");
			OfferingReply offeringReply2 = offeringReply;
			byte b = binaryReader.ReadByte();
			offeringReply2.Phase = (OfferPhase)binaryReader.ReadByte();
			if (offeringReply2.World != world || b > 1 || !Enum.IsDefined(typeof(OfferPhase), offeringReply2.Phase))
			{
				throw new InvalidDataException("Invalid offering reply");
			}
			offeringReply2.Accepted = b == 1;
			return offeringReply2;
		}
	}
	public enum OfferingRecoveryAction
	{
		AwaitHost,
		Remove,
		SaveHeld,
		ReturnHeld,
		ConsumeHeld,
		SaveReturned,
		SaveConsumed,
		PreserveUnremoved,
		Repair
	}
	public static class OfferingRecovery
	{
		public static OfferingRecoveryAction Hold(EscrowEntry entry, string marker)
		{
			Validate(entry);
			if (entry.InventorySaved || entry.Decision != EscrowDecision.AwaitHost || entry.MarkerVersion != 1)
			{
				return OfferingRecoveryAction.Repair;
			}
			if (marker == null)
			{
				return OfferingRecoveryAction.Remove;
			}
			if (marker == "held" && entry.RemovalStarted)
			{
				return OfferingRecoveryAction.SaveHeld;
			}
			return OfferingRecoveryAction.Repair;
		}

		public static OfferingRecoveryAction Outcome(EscrowEntry entry, string marker)
		{
			Validate(entry);
			if (entry.Decision == EscrowDecision.AwaitHost)
			{
				return OfferingRecoveryAction.AwaitHost;
			}
			if (entry.MarkerVersion == 0)
			{
				if (marker != null || entry.RemovalStarted || entry.Decision != EscrowDecision.Return)
				{
					return OfferingRecoveryAction.Repair;
				}
				return OfferingRecoveryAction.PreserveUnremoved;
			}
			if (entry.Decision == EscrowDecision.Consume)
			{
				if (!entry.RemovalStarted)
				{
					return OfferingRecoveryAction.Repair;
				}
				if (marker == "held")
				{
					return OfferingRecoveryAction.ConsumeHeld;
				}
				if (marker == "consumed")
				{
					return OfferingRecoveryAction.SaveConsumed;
				}
			}
			else
			{
				if (marker == "held" && entry.RemovalStarted)
				{
					return OfferingRecoveryAction.ReturnHeld;
				}
				if (marker == "returned")
				{
					return OfferingRecoveryAction.SaveReturned;
				}
				if (marker == null)
				{
					return OfferingRecoveryAction.PreserveUnremoved;
				}
			}
			return OfferingRecoveryAction.Repair;
		}

		private static void Validate(EscrowEntry entry)
		{
			if (entry == null || entry.MarkerVersion < 0 || entry.MarkerVersion > 1 || !Enum.IsDefined(typeof(EscrowDecision), entry.Decision))
			{
				throw new ArgumentException("Invalid trophy recovery evidence");
			}
		}
	}
	[DataContract]
	public sealed class ParticipationRules
	{
		[DataMember]
		public double Radius = 100.0;

		[DataMember]
		public double GraceSeconds = 180.0;

		[DataMember]
		public double DamageFraction = 0.01;

		[DataMember]
		public double HealingFraction = 0.2;

		[DataMember]
		public double DefenseFraction = 0.2;

		public ParticipationRules Copy()
		{
			Validate();
			ParticipationRules participationRules = new ParticipationRules();
			participationRules.Radius = Radius;
			participationRules.GraceSeconds = GraceSeconds;
			participationRules.DamageFraction = DamageFraction;
			participationRules.HealingFraction = HealingFraction;
			participationRules.DefenseFraction = DefenseFraction;
			return participationRules;
		}

		public bool SameAs(ParticipationRules other)
		{
			if (other != null && Radius == other.Radius && GraceSeconds == other.GraceSeconds && DamageFraction == other.DamageFraction && HealingFraction == other.HealingFraction)
			{
				return DefenseFraction == other.DefenseFraction;
			}
			return false;
		}

		public void Validate()
		{
			double[] array = new double[5] { Radius, GraceSeconds, DamageFraction, HealingFraction, DefenseFraction };
			foreach (double num in array)
			{
				if (double.IsNaN(num) || double.IsInfinity(num) || num <= 0.0)
				{
					throw new ArgumentException("Invalid participation setting");
				}
			}
			if (DamageFraction > 1.0 || HealingFraction > 10.0 || DefenseFraction > 10.0 || Radius > 1000.0 || GraceSeconds > 3600.0)
			{
				throw new ArgumentException("Participation setting exceeds supported range");
			}
		}
	}
	[DataContract]
	public sealed class ParticipationEntry
	{
		[DataMember]
		public long Player;

		[DataMember]
		public double Damage;

		[DataMember]
		public double Healing;

		[DataMember]
		public double Defense;

		[DataMember]
		public double? LastPresent;

		[DataMember]
		public bool Present;
	}
	[DataContract]
	public sealed class ParticipationSnapshot
	{
		[DataMember]
		public double BossMaxHealth;

		[DataMember]
		public double LastTime;

		[DataMember]
		public ParticipationRules Rules;

		[DataMember]
		public ParticipationEntry[] Entries;

		[DataMember]
		public long[] Result;

		[DataMember]