Decompiled source of RagnaroksWrath v0.29.1

plugins/RagnaroksWrath.dll

Decompiled 17 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RavenIron.RagnaroksWrath.Client;
using RavenIron.RagnaroksWrath.Config;
using RavenIron.RagnaroksWrath.Core;
using RavenIron.RagnaroksWrath.Feedback;
using RavenIron.RagnaroksWrath.Net;
using RavenIron.RagnaroksWrath.Systems;
using RavenIron.RagnaroksWrath.Systems.World;
using RavenIron.RagnaroksWrath.Visuals;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Raven Iron")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.29.1.0")]
[assembly: AssemblyInformationalVersion("0.29.1+fafe091cadc7d9fb43403dfda22c261076e244fb")]
[assembly: AssemblyProduct("Ragnarok's Wrath")]
[assembly: AssemblyTitle("RagnaroksWrath")]
[assembly: AssemblyVersion("0.29.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RavenIron.RagnaroksWrath
{
	[BepInPlugin("com.raveniron.ragnarokswrath", "Ragnarok's Wrath", "0.29.1")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class RagnaroksWrath : BaseUnityPlugin
	{
		public const string PluginId = "com.raveniron.ragnarokswrath";

		public const string PluginName = "Ragnarok's Wrath";

		public const string PluginVersion = "0.29.1";

		private Harmony _harmony;

		public static RagnaroksWrath Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static bool IsSimulationAuthority()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			return instance.IsServer();
		}

		public static bool IsDedicated()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.IsDedicated();
			}
			return false;
		}

		private void Awake()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFile advanced = new ConfigFile(Path.Combine(Paths.ConfigPath, "com.raveniron.ragnarokswrath.advanced.cfg"), false, ((BaseUnityPlugin)this).Info.Metadata);
			ModConfig.Bind(((BaseUnityPlugin)this).Config, advanced);
			_harmony = new Harmony("com.raveniron.ragnarokswrath");
			_harmony.PatchAll();
			((Component)this).gameObject.AddComponent<WorldTick>();
			RegisterSystems();
			if ((int)SystemInfo.graphicsDeviceType != 4)
			{
				((Component)this).gameObject.AddComponent<PlagueFog>();
				((Component)this).gameObject.AddComponent<FrostBreath>();
				((Component)this).gameObject.AddComponent<ScorchAsh>();
				((Component)this).gameObject.AddComponent<RelicRunes>();
				((Component)this).gameObject.AddComponent<HealthEffects>();
				((Component)this).gameObject.AddComponent<ConsequenceEffects>();
			}
			Log.LogInfo((object)"Ragnarok's Wrath v0.29.1 loaded.");
		}

		internal static void RegisterSystems()
		{
			WorldTick.Register(new SeasonSystem());
			WorldTick.Register(new WeatherSystem());
			WorldTick.Register(new WindSystem());
			WorldTick.Register(new BiomeStateSystem());
			WorldTick.Register(new FireSystem());
			WorldTick.Register(new PlagueSystem());
			WorldTick.Register(new EcologySystem());
			WorldTick.Register(new FarmingSystem());
			WorldTick.Register(new WorldStateSystem());
			WorldTick.Register(new HealthSystem());
			WorldTick.Register(new ConsequenceSystem());
			WorldTick.Register(new RivalrySystem());
			WorldTick.Register(new RelicSystem());
			WorldTick.Register(new TitleSystem());
			WorldTick.Register(new ZoneSyncSystem());
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			Instance = null;
		}
	}
}
namespace RavenIron.RagnaroksWrath.Visuals
{
	public class FrostBreath : MonoBehaviour
	{
		private const float CheckInterval = 0.5f;

		private ParticleSystem _system;

		private bool _buildFailed;

		private float _nextCheck;

		private float _nextBreath;

		private void Update()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.time + 0.5f;
			try
			{
				ZoneSync.EnsureRegistered();
				SeasonSync.EnsureRegistered();
				if (!ModConfig.FrostBreathEnabled.Value)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || localPlayer.InShelter())
				{
					return;
				}
				Vector3 position = ((Component)localPlayer).transform.position;
				float frost = ZoneSync.StateAt(ZoneKey.FromWorldPos(position)).Frost;
				if (!float.IsNaN(frost) && !(frost < ModConfig.FrostBreathFloor.Value) && !(Time.time < _nextBreath))
				{
					if ((Object)(object)_system == (Object)null && !_buildFailed)
					{
						Build();
					}
					if (!((Object)(object)_system == (Object)null))
					{
						((Component)_system).transform.position = position + Vector3.up * 1.55f + ((Component)localPlayer).transform.forward * 0.25f;
						((Component)_system).transform.rotation = ((Component)localPlayer).transform.rotation;
						_system.Emit(3 + Mathf.RoundToInt(Mathf.Clamp01(frost) * 4f));
						_nextBreath = Time.time + Random.Range(3.2f, 4.4f);
					}
				}
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				RagnaroksWrath.Log.LogWarning((object)("FrostBreath: disabled after error: " + ex.Message));
			}
		}

		private void Build()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = new GameObject("ragnarokswrath_frost_breath");
				val.transform.SetParent(((Component)this).transform, false);
				_system = val.AddComponent<ParticleSystem>();
				MainModule main = _system.main;
				((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1;
				((MainModule)(ref main)).startLifetime = new MinMaxCurve(0.8f, 1.4f);
				((MainModule)(ref main)).startSpeed = new MinMaxCurve(0.25f, 0.55f);
				((MainModule)(ref main)).startSize = new MinMaxCurve(0.12f, 0.3f);
				((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(new Color(0.92f, 0.96f, 1f, 0.3f));
				((MainModule)(ref main)).maxParticles = 64;
				ShapeModule shape = _system.shape;
				((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)4;
				((ShapeModule)(ref shape)).angle = 14f;
				((ShapeModule)(ref shape)).radius = 0.04f;
				EmissionModule emission = _system.emission;
				((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(0f);
				ParticleSystemRenderer component = val.GetComponent<ParticleSystemRenderer>();
				((Renderer)component).material = ParticleKit.BuildMaterial("FrostBreath");
				component.sortMode = (ParticleSystemSortMode)1;
				RagnaroksWrath.Log.LogInfo((object)"FrostBreath: emitter built.");
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				if ((Object)(object)_system != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_system).gameObject);
					_system = null;
				}
				RagnaroksWrath.Log.LogWarning((object)("FrostBreath: build failed, breath disabled: " + ex.Message));
			}
		}
	}
	internal static class ParticleKit
	{
		private static readonly string[] CandidateShaders = new string[6] { "Particles/Standard Unlit", "Legacy Shaders/Particles/Alpha Blended", "Sprites/Default", "UI/Default", "Particles/Standard Surface", "Legacy Shaders/Particles/Additive" };

		public static Material BuildMaterial(string owner)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Expected O, but got Unknown
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			Shader val = FindShader(owner);
			Texture2D val2 = new Texture2D(64, 64, (TextureFormat)4, false);
			float num = 31.5f;
			for (int i = 0; i < 64; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					float num2 = ((float)j - num) / num;
					float num3 = ((float)i - num) / num;
					float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3);
					float num5 = Mathf.Clamp01(1f - num4);
					val2.SetPixel(j, i, new Color(1f, 1f, 1f, num5 * num5));
				}
			}
			val2.Apply();
			return new Material(val)
			{
				mainTexture = (Texture)(object)val2
			};
		}

		private static Shader FindShader(string owner)
		{
			string[] candidateShaders = CandidateShaders;
			foreach (string text in candidateShaders)
			{
				Shader val = Shader.Find(text);
				if ((Object)(object)val != (Object)null)
				{
					RagnaroksWrath.Log.LogInfo((object)(owner + ": using shader '" + text + "'."));
					return val;
				}
			}
			throw new InvalidOperationException("no particle shader available");
		}

		public static Material BuildRuneMaterial(string owner)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Expected O, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			Shader val = FindShader(owner);
			Texture2D val2 = new Texture2D(128, 128, (TextureFormat)4, false);
			Color val3 = default(Color);
			((Color)(ref val3))..ctor(1f, 1f, 1f, 0f);
			for (int i = 0; i < 128; i++)
			{
				for (int j = 0; j < 128; j++)
				{
					val2.SetPixel(j, i, val3);
				}
			}
			DrawStroke(val2, 0, 0, 64, 0.38f, 0.1f, 0.38f, 0.9f);
			DrawStroke(val2, 0, 0, 64, 0.38f, 0.86f, 0.72f, 0.98f);
			DrawStroke(val2, 0, 0, 64, 0.38f, 0.62f, 0.72f, 0.74f);
			DrawStroke(val2, 1, 0, 64, 0.5f, 0.1f, 0.5f, 0.9f);
			DrawStroke(val2, 1, 0, 64, 0.5f, 0.62f, 0.24f, 0.92f);
			DrawStroke(val2, 1, 0, 64, 0.5f, 0.62f, 0.76f, 0.92f);
			DrawStroke(val2, 0, 1, 64, 0.18f, 0.14f, 0.82f, 0.86f);
			DrawStroke(val2, 0, 1, 64, 0.82f, 0.14f, 0.18f, 0.86f);
			DrawStroke(val2, 1, 1, 64, 0.36f, 0.08f, 0.36f, 0.92f);
			DrawStroke(val2, 1, 1, 64, 0.36f, 0.7f, 0.72f, 0.5f);
			DrawStroke(val2, 1, 1, 64, 0.72f, 0.5f, 0.36f, 0.3f);
			val2.Apply();
			return new Material(val)
			{
				mainTexture = (Texture)(object)val2
			};
		}

		private static void DrawStroke(Texture2D tex, int tileX, int tileY, int tile, float x0, float y0, float x1, float y1)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(x0, y0);
			Vector2 val2 = new Vector2(x1, y1) - val;
			float num = Mathf.Max(((Vector2)(ref val2)).sqrMagnitude, 1E-06f);
			for (int i = 0; i < tile; i++)
			{
				for (int j = 0; j < tile; j++)
				{
					Vector2 val3 = new Vector2(((float)j + 0.5f) / (float)tile, ((float)i + 0.5f) / (float)tile);
					float num2 = Mathf.Clamp01(Vector2.Dot(val3 - val, val2) / num);
					float num3 = Vector2.Distance(val3, val + val2 * num2);
					if (!(num3 >= 0.055f))
					{
						float num4 = Mathf.Clamp01((0.055f - num3) / 0.01925f);
						int num5 = tileX * tile + j;
						int num6 = tileY * tile + i;
						Color pixel = tex.GetPixel(num5, num6);
						if (num4 > pixel.a)
						{
							tex.SetPixel(num5, num6, new Color(1f, 1f, 1f, num4));
						}
					}
				}
			}
		}
	}
	public class PlagueFog : MonoBehaviour
	{
		private const float UpdateInterval = 1f;

		private ParticleSystem _system;

		private bool _buildFailed;

		private float _nextUpdate;

		private void Update()
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextUpdate)
			{
				return;
			}
			_nextUpdate = Time.time + 1f;
			try
			{
				ZoneSync.EnsureRegistered();
				SeasonSync.EnsureRegistered();
				if (!ModConfig.PlagueFogEnabled.Value)
				{
					SetEmission(0f);
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					SetEmission(0f);
					return;
				}
				if (localPlayer.InShelter())
				{
					SetEmission(0f);
					return;
				}
				Vector3 position = ((Component)localPlayer).transform.position;
				float num = FogMath.EmissionFor(ZoneSync.StateAt(ZoneKey.FromWorldPos(position)).Plague, ModConfig.PlagueFogDensity.Value);
				if (num > 0f && (Object)(object)_system == (Object)null && !_buildFailed)
				{
					Build();
				}
				if (!((Object)(object)_system == (Object)null))
				{
					((Component)_system).transform.position = position + Vector3.up * 0.5f;
					SetEmission(num);
				}
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				RagnaroksWrath.Log.LogWarning((object)("PlagueFog: disabled after error: " + ex.Message));
			}
		}

		private void SetEmission(float rate)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_system == (Object)null))
			{
				EmissionModule emission = _system.emission;
				((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(rate);
			}
		}

		private void Build()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = new GameObject("ragnarokswrath_plague_fog");
				val.transform.SetParent(((Component)this).transform, false);
				_system = val.AddComponent<ParticleSystem>();
				MainModule main = _system.main;
				((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1;
				((MainModule)(ref main)).startLifetime = new MinMaxCurve(6f, 10f);
				((MainModule)(ref main)).startSpeed = new MinMaxCurve(0.05f, 0.2f);
				((MainModule)(ref main)).startSize = new MinMaxCurve(2.5f, 4.5f);
				((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(new Color(0.42f, 0.5f, 0.38f, 0.16f));
				((MainModule)(ref main)).maxParticles = 600;
				ShapeModule shape = _system.shape;
				((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)5;
				((ShapeModule)(ref shape)).scale = new Vector3(42f, 2.5f, 42f);
				EmissionModule emission = _system.emission;
				((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(0f);
				ParticleSystemRenderer component = val.GetComponent<ParticleSystemRenderer>();
				((Renderer)component).material = ParticleKit.BuildMaterial("PlagueFog");
				component.sortMode = (ParticleSystemSortMode)1;
				RagnaroksWrath.Log.LogInfo((object)"PlagueFog: emitter built.");
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				if ((Object)(object)_system != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_system).gameObject);
					_system = null;
				}
				RagnaroksWrath.Log.LogWarning((object)("PlagueFog: build failed, fog disabled: " + ex.Message));
			}
		}
	}
	public class RelicRunes : MonoBehaviour
	{
		private const float UpdateInterval = 1f;

		private const float EmissionRate = 3f;

		private static readonly Color Blessed = new Color(0.79f, 0.71f, 0.35f, 0.85f);

		private static readonly Color Cursed = new Color(0.71f, 0.31f, 0.31f, 0.85f);

		private readonly Dictionary<ZoneKey, ParticleSystem> _columns = new Dictionary<ZoneKey, ParticleSystem>(4);

		private readonly List<ZoneKey> _dead = new List<ZoneKey>(4);

		private Material _material;

		private bool _buildFailed;

		private float _nextUpdate;

		private void Update()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextUpdate)
			{
				return;
			}
			_nextUpdate = Time.time + 1f;
			try
			{
				RelicSync.EnsureRegistered();
				Player localPlayer = Player.m_localPlayer;
				bool flag = ModConfig.RelicRunesEnabled.Value && (Object)(object)localPlayer != (Object)null && !_buildFailed;
				_dead.Clear();
				ZoneKey zoneKey = (flag ? ZoneKey.FromWorldPos(((Component)localPlayer).transform.position) : default(ZoneKey));
				foreach (KeyValuePair<ZoneKey, ParticleSystem> column in _columns)
				{
					if (!flag || Math.Abs(column.Key.X - zoneKey.X) > 1 || Math.Abs(column.Key.Y - zoneKey.Y) > 1 || !RelicSync.RelicAt(column.Key).Standing)
					{
						_dead.Add(column.Key);
					}
				}
				for (int i = 0; i < _dead.Count; i++)
				{
					ParticleSystem val = _columns[_dead[i]];
					_columns.Remove(_dead[i]);
					if ((Object)(object)val != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)val).gameObject);
					}
				}
				if (!flag)
				{
					return;
				}
				for (int j = -1; j <= 1; j++)
				{
					for (int k = -1; k <= 1; k++)
					{
						ZoneKey zoneKey2 = new ZoneKey(zoneKey.X + j, zoneKey.Y + k);
						if (_columns.ContainsKey(zoneKey2))
						{
							continue;
						}
						RelicLedger.Relic relic = RelicSync.RelicAt(zoneKey2);
						if (relic.Standing)
						{
							ParticleSystem val2 = BuildColumn(zoneKey2, relic.Cursed);
							if ((Object)(object)val2 != (Object)null)
							{
								_columns[zoneKey2] = val2;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				RagnaroksWrath.Log.LogWarning((object)("RelicRunes: disabled after error: " + ex.Message));
			}
		}

		private ParticleSystem BuildColumn(ZoneKey zone, bool cursed)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)_material == (Object)null)
				{
					_material = ParticleKit.BuildRuneMaterial("RelicRunes");
				}
				Vector3 val = zone.ToWorldPos();
				Vector3 val2 = default(Vector3);
				Biome val3 = default(Biome);
				BiomeArea val4 = default(BiomeArea);
				Heightmap val5 = default(Heightmap);
				ZoneSystem.instance.GetGroundData(ref val, ref val2, ref val3, ref val4, ref val5);
				GameObject val6 = new GameObject($"ragnarokswrath_relic_runes_{zone.X}_{zone.Y}");
				val6.transform.SetParent(((Component)this).transform, false);
				val6.transform.position = val + Vector3.up * 0.4f;
				ParticleSystem obj = val6.AddComponent<ParticleSystem>();
				MainModule main = obj.main;
				((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1;
				((MainModule)(ref main)).startLifetime = new MinMaxCurve(4f, 6f);
				((MainModule)(ref main)).startSpeed = new MinMaxCurve(0.25f, 0.45f);
				((MainModule)(ref main)).startSize = new MinMaxCurve(0.28f, 0.42f);
				((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(cursed ? Cursed : Blessed);
				((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(0f);
				((MainModule)(ref main)).maxParticles = 64;
				ShapeModule shape = obj.shape;
				((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)10;
				((ShapeModule)(ref shape)).radius = 1.2f;
				TextureSheetAnimationModule textureSheetAnimation = obj.textureSheetAnimation;
				((TextureSheetAnimationModule)(ref textureSheetAnimation)).enabled = true;
				((TextureSheetAnimationModule)(ref textureSheetAnimation)).numTilesX = 2;
				((TextureSheetAnimationModule)(ref textureSheetAnimation)).numTilesY = 2;
				((TextureSheetAnimationModule)(ref textureSheetAnimation)).animation = (ParticleSystemAnimationType)0;
				((TextureSheetAnimationModule)(ref textureSheetAnimation)).frameOverTime = new MinMaxCurve(0f);
				((TextureSheetAnimationModule)(ref textureSheetAnimation)).startFrame = new MinMaxCurve(0f, 1f);
				EmissionModule emission = obj.emission;
				((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(3f);
				ParticleSystemRenderer component = val6.GetComponent<ParticleSystemRenderer>();
				((Renderer)component).material = _material;
				component.sortMode = (ParticleSystemSortMode)1;
				RagnaroksWrath.Log.LogInfo((object)string.Format("RelicRunes: column raised at {0} ({1}).", zone, cursed ? "cursed" : "blessed"));
				return obj;
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				RagnaroksWrath.Log.LogWarning((object)("RelicRunes: build failed, runes disabled: " + ex.Message));
				return null;
			}
		}
	}
	public class ScorchAsh : MonoBehaviour
	{
		private const float UpdateInterval = 1f;

		private ParticleSystem _system;

		private bool _buildFailed;

		private float _nextUpdate;

		private void Update()
		{
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextUpdate)
			{
				return;
			}
			_nextUpdate = Time.time + 1f;
			try
			{
				ZoneSync.EnsureRegistered();
				if (!ModConfig.ScorchAshEnabled.Value)
				{
					SetEmission(0f);
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					SetEmission(0f);
					return;
				}
				if (localPlayer.InShelter())
				{
					SetEmission(0f);
					return;
				}
				Vector3 position = ((Component)localPlayer).transform.position;
				float num = AshMath.EmissionFor(ZoneSync.StateAt(ZoneKey.FromWorldPos(position)).Scorch, ModConfig.ScorchAshDensity.Value);
				if (num > 0f && (Object)(object)_system == (Object)null && !_buildFailed)
				{
					Build();
				}
				if (!((Object)(object)_system == (Object)null))
				{
					((Component)_system).transform.position = position + Vector3.up * 3.5f;
					SetEmission(num);
				}
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				RagnaroksWrath.Log.LogWarning((object)("ScorchAsh: disabled after error: " + ex.Message));
			}
		}

		private void SetEmission(float rate)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_system == (Object)null))
			{
				EmissionModule emission = _system.emission;
				((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(rate);
			}
		}

		private void Build()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = new GameObject("ragnarokswrath_scorch_ash");
				val.transform.SetParent(((Component)this).transform, false);
				_system = val.AddComponent<ParticleSystem>();
				MainModule main = _system.main;
				((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1;
				((MainModule)(ref main)).startLifetime = new MinMaxCurve(5f, 9f);
				((MainModule)(ref main)).startSpeed = new MinMaxCurve(0.02f, 0.1f);
				((MainModule)(ref main)).startSize = new MinMaxCurve(0.12f, 0.3f);
				((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(new Color(0.16f, 0.15f, 0.14f, 0.75f));
				((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(0.015f);
				((MainModule)(ref main)).maxParticles = 800;
				ShapeModule shape = _system.shape;
				((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)5;
				((ShapeModule)(ref shape)).scale = new Vector3(28f, 1.5f, 28f);
				EmissionModule emission = _system.emission;
				((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(0f);
				ParticleSystemRenderer component = val.GetComponent<ParticleSystemRenderer>();
				((Renderer)component).material = ParticleKit.BuildMaterial("ScorchAsh");
				component.sortMode = (ParticleSystemSortMode)1;
				RagnaroksWrath.Log.LogInfo((object)"ScorchAsh: emitter built.");
			}
			catch (Exception ex)
			{
				_buildFailed = true;
				if ((Object)(object)_system != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_system).gameObject);
					_system = null;
				}
				RagnaroksWrath.Log.LogWarning((object)("ScorchAsh: build failed, ash disabled: " + ex.Message));
			}
		}
	}
}
namespace RavenIron.RagnaroksWrath.Systems
{
	public class TitleSystem : IWorldSystem
	{
		public const string Stormrider = "Stormrider";

		public const string Plaguewalker = "Plaguewalker";

		public const string Winterborn = "Winterborn";

		public const string Ashbringer = "Ashbringer";

		public const string Warden = "Warden";

		public const string Despoiler = "Despoiler";

		private readonly Dictionary<long, float> _winterSeconds = new Dictionary<long, float>(8);

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

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

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

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

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

		private bool _sawWinter;

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

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

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

		private bool _storeLoaded;

		public string Name => "TitleSystem";

		public bool Enabled => ModConfig.EnableTitle.Value;

		public float IntervalSeconds => ModConfig.TitleIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] titles: Stormrider (in a storm's area), Plaguewalker " + string.Format("(plague >= {0:F2} underfoot), {1} ", ModConfig.PlagueSpreadThreshold.Value, "Winterborn") + $"({ModConfig.WinterbornSeconds.Value:F0}s online in Winter). Latest earned wins."));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			TitleSync.EnsureRegistered();
			if (!Persistence.IsLoaded)
			{
				return;
			}
			if (!_storeLoaded)
			{
				TitleStore.Load();
				_storeLoaded = TitleStore.IsLoaded;
			}
			if (!_storeLoaded)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			bool flag = SeasonSystem.Current == Season.Winter;
			if (!flag)
			{
				_winterSeconds.Clear();
				if (_sawWinter)
				{
					TitleStore.ClearWinterborn();
				}
				_sawWinter = false;
			}
			else
			{
				_sawWinter = true;
			}
			float value = ModConfig.PlagueSpreadThreshold.Value;
			_seenThisTick.Clear();
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				long num = val.GetLong(ZDOVars.s_playerID, 0L);
				if (num == 0L)
				{
					continue;
				}
				_seenThisTick.Add(num);
				Vector3 position = val.GetPosition();
				if (_knownOnline.Add(num))
				{
					foreach (KeyValuePair<long, string> item in TitleStore.All())
					{
						TitleSync.Broadcast(item.Key, item.Value);
					}
				}
				string text = null;
				if (TitleEdge.Rises(_stormHeld, num, WeatherSystem.IsStormAt(position)))
				{
					text = "Stormrider";
				}
				if (TitleEdge.Rises(_plagueHeld, num, Persistence.Get(ZoneKey.FromWorldPos(position)).Plague >= value))
				{
					text = "Plaguewalker";
				}
				float num2 = TitleEdge.WinterSeconds(_winterSeconds, num, flag, deltaSeconds);
				if (TitleEdge.Rises(_winterbornHeld, num, flag && num2 >= ModConfig.WinterbornSeconds.Value) && !TitleStore.WinterbornThisWinter(num))
				{
					TitleStore.MarkWinterborn(num);
					text = "Winterborn";
				}
				if (ModConfig.EnableRivalry.Value && RivalryLedger.IsLoaded)
				{
					if (TitleEdge.Rises(_ashbringerHeld, num, RivalryLedger.MaxGrudgeFor(num, ModConfig.GrudgeScale.Value) >= ModConfig.AshbringerGrudge.Value))
					{
						text = "Ashbringer";
					}
					if (TitleEdge.Rises(_wardenHeld, num, RivalrySystem.CareZonesHeld(num) >= ModConfig.WardenZonesHeld.Value))
					{
						text = "Warden";
					}
					if (TitleEdge.Rises(_despoilerHeld, num, RivalrySystem.HarmZonesHeld(num) >= ModConfig.DespoilerZonesHeld.Value))
					{
						text = "Despoiler";
					}
				}
				if (text != null)
				{
					Award(num, val, text);
				}
			}
			_knownOnline.RemoveWhere((long id) => !_seenThisTick.Contains(id));
		}

		private void Award(long playerId, ZDO zdo, string title)
		{
			if (!(TitleStore.Get(playerId) == title))
			{
				TitleStore.Set(playerId, title);
				TitleSync.Broadcast(playerId, title);
				string text = zdo.GetString(ZDOVars.s_playerName, "");
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] {text} ({playerId}) earned {title}.");
				if (ModConfig.AnnounceTitles.Value && !string.IsNullOrEmpty(text) && text != "Stranger" && text != "...")
				{
					MessageFeed.ToEveryone(text + " is now " + title + ".");
				}
			}
		}
	}
	public class ZoneSyncSystem : IWorldSystem
	{
		public string Name => "ZoneSyncSystem";

		public bool Enabled => ModConfig.EnableZoneSync.Value;

		public float IntervalSeconds => ModConfig.ZoneSyncIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] pushing a {ModConfig.ZoneSyncRadiusZones.Value * 2 + 1}x" + $"{ModConfig.ZoneSyncRadiusZones.Value * 2 + 1} zone ring to each player every " + $"{IntervalSeconds:F0}s (absolute snapshots, defaults included)."));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			ZoneSync.EnsureRegistered();
			VersionSync.EnsureRegistered();
			if (!Persistence.IsLoaded)
			{
				return;
			}
			VersionSync.MaybeBroadcast(deltaSeconds);
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZNetPeer> peers;
			try
			{
				peers = instance.GetPeers();
			}
			catch
			{
				return;
			}
			if (peers == null)
			{
				return;
			}
			int radius = Math.Max(1, ModConfig.ZoneSyncRadiusZones.Value);
			ZDOMan instance2 = ZDOMan.instance;
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val != null && val.IsReady())
				{
					long playerId = 0L;
					ZDO val2 = ((instance2 != null) ? instance2.GetZDO(val.m_characterID) : null);
					if (val2 != null)
					{
						playerId = val2.GetLong(ZDOVars.s_playerID, 0L);
					}
					ZoneSync.SendRing(val.m_uid, playerId, ZoneKey.FromWorldPos(val.m_refPos), radius);
				}
			}
		}
	}
}
namespace RavenIron.RagnaroksWrath.Systems.World
{
	public class BiomeStateSystem : IWorldSystem
	{
		private readonly List<ZoneKey> _contacted = new List<ZoneKey>(64);

		private readonly Dictionary<ZoneKey, float> _grudgeByZone = new Dictionary<ZoneKey, float>(64);

		private readonly HashSet<ZoneKey> _mercyZones = new HashSet<ZoneKey>();

		private int _cursor;

		public string Name => "BiomeStateSystem";

		public bool Enabled => ModConfig.EnableBiomeState.Value;

		public float IntervalSeconds => ModConfig.BiomeStateIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] recovery {ModConfig.BiomeRecoveryPerHour.Value:F3}/h, " + $"frost pressure {ModConfig.BiomeFrostPressurePerHour.Value:F3}/h, " + $"contact radius {ModConfig.BiomeContactRadiusZones.Value} zone(s), " + $"max {ModConfig.BiomeMaxZonesPerTick.Value} zone(s)/tick."));
		}

		public void Tick(float deltaSeconds)
		{
			if (!Persistence.IsLoaded)
			{
				return;
			}
			_contacted.Clear();
			_grudgeByZone.Clear();
			_mercyZones.Clear();
			CollectContactedZones(_contacted, _grudgeByZone, _mercyZones);
			if (_contacted.Count == 0)
			{
				_cursor = 0;
				return;
			}
			if (_cursor >= _contacted.Count)
			{
				_cursor = 0;
			}
			float value = ModConfig.BiomeRecoveryPerHour.Value;
			float value2 = ModConfig.BiomeFrostPressurePerHour.Value;
			float coldMultiplier = SeasonSystem.ColdMultiplier();
			float fireRiskMultiplier = SeasonSystem.FireRiskMultiplier();
			float pressurePerHour = ModConfig.PlagueGrowthPerHour.Value * SeasonSystem.PlagueGrowthMultiplier();
			float value3 = ModConfig.PlagueCorruptionBoost.Value;
			int num = Math.Min(ModConfig.BiomeMaxZonesPerTick.Value, _contacted.Count);
			int num2 = 0;
			for (int i = 0; i < num; i++)
			{
				ZoneKey zoneKey = _contacted[_cursor];
				_cursor = (_cursor + 1) % _contacted.Count;
				double num3 = ZoneClock.CreditOnContact(zoneKey);
				if (!(num3 <= 0.0))
				{
					float value4;
					float grudge = (_grudgeByZone.TryGetValue(zoneKey, out value4) ? value4 : 0f);
					float num4 = RivalryMath.GrudgedRecovery(value, grudge);
					if (_mercyZones.Contains(zoneKey))
					{
						num4 *= 1f + ModConfig.MercyRecoveryBonus.Value;
					}
					num4 *= RelicSystem.RecoveryMultiplierAt(zoneKey);
					ZoneState state = BiomeDrift.Apply(Persistence.Get(zoneKey), num3, num4, RivalryMath.GrudgedPressure(value2, grudge), coldMultiplier, fireRiskMultiplier, RivalryMath.GrudgedPressure(pressurePerHour, grudge), value3);
					Persistence.Set(zoneKey, state);
					num2++;
				}
			}
			if (num2 > 0 && ModConfig.VerboseLogging.Value)
			{
				RagnaroksWrath.Log.LogInfo((object)($"[{Name}] drifted {num2} of {_contacted.Count} contacted zone(s); " + $"{Persistence.TrackedZoneCount} zone(s) tracked."));
			}
		}

		private static void CollectContactedZones(List<ZoneKey> into, Dictionary<ZoneKey, float> grudges, HashSet<ZoneKey> mercies)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			int value = ModConfig.BiomeContactRadiusZones.Value;
			bool flag = ModConfig.EnableRivalry.Value && RivalryLedger.IsLoaded;
			float value2 = ModConfig.GrudgeScale.Value;
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch (Exception ex)
			{
				RagnaroksWrath.Log.LogWarning((object)("[BiomeStateSystem] could not read character ZDOs: " + ex.Message));
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				long num = (flag ? val.GetLong(ZDOVars.s_playerID, 0L) : 0);
				ZoneKey zoneKey = ZoneKey.FromWorldPos(val.GetPosition());
				for (int j = -value; j <= value; j++)
				{
					for (int k = -value; k <= value; k++)
					{
						ZoneKey zoneKey2 = new ZoneKey(zoneKey.X + j, zoneKey.Y + k);
						if (!into.Contains(zoneKey2))
						{
							into.Add(zoneKey2);
						}
						if (num != 0L)
						{
							RivalryLedger.Row row = RivalryLedger.Get(zoneKey2, num);
							float num2 = RivalryMath.GrudgeFor(row.Harm, row.Care, value2);
							if (num2 > 0f && (!grudges.TryGetValue(zoneKey2, out var value3) || num2 > value3))
							{
								grudges[zoneKey2] = num2;
							}
							if (RivalrySystem.IsDominantCarer(zoneKey2, num))
							{
								mercies.Add(zoneKey2);
							}
						}
					}
				}
			}
		}
	}
	public class ConsequenceSystem : IWorldSystem
	{
		private readonly HashSet<ZoneKey> _announced = new HashSet<ZoneKey>();

		public string Name => "ConsequenceSystem";

		public bool Enabled => ModConfig.EnableConsequence.Value;

		public float IntervalSeconds => ModConfig.ConsequenceIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] the drift store grows hands: barren pickables (plague >= " + $"{ModConfig.BarrenPlagueThreshold.Value:F2} / scorch >= {ModConfig.BarrenScorchThreshold.Value:F2}), " + $"starred spawns (corruption >= {ModConfig.EmpowerCorruptionThreshold.Value:F2}, " + $"x{ModConfig.EmpowerLevelUpMultiplierAtFull.Value:F1} odds at full), sickened wildlife " + $"(plague >= {ModConfig.SickenPlagueThreshold.Value:F2}), withering crops (blight >= " + $"{ModConfig.CropWitherBlightThreshold.Value:F2}). Player structures are NEVER touched."));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			if (!Persistence.IsLoaded || !ModConfig.AnnounceConsequences.Value)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				Vector3 position = val.GetPosition();
				ZoneKey zoneKey = ZoneKey.FromWorldPos(position);
				if (_announced.Contains(zoneKey))
				{
					continue;
				}
				ConsequenceFlags consequenceFlags = ConsequenceMath.FlagsFor(Persistence.Get(zoneKey), ModConfig.BarrenPlagueThreshold.Value, ModConfig.BarrenScorchThreshold.Value, ModConfig.SickenPlagueThreshold.Value, ModConfig.EmpowerCorruptionThreshold.Value, ModConfig.CropWitherBlightThreshold.Value);
				if (consequenceFlags != ConsequenceFlags.None)
				{
					_announced.Add(zoneKey);
					MessageFeed.ToPlayersNear(position, 64f, LineFor(consequenceFlags));
					if (ModConfig.VerboseLogging.Value)
					{
						RagnaroksWrath.Log.LogInfo((object)$"[{Name}] zone {zoneKey} announced: {consequenceFlags}.");
					}
				}
			}
		}

		internal static string LineFor(ConsequenceFlags flags)
		{
			if ((flags & ConsequenceFlags.Empowered) != ConsequenceFlags.None)
			{
				return "Something festers in this ground.";
			}
			if ((flags & ConsequenceFlags.Barren) != ConsequenceFlags.None)
			{
				return "The land here has gone barren.";
			}
			if ((flags & ConsequenceFlags.Sickening) != ConsequenceFlags.None)
			{
				return "The wild things here are sickening.";
			}
			return "Nothing planted here will live.";
		}
	}
	public class EcologySystem : IWorldSystem
	{
		private readonly List<KeyValuePair<ZoneKey, ZoneState>> _snapshot = new List<KeyValuePair<ZoneKey, ZoneState>>(64);

		public string Name => "EcologySystem";

		public bool Enabled => ModConfig.EnableEcology.Value;

		public float IntervalSeconds => ModConfig.EcologyIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] blight corrupts at {ModConfig.EcologyCorruptionPerHour.Value:F3}/h " + $"where plague>={ModConfig.EcologyPlagueThreshold.Value:F2} or " + $"scorch>={ModConfig.EcologyScorchThreshold.Value:F2}."));
		}

		public void Tick(float deltaSeconds)
		{
			if (!Persistence.IsLoaded)
			{
				return;
			}
			float value = ModConfig.EcologyCorruptionPerHour.Value;
			float value2 = ModConfig.EcologyPlagueThreshold.Value;
			float value3 = ModConfig.EcologyScorchThreshold.Value;
			_snapshot.Clear();
			foreach (KeyValuePair<ZoneKey, ZoneState> item in Persistence.All())
			{
				if (item.Value.Plague >= value2 || item.Value.Scorch >= value3)
				{
					_snapshot.Add(item);
				}
			}
			for (int i = 0; i < _snapshot.Count; i++)
			{
				ZoneState state = EcologyPressure.Apply(_snapshot[i].Value, deltaSeconds, value, value2, value3);
				Persistence.Set(_snapshot[i].Key, state);
			}
		}
	}
	public class FarmingSystem : IWorldSystem
	{
		private string[] _cropPrefabs = Array.Empty<string>();

		private int _prefabCursor;

		private readonly List<ZDO> _found = new List<ZDO>(128);

		private int _sweepIndex;

		private readonly Dictionary<ZoneKey, int> _cropCounts = new Dictionary<ZoneKey, int>(32);

		private float _rotationSeconds;

		public string Name => "FarmingSystem";

		public bool Enabled => ModConfig.EnableFarming.Value;

		public float IntervalSeconds => ModConfig.FarmingIntervalSeconds.Value;

		public void Initialise()
		{
			_cropPrefabs = (ModConfig.FarmingCropPrefabs.Value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < _cropPrefabs.Length; i++)
			{
				_cropPrefabs[i] = _cropPrefabs[i].Trim();
			}
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] sweeping {_cropPrefabs.Length} crop prefab(s) every {IntervalSeconds:F0}s; " + $"depletion {ModConfig.FarmingDepletionPerCropHour.Value:F4}/crop-hour. " + "Depletion is WRITTEN here; the growth consumer bites client-side " + $"(x{ModConfig.FarmingGrowthSlowdownAtFull.Value:0.##} grow time at full depletion)."));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (!Persistence.IsLoaded || _cropPrefabs.Length == 0)
			{
				return;
			}
			ZDOMan instance = ZDOMan.instance;
			if (instance == null)
			{
				return;
			}
			_rotationSeconds += deltaSeconds;
			string text = _cropPrefabs[_prefabCursor];
			try
			{
				bool flag = false;
				while (!flag)
				{
					flag = instance.GetAllZDOsWithPrefabIterative(text, _found, ref _sweepIndex);
				}
			}
			catch (Exception ex)
			{
				RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] sweep failed on '" + text + "': " + ex.Message));
				_found.Clear();
				_sweepIndex = 0;
				return;
			}
			for (int i = 0; i < _found.Count; i++)
			{
				ZDO val = _found[i];
				if (val != null && val.IsValid())
				{
					ZoneKey key = ZoneKey.FromWorldPos(val.GetPosition());
					_cropCounts.TryGetValue(key, out var value);
					_cropCounts[key] = value + 1;
				}
			}
			if (ModConfig.VerboseLogging.Value && _found.Count > 0)
			{
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] '{text}': {_found.Count} standing.");
			}
			_found.Clear();
			_sweepIndex = 0;
			_prefabCursor++;
			if (_prefabCursor < _cropPrefabs.Length)
			{
				return;
			}
			_prefabCursor = 0;
			float num = _rotationSeconds / 3600f;
			_rotationSeconds = 0f;
			float value2 = ModConfig.FarmingDepletionPerCropHour.Value;
			if (value2 > 0f && num > 0f)
			{
				foreach (KeyValuePair<ZoneKey, int> cropCount in _cropCounts)
				{
					ZoneState state = Persistence.Get(cropCount.Key);
					state.Fertility += value2 * (float)cropCount.Value * num;
					Persistence.Set(cropCount.Key, state);
				}
			}
			if (ModConfig.VerboseLogging.Value && _cropCounts.Count > 0)
			{
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] rotation complete: crops in {_cropCounts.Count} zone(s).");
			}
			_cropCounts.Clear();
		}
	}
	public class FireSystem : IWorldSystem
	{
		public const string FireFrontGuid = "com.raveniron.firefront";

		private static readonly Version MinimumFireFrontVersion = new Version(0, 17, 2);

		private static readonly Version IgniterFireFrontVersion = new Version(0, 17, 3);

		private static readonly Version PerFireIgniterFireFrontVersion = new Version(1, 0, 2);

		private static readonly Version GameOneZeroFireFrontVersion = new Version(0, 20, 0);

		private static readonly Version RainFireFrontVersion = new Version(0, 21, 0);

		private bool _fireFrontPresent;

		private PropertyInfo _instanceProperty;

		private MethodInfo _collectMethod;

		private PropertyInfo _igniterProperty;

		private bool _igniterAbsenceLogged;

		private readonly List<Vector3> _firePositions = new List<Vector3>(64);

		private readonly List<ZoneKey> _burningZones = new List<ZoneKey>(16);

		private MethodInfo _collectWithIgnitersMethod;

		private bool _collectWithIgnitersResolved;

		private Version _fireFrontVersion;

		private bool _perFire;

		private readonly List<long> _fireIgniters = new List<long>(64);

		private readonly object[] _collectWithIgnitersArgs = new object[2];

		private readonly List<KeyValuePair<ZoneKey, long>> _blame = new List<KeyValuePair<ZoneKey, long>>(16);

		private bool _blameMismatchLogged;

		private readonly ArsonFootprint _arson = new ArsonFootprint();

		private readonly List<ZoneKey> _arsonZones = new List<ZoneKey>(16);

		private readonly object[] _collectArgs = new object[1];

		private MethodInfo _igniteMethod;

		private bool _igniteGroundAbsenceLogged;

		private readonly Random _rng = new Random();

		private readonly List<ZDO> _stormPlayers = new List<ZDO>(8);

		private readonly List<ZDO> _sectorScratch = new List<ZDO>(256);

		private MethodInfo _rainMethod;

		private bool _rainAbsenceLogged;

		private readonly object[] _rainArgs = new object[1];

		public string Name => "FireSystem";

		public bool Enabled => ModConfig.EnableFire.Value;

		public float IntervalSeconds => ModConfig.FireScorchIntervalSeconds.Value;

		public void Initialise()
		{
			_fireFrontPresent = Chainloader.PluginInfos.ContainsKey("com.raveniron.firefront");
			if (!_fireFrontPresent)
			{
				RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] FireFront not present — dormant. It ships as a dependency of this mod; a manual install likely skipped it. Scorch will not accrue from fire; install FireFront (com.raveniron.firefront) 0.17.2+ to light the world's memory."));
				return;
			}
			Version version = (_fireFrontVersion = Chainloader.PluginInfos["com.raveniron.firefront"].Metadata.Version);
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] FireFront {version} detected — bridging. Burning zones gain " + $"{ModConfig.FireScorchPerMinute.Value:F3} scorch/min."));
			if (version < MinimumFireFrontVersion)
			{
				RagnaroksWrath.Log.LogWarning((object)($"[{Name}] FireFront {version} predates {MinimumFireFrontVersion} — its read API " + "is missing, so scorch CANNOT accrue. Update FireFront; until then every bridge tick will name the unresolved surface."));
			}
			else if (version < IgniterFireFrontVersion)
			{
				RagnaroksWrath.Log.LogInfo((object)($"[{Name}] FireFront {version} predates {IgniterFireFrontVersion} — arson " + "attribution stays dormant; scorch is unaffected."));
			}
			else if (version < PerFireIgniterFireFrontVersion)
			{
				RagnaroksWrath.Log.LogInfo((object)($"[{Name}] FireFront {version} predates {PerFireIgniterFireFrontVersion} — it names " + "one igniter for the whole map, so arson is blamed on that player only where their fire has spread. Update FireFront to blame each fire on whoever lit it."));
			}
			if (version < GameOneZeroFireFrontVersion)
			{
				RagnaroksWrath.Log.LogWarning((object)($"[{Name}] FireFront {version} predates {GameOneZeroFireFrontVersion}, which is the " + "first build ported to Valheim 1.0.7. On 1.0.7 it cannot resolve its own save path, so fires do not survive a restart and anything downstream of them here will look wrong for the wrong reason. Update FireFront."));
			}
			if (ModConfig.StormLightningEnabled.Value)
			{
				bool value = ModConfig.StormsForceWeather.Value;
				RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] storm lightning armed — ~1 bolt per " + $"{ModConfig.LightningMeanMinutes.Value:0.#} storm-minutes, landing " + $"{ModConfig.LightningRingMinMeters.Value:0}-{ModConfig.LightningRingMaxMeters.Value:0}m " + $"from a present player, {ModConfig.LightningStandoffMeters.Value:0}m homestead standoff; " + (value ? "rain judged by each storm's rolled look, since the sky is forced." : "rain judged where each bolt would land, by FireFront's reading of the world's weather.")));
				if (!value && version < RainFireFrontVersion)
				{
					RagnaroksWrath.Log.LogWarning((object)($"[{Name}] FireFront {version} predates {RainFireFrontVersion}, which added the " + "rain read lightning needs when no storm sky is forced. Without it no bolt can be checked for rain, so none will fall. Update FireFront."));
				}
			}
		}

		public void Tick(float deltaSeconds)
		{
			if (!_fireFrontPresent || !Persistence.IsLoaded || !TryCollectFirePositions())
			{
				return;
			}
			TryLightning();
			if (_firePositions.Count == 0)
			{
				_arson.Clear();
				return;
			}
			_burningZones.Clear();
			FireScorch.CollectBurningZones(_firePositions, _burningZones);
			float num = FireScorch.ScorchDelta(ModConfig.FireScorchPerMinute.Value, deltaSeconds);
			if (num <= 0f)
			{
				return;
			}
			float value = ModConfig.ArsonHarmPerScorchPoint.Value;
			bool flag = value > 0f && ModConfig.EnableRivalry.Value && RivalryLedger.IsLoaded;
			long num2 = 0L;
			bool flag2 = false;
			if (_perFire)
			{
				_arson.Clear();
				if (!FireBlame.Collect(_firePositions, _fireIgniters, _blame) && !_blameMismatchLogged)
				{
					_blameMismatchLogged = true;
					RagnaroksWrath.Log.LogWarning((object)($"[{Name}] FireFront returned {_firePositions.Count} fire(s) but " + $"{_fireIgniters.Count} igniter(s) - no arson booked while they disagree."));
				}
				if (flag)
				{
					for (int i = 0; i < _blame.Count; i++)
					{
						RivalryLedger.AddHarm(_blame[i].Key, _blame[i].Value, num * value);
					}
				}
			}
			else
			{
				num2 = TryReadIgniter();
				_arson.Observe(num2, _burningZones, _arsonZones);
				flag2 = num2 != 0L && _arsonZones.Count > 0 && flag;
			}
			for (int j = 0; j < _burningZones.Count; j++)
			{
				ZoneKey zoneKey = _burningZones[j];
				ZoneState state = Persistence.Get(zoneKey);
				state.Scorch += num;
				Persistence.Set(zoneKey, state);
				if (flag2 && _arsonZones.Contains(zoneKey))
				{
					RivalryLedger.AddHarm(zoneKey, num2, num * value);
				}
			}
			if (ModConfig.VerboseLogging.Value)
			{
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] {_firePositions.Count} fire(s) scorching {_burningZones.Count} zone(s).");
			}
		}

		private void TryLightning()
		{
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.StormLightningEnabled.Value || !WeatherSystem.StormActive)
			{
				return;
			}
			float num = LightningStrike.ChancePerTick(IntervalSeconds, ModConfig.LightningMeanMinutes.Value);
			if (_rng.NextDouble() > (double)num || (!_perFire && TryReadIgniter() != 0L))
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			_stormPlayers.Clear();
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val != null && val.IsValid() && val.GetLong(ZDOVars.s_playerID, 0L) != 0L && WeatherSystem.IsStormAt(val.GetPosition()))
				{
					_stormPlayers.Add(val);
				}
			}
			if (_stormPlayers.Count == 0)
			{
				return;
			}
			Vector3 val2 = LightningStrike.StrikePoint(_stormPlayers[_rng.Next(_stormPlayers.Count)].GetPosition(), _rng.NextDouble(), _rng.NextDouble(), ModConfig.LightningRingMinMeters.Value, ModConfig.LightningRingMaxMeters.Value);
			bool value = ModConfig.StormsForceWeather.Value;
			bool? rainingWhereItLands = (value ? ((bool?)null) : TryReadRainAt(val2));
			if (!LightningStrike.SkyAllows(value, WeatherSystem.StormIsDry, rainingWhereItLands))
			{
				if (ModConfig.VerboseLogging.Value)
				{
					RagnaroksWrath.Log.LogInfo((object)($"[{Name}] bolt withheld at ({val2.x:F0}, {val2.z:F0}) — " + (value ? "the storm's forced sky is wet." : ((rainingWhereItLands == true) ? "FireFront reads rain there." : "FireFront cannot say whether it rains there."))));
				}
			}
			else if (IsNearPlayerBuilt(val2, ModConfig.LightningStandoffMeters.Value))
			{
				if (ModConfig.VerboseLogging.Value)
				{
					RagnaroksWrath.Log.LogInfo((object)$"[{Name}] bolt grounded by the homestead in {ZoneKey.FromWorldPos(val2)} — lost.");
				}
			}
			else if (TryIgniteGround(val2, ModConfig.LightningIgniteRadiusMeters.Value))
			{
				MessageFeed.ToPlayersNear(val2, 64f, "Lightning splits the sky!", MessageFeed.Placement.Centre);
				RagnaroksWrath.Log.LogInfo((object)($"[{Name}] lightning strike at ({val2.x:F0}, {val2.z:F0}) in " + $"{ZoneKey.FromWorldPos(val2)} — the fire, if any, is FireFront's now."));
			}
		}

		private bool IsNearPlayerBuilt(Vector3 strike, float standoff)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Homestead.IsNearPlayerBuilt(strike, standoff, _sectorScratch, resultWhenUncheckable: true);
		}

		private bool TryIgniteGround(Vector3 origin, float radius)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_igniteMethod == null)
				{
					Type type = _collectMethod?.DeclaringType;
					if (type == null)
					{
						return false;
					}
					_igniteMethod = type.GetMethod("IgniteGroundNear", BindingFlags.Instance | BindingFlags.Public);
					if (_igniteMethod == null)
					{
						if (!_igniteGroundAbsenceLogged)
						{
							_igniteGroundAbsenceLogged = true;
							RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] FireFront has no IgniteGroundNear surface — storm lightning dormant; scorch unaffected."));
						}
						return false;
					}
				}
				object obj = _instanceProperty?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				_igniteMethod.Invoke(obj, new object[2] { origin, radius });
				return true;
			}
			catch (Exception ex)
			{
				RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] lightning ignite failed: " + ex.Message));
				return false;
			}
		}

		private bool? TryReadRainAt(Vector3 position)
		{
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_rainMethod == null)
				{
					MethodInfo methodInfo = (_collectMethod?.DeclaringType?.Assembly.GetType("FireFront.Utils.ValheimBridge"))?.GetMethod("IsRainingAt", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Vector3) }, null);
					if (methodInfo == null || methodInfo.ReturnType != typeof(bool))
					{
						if (!_rainAbsenceLogged)
						{
							_rainAbsenceLogged = true;
							RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] FireFront has no ValheimBridge.IsRainingAt surface (0.21.0+) — with no storm sky forced, no bolt can be checked for rain, so none will fall. Scorch is unaffected. Update FireFront."));
						}
						return null;
					}
					_rainMethod = methodInfo;
				}
				_rainArgs[0] = position;
				return (bool)_rainMethod.Invoke(null, _rainArgs);
			}
			catch (Exception ex)
			{
				RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] could not read FireFront's weather, so the bolt is withheld: " + (ex.InnerException ?? ex).Message));
				return null;
			}
		}

		private long TryReadIgniter()
		{
			try
			{
				if (_igniterProperty == null)
				{
					Type type = _collectMethod?.DeclaringType;
					if (type == null)
					{
						return 0L;
					}
					_igniterProperty = type.GetProperty("CurrentFireIgniterPlayerId", BindingFlags.Instance | BindingFlags.Public);
					if (_igniterProperty == null)
					{
						if (!_igniterAbsenceLogged)
						{
							_igniterAbsenceLogged = true;
							RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] FireFront predates the igniter surface (0.17.3) — arson attribution dormant; scorch still accrues."));
						}
						return 0L;
					}
				}
				object obj = _instanceProperty?.GetValue(null);
				if (obj == null)
				{
					return 0L;
				}
				return (long)_igniterProperty.GetValue(obj);
			}
			catch (Exception)
			{
				return 0L;
			}
		}

		private bool TryCollectFirePositions()
		{
			try
			{
				if (_collectMethod == null)
				{
					Type type = ((object)Chainloader.PluginInfos["com.raveniron.firefront"].Instance).GetType().Assembly.GetType("FireFront.Fire.FireManager");
					if (type == null)
					{
						RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] FireFront.Fire.FireManager not found — FireFront's API moved."));
						return false;
					}
					_instanceProperty = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);
					_collectMethod = type.GetMethod("CollectActiveFirePositions", BindingFlags.Instance | BindingFlags.Public);
					if (_instanceProperty == null || _collectMethod == null)
					{
						_collectMethod = null;
						RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] FireManager.Instance or CollectActiveFirePositions not found — FireFront is present but older than 0.17.2. Scorch will not accrue."));
						return false;
					}
				}
				if (!_collectWithIgnitersResolved)
				{
					_collectWithIgnitersResolved = true;
					_collectWithIgnitersMethod = _collectMethod.DeclaringType.GetMethod("CollectActiveFiresWithIgniters", BindingFlags.Instance | BindingFlags.Public, null, new Type[2]
					{
						typeof(List<Vector3>),
						typeof(List<long>)
					}, null);
					if (_collectWithIgnitersMethod != null)
					{
						RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] FireFront names each fire's igniter - arson is blamed per fire."));
					}
					else if (_fireFrontVersion != null && _fireFrontVersion >= PerFireIgniterFireFrontVersion)
					{
						RagnaroksWrath.Log.LogWarning((object)($"[{Name}] FireFront {_fireFrontVersion} has no CollectActiveFiresWithIgniters" + "(List<Vector3>, List<long>) - FireFront's API moved. Arson falls back to its single igniter, blamed only where that fire has spread."));
					}
				}
				object value = _instanceProperty.GetValue(null);
				if (value == null)
				{
					return false;
				}
				_firePositions.Clear();
				_fireIgniters.Clear();
				if (_collectWithIgnitersMethod != null)
				{
					_collectWithIgnitersArgs[0] = _firePositions;
					_collectWithIgnitersArgs[1] = _fireIgniters;
					_collectWithIgnitersMethod.Invoke(value, _collectWithIgnitersArgs);
					_perFire = true;
				}
				else
				{
					_collectArgs[0] = _firePositions;
					_collectMethod.Invoke(value, _collectArgs);
					_perFire = false;
				}
				return true;
			}
			catch (Exception ex)
			{
				RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] could not read FireFront fires: " + ex.Message));
				return false;
			}
		}
	}
	public class HealthSystem : IWorldSystem
	{
		private const float SaveCadenceSeconds = 60f;

		private const float KeepaliveSeconds = 30f;

		private bool _storeLoaded;

		private float _sinceSave;

		private readonly Dictionary<long, long> _peerByPlayer = new Dictionary<long, long>(8);

		private readonly Dictionary<long, float> _lastSent = new Dictionary<long, float>(8);

		private readonly Dictionary<long, float> _lastSentAt = new Dictionary<long, float>(8);

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

		private readonly List<long> _departed = new List<long>(4);

		public string Name => "HealthSystem";

		public bool Enabled => ModConfig.EnableHealth.Value;

		public float IntervalSeconds => ModConfig.HealthIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] exposure: clean->max in {ModConfig.ExposureMinutesToMax.Value:F0}min at full plague " + $"(floor {0.15f:F2}), max->clean in {ModConfig.ExposureRecoveryMinutes.Value:F0}min; " + $"poison-resist x{ModConfig.ExposurePoisonResistMultiplier.Value:F2} accrual, " + $"rested x{ModConfig.ExposureRestedRecoveryMultiplier.Value:F1} recovery. " + "Weakens, never kills — regen multipliers only, applied client-side."));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			HealthSync.EnsureRegistered();
			if (!Persistence.IsLoaded)
			{
				return;
			}
			if (!_storeLoaded)
			{
				HealthStore.Load();
				_storeLoaded = HealthStore.IsLoaded;
			}
			if (!_storeLoaded)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			MapPeersToPlayers(instance);
			_seenThisTick.Clear();
			float value = ModConfig.ExposureMinutesToMax.Value;
			float value2 = ModConfig.ExposureRecoveryMinutes.Value;
			float value3 = ModConfig.ExposurePoisonResistMultiplier.Value;
			float value4 = ModConfig.ExposureRestedRecoveryMultiplier.Value;
			int num = 0;
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				long num2 = val.GetLong(ZDOVars.s_playerID, 0L);
				if (num2 != 0L)
				{
					_seenThisTick.Add(num2);
					ZoneKey zone = ZoneKey.FromWorldPos(val.GetPosition());
					float plague = Persistence.Get(zone).Plague;
					int num3 = RemedyBitsFor(num2, realtimeSinceStartup);
					float num4 = HealthStore.Get(num2);
					float num5 = (RivalrySystem.IsDominantCarer(zone, num2) ? (1f + ModConfig.MercySicknessBonus.Value) : 1f);
					num5 *= RelicSystem.ExposureDecayMultAt(zone);
					float minutesToMax = value * RelicSystem.ExposureMinutesMultAt(zone);
					float num6 = ((plague >= 0.15f) ? ExposureMath.Accrue(num4, plague, minutesToMax, (num3 & 1) != 0, value3, deltaSeconds) : ExposureMath.Decay(num4, value2, (num3 & 2) != 0, value4, deltaSeconds, num5));
					if (num6 != num4)
					{
						HealthStore.Set(num2, num6);
					}
					if (num6 > 0f)
					{
						num++;
					}
					MaybeSync(num2, num6, realtimeSinceStartup);
				}
			}
			PruneDeparted();
			_sinceSave += deltaSeconds;
			if (_sinceSave >= 60f)
			{
				_sinceSave = 0f;
				HealthStore.SaveIfDirty();
			}
			if (ModConfig.VerboseLogging.Value && num > 0)
			{
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] {num} exposed player(s) of {_seenThisTick.Count} online.");
			}
		}

		private void MaybeSync(long playerId, float exposure, float now)
		{
			if (_peerByPlayer.TryGetValue(playerId, out var value))
			{
				_lastSent.TryGetValue(playerId, out var value2);
				_lastSentAt.TryGetValue(playerId, out var value3);
				if (ExposureMath.QuantizedDiffer(value2, exposure) || (exposure > 0f && now - value3 >= 30f))
				{
					HealthSync.SendExposure(value, playerId, exposure);
					_lastSent[playerId] = exposure;
					_lastSentAt[playerId] = now;
				}
			}
		}

		private void MapPeersToPlayers(ZNet znet)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			_peerByPlayer.Clear();
			List<ZNetPeer> peers;
			try
			{
				peers = znet.GetPeers();
			}
			catch
			{
				return;
			}
			if (peers == null)
			{
				return;
			}
			ZDOMan instance = ZDOMan.instance;
			if (instance == null)
			{
				return;
			}
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val == null || !val.IsReady())
				{
					continue;
				}
				ZDO zDO = instance.GetZDO(val.m_characterID);
				if (zDO != null)
				{
					long num = zDO.GetLong(ZDOVars.s_playerID, 0L);
					if (num != 0L)
					{
						_peerByPlayer[num] = val.m_uid;
					}
				}
			}
		}

		private int RemedyBitsFor(long playerId, float now)
		{
			if (_peerByPlayer.TryGetValue(playerId, out var value))
			{
				return HealthSync.RemedyBitsFor(value, now);
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerID() == playerId)
			{
				return HealthSync.ComputeLocalRemedyBits(localPlayer);
			}
			return 0;
		}

		private void PruneDeparted()
		{
			_departed.Clear();
			foreach (KeyValuePair<long, float> item in _lastSent)
			{
				if (!_seenThisTick.Contains(item.Key))
				{
					_departed.Add(item.Key);
				}
			}
			for (int i = 0; i < _departed.Count; i++)
			{
				_lastSent.Remove(_departed[i]);
				_lastSentAt.Remove(_departed[i]);
			}
		}
	}
	public class PlagueSystem : IWorldSystem
	{
		private readonly Random _rng = new Random();

		private readonly List<KeyValuePair<ZoneKey, float>> _plagued = new List<KeyValuePair<ZoneKey, float>>(32);

		private readonly HashSet<ZoneKey> _infected = new HashSet<ZoneKey>();

		private readonly List<ZoneKey> _targets = new List<ZoneKey>(16);

		public string Name => "PlagueSystem";

		public bool Enabled => ModConfig.EnablePlague.Value;

		public float IntervalSeconds => ModConfig.PlagueSpreadIntervalSeconds.Value;

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)($"[{Name}] spread every {IntervalSeconds:F0}s at {ModConfig.PlagueSpreadChance.Value:P0} " + $"per frontier zone (threshold {ModConfig.PlagueSpreadThreshold.Value:F2}, " + $"seed {ModConfig.PlagueSeedAmount.Value:F2}). Growth and cure live in BiomeDrift. " + (ModConfig.PlagueGenesisEnabled.Value ? ($"Genesis: outbreaks take root organically, ~{ModConfig.PlagueGenesisMeanHours.Value:0.#}h " + "mean on clean ground, faster on blighted, storm-carried.") : "Genesis OFF: outbreaks start only by admin hand.")));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			if (!Persistence.IsLoaded)
			{
				return;
			}
			_plagued.Clear();
			_infected.Clear();
			foreach (KeyValuePair<ZoneKey, ZoneState> item in Persistence.All())
			{
				if (!(item.Value.Plague <= 0f))
				{
					_plagued.Add(new KeyValuePair<ZoneKey, float>(item.Key, item.Value.Plague));
					_infected.Add(item.Key);
				}
			}
			TryGenesis();
			if (_plagued.Count == 0)
			{
				return;
			}
			_targets.Clear();
			PlagueSpread.CollectSpreadTargets(_plagued, ModConfig.PlagueSpreadThreshold.Value, _infected, _targets);
			if (_targets.Count == 0)
			{
				return;
			}
			float value = ModConfig.PlagueSpreadChance.Value;
			float value2 = ModConfig.PlagueSeedAmount.Value;
			int value3 = ModConfig.PlagueMaxSpreadsPerTick.Value;
			int num = 0;
			for (int i = 0; i < _targets.Count; i++)
			{
				if (num >= value3)
				{
					break;
				}
				ZoneKey zone = _targets[i];
				float num2 = value * WeatherSystem.PlagueSpreadMultiplierAt(zone.ToWorldPos());
				if (!(_rng.NextDouble() > (double)num2))
				{
					ZoneState state = Persistence.Get(zone);
					state.Plague = value2;
					Persistence.Set(zone, state);
					num++;
				}
			}
			if (num > 0 && ModConfig.VerboseLogging.Value)
			{
				RagnaroksWrath.Log.LogInfo((object)($"[{Name}] seeded {num} zone(s) from {_plagued.Count} infected " + $"({_targets.Count} frontier candidate(s))."));
			}
		}

		private void TryGenesis()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.PlagueGenesisEnabled.Value)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null || allCharacterZDOS.Count == 0)
			{
				return;
			}
			ZDO val = allCharacterZDOS[_rng.Next(allCharacterZDOS.Count)];
			if (val == null || !val.IsValid() || val.GetLong(ZDOVars.s_playerID, 0L) == 0L)
			{
				return;
			}
			int num = Math.Max(0, ModConfig.BiomeContactRadiusZones.Value);
			ZoneKey zoneKey = ZoneKey.FromWorldPos(val.GetPosition());
			ZoneKey zoneKey2 = new ZoneKey(zoneKey.X + _rng.Next(-num, num + 1), zoneKey.Y + _rng.Next(-num, num + 1));
			ZoneState state = Persistence.Get(zoneKey2);
			if (!(state.Plague > 0f))
			{
				float num2 = PlagueGenesis.Weight(state.Corruption, state.Scorch);
				float num3 = WeatherSystem.PlagueSpreadMultiplierAt(zoneKey2.ToWorldPos());
				float num4 = PlagueGenesis.ChancePerTick(IntervalSeconds, ModConfig.PlagueGenesisMeanHours.Value) * num2 * num3;
				if (!(_rng.NextDouble() > (double)num4))
				{
					state.Plague = ModConfig.PlagueSeedAmount.Value;
					Persistence.Set(zoneKey2, state);
					ZoneClock.MarkContact(zoneKey2);
					RagnaroksWrath.Log.LogInfo((object)($"[{Name}] sickness takes root in {zoneKey2} " + $"(ground weight {num2:0.##}, storm x{num3:0.##})."));
				}
			}
		}
	}
	public sealed class RelicSystem : IWorldSystem
	{
		private const float SaveCadenceSeconds = 60f;

		private const float RebroadcastSeconds = 60f;

		private bool _ledgerLoaded;

		private float _sinceSave;

		private float _sinceBroadcast;

		private WorldCondition _lastCondition = WorldCondition.Stable;

		private bool _conditionSeen;

		private readonly HashSet<ZoneKey> _storyTold = new HashSet<ZoneKey>();

		private readonly Dictionary<ZoneKey, long> _presentPeer = new Dictionary<ZoneKey, long>(64);

		private readonly HashSet<ZoneKey> _occupied = new HashSet<ZoneKey>();

		private static readonly List<KeyValuePair<ZoneKey, bool>> _warResults = new List<KeyValuePair<ZoneKey, bool>>(4);

		private readonly List<ZoneKey> _scratch = new List<ZoneKey>(16);

		private readonly List<KeyValuePair<ZoneKey, RelicLedger.Peaks>> _peaksSnapshot = new List<KeyValuePair<ZoneKey, RelicLedger.Peaks>>(16);

		public string Name => "RelicSystem";

		public bool Enabled => ModConfig.EnableRelic.Value;

		public float IntervalSeconds => ModConfig.RelicIntervalSeconds.Value;

		internal static void ResetWorldState()
		{
			_warResults.Clear();
		}

		public static void NotifyWarResolved(ZoneKey zone, bool wildWon)
		{
			if (ModConfig.EnableRelic.Value)
			{
				_warResults.Add(new KeyValuePair<ZoneKey, bool>(zone, wildWon));
			}
		}

		public static float RecoveryMultiplierAt(ZoneKey zone)
		{
			if (!ModConfig.EnableRelic.Value || !RelicLedger.IsLoaded)
			{
				return 1f;
			}
			RelicLedger.Relic relic = RelicLedger.RelicAt(zone);
			return RelicMath.RecoveryMultiplier(relic.Type, relic.Cursed, ModConfig.RelicBlessedRecoveryMult.Value, ModConfig.RelicCursedRecoveryMult.Value);
		}

		public static float ExposureDecayMultAt(ZoneKey zone)
		{
			if (!ModConfig.EnableRelic.Value || !RelicLedger.IsLoaded)
			{
				return 1f;
			}
			RelicLedger.Relic relic = RelicLedger.RelicAt(zone);
			return RelicMath.ExposureDecayMultiplier(relic.Type, relic.Cursed, ModConfig.RelicBlessedExposureDrainMult.Value);
		}

		public static float ExposureMinutesMultAt(ZoneKey zone)
		{
			if (!ModConfig.EnableRelic.Value || !RelicLedger.IsLoaded)
			{
				return 1f;
			}
			RelicLedger.Relic relic = RelicLedger.RelicAt(zone);
			return RelicMath.ExposureMinutesMultiplier(relic.Type, relic.Cursed, ModConfig.RelicCursedExposureAccrualMult.Value);
		}

		public void Initialise()
		{
			RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] consecrated places: fire healed / plague cured -> blessed, war resolved -> by the victor, Stricken recovered -> the era stone. Auras: " + $"recovery x{ModConfig.RelicBlessedRecoveryMult.Value:0.##} blessed / " + $"x{ModConfig.RelicCursedRecoveryMult.Value:0.##} cursed; exposure drain " + $"x{ModConfig.RelicBlessedExposureDrainMult.Value:0.##} blessed, accrual " + $"x{ModConfig.RelicCursedExposureAccrualMult.Value:0.##} cursed."));
		}

		public void Tick(float deltaSeconds)
		{
			if (!Persistence.IsLoaded)
			{
				_warResults.Clear();
				return;
			}
			if (!_ledgerLoaded)
			{
				RelicLedger.Load();
				_ledgerLoaded = RelicLedger.IsLoaded;
			}
			if (!_ledgerLoaded)
			{
				_warResults.Clear();
				return;
			}
			RelicSync.EnsureRegistered();
			CollectPresence();
			UpdatePeaks();
			ConsecrateCompletions();
			DrainWarResults();
			WatchEra();
			RetryPending();
			RetryPlacement();
			TellStories();
			_sinceBroadcast += deltaSeconds;
			if (_sinceBroadcast >= 60f)
			{
				_sinceBroadcast = 0f;
				RelicSync.BroadcastAll();
			}
			_sinceSave += deltaSeconds;
			if (_sinceSave >= 60f)
			{
				_sinceSave = 0f;
				RelicLedger.SaveIfDirty();
			}
		}

		public static void OnRelicBroken(ZoneKey zone, long vandalPlayerId)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if (RelicLedger.IsLoaded && RelicLedger.RelicAt(zone).Standing)
			{
				RelicLedger.RemoveRelic(zone);
				RelicSync.BroadcastRemoval(zone);
				if (vandalPlayerId != 0L && ModConfig.EnableRivalry.Value && RivalryLedger.IsLoaded)
				{
					RivalryLedger.AddHarm(zone, vandalPlayerId, ModConfig.RelicVandalHarm.Value);
				}
				MessageFeed.ToPlayersNear(zone.ToWorldPos(), 64f, "The stone lies broken. The land forgets.", MessageFeed.Placement.Centre);
				RagnaroksWrath.Log.LogInfo((object)($"[RelicSystem] relic in {zone} desecrated" + ((vandalPlayerId != 0L) ? $" by {vandalPlayerId} (harm booked)." : " (no attacker identified).")));
			}
		}

		private void CollectPresence()
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			_presentPeer.Clear();
			_occupied.Clear();
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			int num = Math.Max(0, ModConfig.BiomeContactRadiusZones.Value);
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val == null || !val.IsValid() || val.GetLong(ZDOVars.s_playerID, 0L) == 0L)
				{
					continue;
				}
				ZoneKey item = ZoneKey.FromWorldPos(val.GetPosition());
				_occupied.Add(item);
				long owner = val.GetOwner();
				for (int j = -num; j <= num; j++)
				{
					for (int k = -num; k <= num; k++)
					{
						ZoneKey key = new ZoneKey(item.X + j, item.Y + k);
						if (owner != 0L && !_presentPeer.ContainsKey(key))
						{
							_presentPeer[key] = owner;
						}
					}
				}
			}
		}

		private void UpdatePeaks()
		{
			float value = ModConfig.FireRelicPeakThreshold.Value;
			float value2 = ModConfig.PlagueRelicPeakThreshold.Value;
			foreach (KeyValuePair<ZoneKey, ZoneState> item in Persistence.All())
			{
				if (!RelicLedger.RelicAt(item.Key).Standing)
				{
					RelicLedger.Peaks peaks = RelicLedger.PeaksFor(item.Key);
					float num = RelicMath.TrackPeak(item.Value.Scorch, value, peaks.Scorch);
					float num2 = RelicMath.TrackPeak(item.Value.Plague, value2, peaks.Plague);
					if (num != peaks.Scorch || num2 != peaks.Plague)
					{
						RelicLedger.SetPeaks(item.Key, new RelicLedger.Peaks
						{
							Scorch = num,
							Plague = num2
						});
					}
				}
			}
		}

		private void ConsecrateCompletions()
		{
			_peaksSnapshot.Clear();
			foreach (KeyValuePair<ZoneKey, RelicLedger.Peaks> item in RelicLedger.AllPeaks())
			{
				_peaksSnapshot.Add(item);
			}
			for (int i = 0; i < _peaksSnapshot.Count; i++)
			{
				ZoneKey key = _peaksSnapshot[i].Key;
				RelicLedger.Peaks value = _peaksSnapshot[i].Value;
				ZoneState zoneState = Persistence.Get(key);
				if (RelicMath.ShouldConsecrate(value.Scorch, zoneState.Scorch))
				{
					TryConsecrate(key, 0, cursed: false);
				}
				else if (RelicMath.ShouldConsecrate(value.Plague, zoneState.Plague))
				{
					TryConsecrate(key, 1, cursed: false);
				}
			}
		}

		private void DrainWarResults()
		{
			for (int i = 0; i < _warResults.Count; i++)
			{
				TryConsecrate(_warResults[i].Key, 2, !_warResults[i].Value);
			}
			_warResults.Clear();
		}

		private void WatchEra()
		{
			WorldCondition condition = WorldStateSystem.Condition;
			if (!_conditionSeen)
			{
				_conditionSeen = true;
				_lastCondition = condition;
			}
			else
			{
				if (condition == _lastCondition)
				{
					return;
				}
				if (condition == WorldCondition.Stricken && !RelicLedger.EraArmed)
				{
					int num = 0;
					foreach (KeyValuePair<ZoneKey, ZoneState> item in Persistence.All())
					{
						float num2 = RivalryMath.ZoneDamage(item.Value);
						if (num2 > 0f)
						{
							RelicLedger.SetEraSnapshot(item.Key, num2);
							num++;
						}
					}
					RagnaroksWrath.Log.LogInfo((object)$"[{Name}] the land is stricken — era armed over {num} zone(s).");
				}
				else if (RelicLedger.EraArmed && (condition == WorldCondition.Stable || condition == WorldCondition.Flourishing))
				{
					ZoneKey zoneKey = default(ZoneKey);
					float num3 = 0f;
					foreach (KeyValuePair<ZoneKey, float> item2 in RelicLedger.EraSnapshot())
					{
						if (!RelicLedger.RelicAt(item2.Key).Standing)
						{
							float num4 = item2.Value - RivalryMath.ZoneDamage(Persistence.Get(item2.Key));
							if (num4 > num3)
							{
								num3 = num4;
								zoneKey = item2.Key;
							}
						}
					}
					RelicLedger.ClearEra();
					if (num3 > 0f)
					{
						RagnaroksWrath.Log.LogInfo((object)$"[{Name}] the era turns — {zoneKey} healed {num3:0.###}, the most of any ground.");
						TryConsecrate(zoneKey, 3, cursed: false);
					}
				}
				_lastCondition = condition;
			}
		}

		private void RetryPending()
		{
			if (RelicLedger.PendingCount == 0)
			{
				return;
			}
			_scratch.Clear();
			foreach (KeyValuePair<ZoneKey, RelicLedger.Relic> item in RelicLedger.AllPending())
			{
				if (_presentPeer.ContainsKey(item.Key))
				{
					_scratch.Add(item.Key);
				}
			}
			for (int i = 0; i < _scratch.Count; i++)
			{
				RelicLedger.Relic relic = default(RelicLedger.Relic);
				foreach (KeyValuePair<ZoneKey, RelicLedger.Relic> item2 in RelicLedger.AllPending())
				{
					if (item2.Key == _scratch[i])
					{
						relic = item2.Value;
						break;
					}
				}
				RelicLedger.RemovePending(_scratch[i]);
				Commit(_scratch[i], relic);
			}
		}

		private void RetryPlacement()
		{
			foreach (KeyValuePair<ZoneKey, RelicLedger.Relic> item in RelicLedger.AllRelics())
			{
				if (!item.Value.Placed && _presentPeer.TryGetValue(item.Key, out var value))
				{
					RelicSync.RequestPlacement(value, item.Key, item.Value);
				}
			}
		}

		private void TellStories()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			foreach (KeyValuePair<ZoneKey, RelicLedger.Relic> item in RelicLedger.AllRelics())
			{
				if (!_storyTold.Contains(item.Key) && _occupied.Contains(item.Key))
				{
					_storyTold.Add(item.Key);
					MessageFeed.ToPlayersNear(item.Key.ToWorldPos(), 64f, RelicMath.Story(item.Value.Type, item.Value.Cursed));
				}
			}
		}

		private void TryConsecrate(ZoneKey zone, int type, bool cursed)
		{
			if (!RelicLedger.RelicAt(zone).Standing)
			{
				RelicLedger.Relic relic = new RelicLedger.Relic
				{
					Type = type,
					Cursed = cursed,
					Day = SeasonSystem.CurrentDayOrZero()
				};
				if (!_presentPeer.ContainsKey(zone))
				{
					RelicLedger.AddPending(zone, relic);
					RagnaroksWrath.Log.LogInfo((object)$"[{Name}] consecration of {zone} queued — nobody present to raise the stone.");
				}
				else
				{
					Commit(zone, relic);
				}
			}
		}

		private void Commit(ZoneKey zone, RelicLedger.Relic relic)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			if (relic.Standing)
			{
				RelicLedger.SetRelic(zone, relic);
				RelicSync.BroadcastRelic(zone, relic);
				if (_presentPeer.TryGetValue(zone, out var value))
				{
					RelicSync.RequestPlacement(value, zone, relic);
				}
				_storyTold.Remove(zone);
				MessageFeed.ToPlayersNear(zone.ToWorldPos(), 64f, relic.Cursed ? "The gods curse this ground. Its stone remembers." : "The gods bless this ground. Its stone remembers.", MessageFeed.Placement.Centre);
				RagnaroksWrath.Log.LogInfo((object)($"[{Name}] consecrated {zone}: type {relic.Type}, " + string.Format("{0}, day {1}.", relic.Cursed ? "cursed" : "blessed", relic.Day)));
			}
		}
	}
	public class RivalrySystem : IWorldSystem
	{
		private const float SaveCadenceSeconds = 60f;

		private bool _ledgerLoaded;

		private float _sinceSave;

		private string[] _cropPrefabs = Array.Empty<string>();

		private int _prefabCursor;

		private readonly List<ZDO> _found = new List<ZDO>(128);

		private int _sweepIndex;

		private long _rotationMaxPlantTime;

		private readonly Dictionary<ZoneKey, float> _lastDamage = new Dictionary<ZoneKey, float>(64);

		private readonly Dictionary<ZoneKey, List<long>> _present = new Dictionary<ZoneKey, List<long>>(32);

		private readonly List<ZoneKey> _stale = new List<ZoneKey>(16);

		private static readonly Dictionary<ZoneKey, RivalryContest.Holder> _careHolders = new Dictionary<ZoneKey, RivalryContest.Holder>(32);

		private static readonly Dictionary<ZoneKey, RivalryContest.Holder> _harmHolders = new Dictionary<ZoneKey, RivalryContest.Holder>(32);

		private readonly Dictionary<ZoneKey, Dictionary<long, float>> _careValues = new Dictionary<ZoneKey, Dictionary<long, float>>(32);

		private readonly Dictionary<ZoneKey, Dictionary<long, float>> _harmValues = new Dictionary<ZoneKey, Dictionary<long, float>>(32);

		private readonly List<RivalryContest.Flip> _flips = new List<RivalryContest.Flip>(4);

		private readonly Dictionary<long, string> _names = new Dictionary<long, string>(8);

		private static readonly Dictionary<ZoneKey, float> _warIntensity = new Dictionary<ZoneKey, float>(8);

		private readonly Dictionary<ZoneKey, float> _warScratch = new Dictionary<ZoneKey, float>(8);

		private readonly List<ZoneKey> _warEnded = new List<ZoneKey>(4);

		public string Name => "RivalrySystem";

		public bool Enabled => ModConfig.EnableRivalry.Value;

		public float IntervalSeconds => ModConfig.RivalryIntervalSeconds.Value;

		public static int ContestedZoneCount => _warIntensity.Count;

		public static bool IsDominantCarer(ZoneKey zone, long playerId)
		{
			if (playerId != 0L && _careHolders.TryGetValue(zone, out var value))
			{
				return value.Player == playerId;
			}
			return false;
		}

		public static int CareZonesHeld(long playerId)
		{
			return RivalryContest.ZonesHeld(_careHolders, playerId);
		}

		public static int HarmZonesHeld(long playerId)
		{
			return RivalryContest.ZonesHeld(_harmHolders, playerId);
		}

		public static float WarIntensityAt(ZoneKey zone)
		{
			if (!_warIntensity.TryGetValue(zone, out var value))
			{
				return 0f;
			}
			return value;
		}

		internal static void ResetWorldState()
		{
			_careHolders.Clear();
			_harmHolders.Clear();
			_warIntensity.Clear();
		}

		public void Initialise()
		{
			_cropPrefabs = (ModConfig.FarmingCropPrefabs.Value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < _cropPrefabs.Length; i++)
			{
				_cropPrefabs[i] = _cropPrefabs[i].Trim();
			}
			RagnaroksWrath.Log.LogInfo((object)("[" + Name + "] the influence ledger, all phases. Writers armed: tending " + $"({ModConfig.TendingCarePerPlant.Value:F3} care/plant, watermarked), healing presence " + $"({ModConfig.CarePerHealedPoint.Value:F2} care/healed point, split by ring), and arson " + $"({ModConfig.ArsonHarmPerScorchPoint.Value:F2} harm/scorch point) — see FireSystem's " + "own line for whether the installed FireFront exposes the igniter it needs. " + $"Both columns fade, half-life {ModConfig.RivalryHalfLifeHours.Value:F0}h."));
		}

		public void Tick(float deltaSeconds)
		{
			if (!Persistence.IsLoaded)
			{
				return;
			}
			if (!_ledgerLoaded)
			{
				RivalryLedger.Load();
				_ledgerLoaded = RivalryLedger.IsLoaded;
			}
			if (_ledgerLoaded)
			{
				RivalryLedger.DecayAll(RivalryMath.DecayFactor(ModConfig.RivalryHalfLifeHours.Value, deltaSeconds));
				ObserveHealing();
				SweepTending();
				UpdateContest();
				UpdateWar();
				_sinceSave += deltaSeconds;
				if (_sinceSave >= 60f)
				{
					_sinceSave = 0f;
					RivalryLedger.SaveIfDirty();
				}
			}
		}

		private void ObserveHealing()
		{
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZDO> allCharacterZDOS;
			try
			{
				allCharacterZDOS = instance.GetAllCharacterZDOS();
			}
			catch
			{
				return;
			}
			if (allCharacterZDOS == null)
			{
				return;
			}
			foreach (List<long> value5 in _present.Values)
			{
				value5.Clear();
			}
			int num = Math.Max(0, ModConfig.BiomeContactRadiusZones.Value);
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				long num2 = val.GetLong(ZDOVars.s_playerID, 0L);
				if (num2 == 0L)
				{
					continue;
				}
				string text = val.GetString(ZDOVars.s_playerName, "");
				if (!string.IsNullOrEmpty(text) && text != "Stranger" && text != "...")
				{
					_names[num2] = text;
				}
				ZoneKey zoneKey = ZoneKey.FromWorldPos(val.GetPosition());
				for (int j = -num; j <= num; j++)
				{
					for (int k = -num; k <= num; k++)
					{
						ZoneKey key = new ZoneKey(zoneKey.X + j, zoneKey.Y + k);
						if (!_present.TryGetValue(key, out var value))
						{
							value = (_present[key] = new List<long>(4));
						}
						value.Add(num2);
					}
				}
			}
			float value2 = ModConfig.CarePerHealedPoint.Value;
			foreach (KeyValuePair<ZoneKey, List<long>> item in _present)
			{
				if (item.Value.Count == 0)
				{
					continue;
				}
				float num3 = RivalryMath.ZoneDamage(Persistence.Get(item.Key));
				if (_lastDamage.TryGetValue(item.Key, out var value3))
				{
					float num4 = RivalryMath.SplitAmong(RivalryMath.CareFromHealing(value3, num3, value2), item.Value.Count);
					if (num4 > 0f)
					{
						for (int l = 0; l < item.Value.Count; l++)
						{
							RivalryLedger.AddCare(item.Key, item.Value[l], num4);
						}
					}
				}
				_lastDamage[item.Key] = num3;
			}
			_stale.Clear();
			foreach (KeyValuePair<ZoneKey, float> item2 in _lastDamage)
			{
				if (!_present.TryGetValue(item2.Key, out var value4) || value4.Count == 0)
				{
					_stale.Add(item2.Key);
				}
			}
			for (int m = 0; m < _stale.Count; m++)
			{
				_lastDamage.Remove(_stale[m]);
			}
		}

		private void SweepTending()
		{
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			if (_cropPrefabs.Length == 0 || ModConfig.TendingCarePerPlant.Value <= 0f)
			{
				return;
			}
			ZDOMan instance = ZDOMan.instance;
			if (instance == null)
			{
				return;
			}
			string text = _cropPrefabs[_prefabCursor];
			try
			{
				bool flag = false;
				while (!flag)
				{
					flag = instance.GetAllZDOsWithPrefabIterative(text, _found, ref _sweepIndex);
				}
			}
			catch (Exception ex)
			{
				RagnaroksWrath.Log.LogWarning((object)("[" + Name + "] tending sweep failed on '" + text + "': " + ex.Message));
				_found.Clear();
				_sweepIndex = 0;
				return;
			}
			long plantWatermark = RivalryLedger.PlantWatermark;
			int num = 0;
			for (int i = 0; i < _found.Count; i++)
			{
				ZDO val = _found[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				long num2 = val.GetLong(ZDOVars.s_plantTime, 0L);
				if (num2 > _rotationMaxPlantTime)
				{
					_rotationMaxPlantTime = num2;
				}
				if (RivalryMath.IsNewPlant(num2, plantWatermark))
				{
					long num3 = val.GetLong(ZDOVars.s_creator, 0L);
					if (num3 != 0L)
					{
						RivalryLedger.AddCare(ZoneKey.FromWorldPos(val.GetPosition()), num3, ModConfig.TendingCarePerPlant.Value);
						num++;
					}
				}
			}
			if (ModConfig.VerboseLogging.Value && num > 0)
			{
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] '{text}': {num} new plant(s) booked.");
			}
			_found.Clear();
			_sweepIndex = 0;
			_prefabCursor++;
			if (_prefabCursor >= _cropPrefabs.Length)
			{
				_prefabCursor = 0;
				RivalryLedger.PlantWatermark = _rotationMaxPlantTime;
				_rotationMaxPlantTime = 0L;
			}
		}

		private void UpdateContest()
		{
			foreach (Dictionary<long, float> value4 in _careValues.Values)
			{
				value4.Clear();
			}
			foreach (Dictionary<long, float> value5 in _harmValues.Values)
			{
				value5.Clear();
			}
			foreach (KeyValuePair<RivalryLedger.Key, RivalryLedger.Row> item in RivalryLedger.All())
			{
				if (item.Value.Care > 0f)
				{
					if (!_careValues.TryGetValue(item.Key.Zone, out var value))
					{
						value = (_careValues[item.Key.Zone] = new Dictionary<long, float>(4));
					}
					value[item.Key.Player] = item.Value.Care;
				}
				if (item.Value.Harm > 0f)
				{
					if (!_harmValues.TryGetValue(item.Key.Zone, out var value2))
					{
						value2 = (_harmValues[item.Key.Zone] = new Dictionary<long, float>(4));
					}
					value2[item.Key.Player] = item.Value.Harm;
				}
			}
			PruneEmpty(_careValues);
			PruneEmpty(_harmValues);
			float value3 = ModConfig.ContestHysteresis.Value;
			_flips.Clear();
			RivalryContest.Update(_careValues, _careHolders, ModConfig.CareDominanceFloor.Value, value3, _flips);
			Announce(_flips, "{0} now tends this ground more than {1}.");
			_flips.Clear();
			RivalryContest.Update(_harmValues, _harmHolders, ModConfig.HarmDominanceFloor.Value, value3, _flips);
			Announce(_flips, "This ground now fears {0} more than {1}.");
		}

		private static void PruneEmpty(Dictionary<ZoneKey, Dictionary<long, float>> values)
		{
			List<ZoneKey> list = null;
			foreach (KeyValuePair<ZoneKey, Dictionary<long, float>> value in values)
			{
				if (value.Value.Count == 0)
				{
					List<ZoneKey> obj = list ?? new List<ZoneKey>(4);
					list = obj;
					obj.Add(value.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					values.Remove(list[i]);
				}
			}
		}

		private void UpdateWar()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			float value = ModConfig.ContestBlightThreshold.Value;
			float value2 = ModConfig.ContestCareThreshold.Value;
			float value3 = ModConfig.StormContestMultiplier.Value;
			_warScratch.Clear();
			foreach (KeyValuePair<ZoneKey, Dictionary<long, float>> careValue in _careValues)
			{
				float num = 0f;
				foreach (float value4 in careValue.Value.Values)
				{
					num += value4;
				}
				if (RivalryContest.IsContested(RivalryContest.BlightOf(Persistence.Get(careValue.Key)), num, value, value2))
				{
					bool inStorm = WeatherSystem.IsStormAt(careValue.Key.ToWorldPos());
					_warScratch[careValue.Key] = RivalryContest.Intensity(contested: true, inStorm, value3);
				}
			}
			_warEnded.Clear();
			foreach (KeyValuePair<ZoneKey, float> item in _warIntensity)
			{
				if (!_warScratch.ContainsKey(item.Key))
				{
					_warEnded.Add(item.Key);
				}
			}
			for (int i = 0; i < _warEnded.Count; i++)
			{
				ZoneKey zoneKey = _warEnded[i];
				RivalryContest.WarWinner warWinner = RivalryContest.Winner(RivalryContest.BlightOf(Persistence.Get(zoneKey)), value);
				if (ModConfig.AnnounceContests.Value)
				{
					MessageFeed.ToPlayersNear(zoneKey.ToWorldPos(), 64f, (warWinner == RivalryContest.WarWinner.Wild) ? "The wild has taken this ground back." : "The blight has claimed this ground.", MessageFeed.Placement.Centre);
				}
				RagnaroksWrath.Log.LogInfo((object)$"[{Name}] war in {zoneKey} resolved: {warWinner}.");
				RelicSystem.NotifyWarResolved(zoneKey, warWinner == RivalryContest.WarWinner.Wild);
			}