Decompiled source of ScatteredVikings v1.0.0

IronLabs.ScatteredVikings.dll

Decompiled a day 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.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("IronLabs.ScatteredVikings")]
[assembly: AssemblyDescription("Gives every Viking a personal Meadows spawn.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("IronLabs")]
[assembly: AssemblyProduct("IronLabs.ScatteredVikings")]
[assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("3EF3F0B9-0D06-4746-AAE4-B5FAFD1F0F95")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace IronLabs.ScatteredVikings;

[HarmonyPatch(typeof(Game), "FindSpawnPoint")]
internal static class GameFindSpawnPointPatch
{
	private static bool Prefix(Game __instance, ref bool __result, ref Vector3 point, ref bool usedLogoutPoint, bool ___m_respawnAfterDeath)
	{
		//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_0028: Unknown result type (might be due to invalid IL or missing references)
		PlayerProfile playerProfile = __instance.GetPlayerProfile();
		if (ShouldUseVanillaSpawn(playerProfile, ___m_respawnAfterDeath))
		{
			return true;
		}
		point = SpawnPointSelector.Get(playerProfile);
		usedLogoutPoint = false;
		ZNet.instance.SetReferencePosition(point);
		__result = TryPrepareSpawnPoint(ref point);
		return false;
	}

	private static bool ShouldUseVanillaSpawn(PlayerProfile profile, bool respawnAfterDeath)
	{
		if (!profile.HaveCustomSpawnPoint())
		{
			if (!respawnAfterDeath)
			{
				return profile.HaveLogoutPoint();
			}
			return false;
		}
		return true;
	}

	private static bool TryPrepareSpawnPoint(ref Vector3 point)
	{
		//IL_0006: 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_002e: Unknown result type (might be due to invalid IL or missing references)
		if (!ZNetScene.instance.IsAreaReady(point))
		{
			return false;
		}
		float num = default(float);
		if (!ZoneSystem.instance.GetGroundHeight(point, ref num))
		{
			ModLog.LogWarning($"No ground was found at scattered spawn {point}; waiting for terrain.");
			return false;
		}
		point.y = num + 0.25f;
		return true;
	}
}
internal static class ModLog
{
	private static ManualLogSource _logger;

	private static string _pluginName;

	internal static void Initialize(ManualLogSource logger, string pluginName)
	{
		_logger = logger;
		_pluginName = pluginName;
	}

	internal static void Clear()
	{
		_logger = null;
		_pluginName = null;
	}

	internal static void LogDebug(object message)
	{
		Write((LogLevel)32, message);
	}

	internal static void LogInfo(object message)
	{
		Write((LogLevel)16, message);
	}

	internal static void LogWarning(object message)
	{
		Write((LogLevel)4, message);
	}

	private static void Write(LogLevel level, object message)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		ManualLogSource logger = _logger;
		if (logger != null)
		{
			logger.Log(level, message);
		}
	}
}
[BepInPlugin("IronLabs.ScatteredVikings", "IronLabs.ScatteredVikings", "1.0.0")]
public sealed class ScatteredVikingsPlugin : BaseUnityPlugin
{
	private const string PluginGuid = "IronLabs.ScatteredVikings";

	private const string PluginName = "IronLabs.ScatteredVikings";

	private const string PluginVersion = "1.0.0";

	private readonly Harmony _harmony = new Harmony("IronLabs.ScatteredVikings");

	private static bool _patchesApplied;

	private void Awake()
	{
		ModLog.Initialize(((BaseUnityPlugin)this).Logger, "IronLabs.ScatteredVikings");
		PatchOwnNamespace();
		ModLog.LogInfo("IronLabs.ScatteredVikings 1.0.0 is loaded.");
	}

	private void PatchOwnNamespace()
	{
		if (_patchesApplied)
		{
			ModLog.LogDebug("Harmony patches are already active; skipping registration.");
			return;
		}
		string text = typeof(ScatteredVikingsPlugin).Namespace;
		Type[] types = Assembly.GetExecutingAssembly().GetTypes();
		foreach (Type type in types)
		{
			if (type.Namespace == text)
			{
				_harmony.CreateClassProcessor(type).Patch();
			}
		}
		_patchesApplied = true;
		ModLog.LogDebug("Harmony patches were applied for the plugin namespace.");
	}

	private void OnDestroy()
	{
		SpawnPointSelector.Clear();
		if (_patchesApplied)
		{
			_harmony.UnpatchSelf();
			_patchesApplied = false;
		}
		ModLog.Clear();
	}
}
internal static class SpawnPointSelector
{
	private const float MaximumRadius = 1000f;

	private const float MinimumHeightAboveWater = 2f;

	private const int MaximumAttempts = 10000;

	internal static Vector3 Get(PlayerProfile profile)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		long worldUID = ZNet.instance.GetWorldUID();
		if (SpawnPointStore.TryGet(profile.GetPlayerID(), worldUID, out var point))
		{
			return point;
		}
		point = FindSpawnPoint(profile);
		SpawnPointStore.Set(profile.GetPlayerID(), worldUID, point);
		ModLog.LogDebug($"Selected scattered spawn {point} for player {profile.GetPlayerID()}.");
		return point;
	}

	internal static void Clear()
	{
		SpawnPointStore.ClearCache();
	}

	private static Vector3 FindSpawnPoint(PlayerProfile profile)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: 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_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		Random random = new Random(CreateSeed(profile));
		for (int i = 0; i < 10000; i++)
		{
			Vector3 val = CreateCandidate(random);
			if (IsValid(val))
			{
				val.y = WorldGenerator.instance.GetHeight(val.x, val.z);
				return val;
			}
		}
		ModLog.LogWarning("No random Meadows spawn was found; using the Meadows world center.");
		return new Vector3(0f, WorldGenerator.instance.GetHeight(0f, 0f), 0f);
	}

	private static int CreateSeed(PlayerProfile profile)
	{
		long worldUID = ZNet.instance.GetWorldUID();
		long playerID = profile.GetPlayerID();
		return (((int)worldUID ^ (int)(worldUID >> 32)) * 397) ^ (int)playerID ^ (int)(playerID >> 32);
	}

	private static Vector3 CreateCandidate(Random random)
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		double num = random.NextDouble() * Math.PI * 2.0;
		float num2 = 1000f * Mathf.Sqrt((float)random.NextDouble());
		return new Vector3(num2 * (float)Math.Cos(num), 0f, num2 * (float)Math.Sin(num));
	}

	private static bool IsValid(Vector3 candidate)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Invalid comparison between Unknown and I4
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Invalid comparison between Unknown and I4
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		WorldGenerator instance = WorldGenerator.instance;
		if ((int)instance.GetBiome(candidate) != 1 || (int)instance.GetBiomeArea(candidate) != 2)
		{
			return false;
		}
		return instance.GetHeight(candidate.x, candidate.z) >= ZoneSystem.instance.m_waterLevel + 2f;
	}
}
[HarmonyPatch(typeof(Player), "OnSpawned")]
internal static class PlayerOnSpawnedPatch
{
	private static void Postfix(Player __instance)
	{
		//IL_006a: 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_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)Minimap.instance == (Object)null) && !((Object)(object)Game.instance == (Object)null) && !((Object)(object)ZNet.instance == (Object)null))
		{
			long playerID = Game.instance.GetPlayerProfile().GetPlayerID();
			long worldUID = ZNet.instance.GetWorldUID();
			if (SpawnPointStore.NeedsPin(playerID, worldUID) && SpawnPointStore.TryGet(playerID, worldUID, out var point))
			{
				Minimap.instance.AddPin(point, (PinType)3, "START", true, false, 0L, default(PlatformUserID));
				SpawnPointStore.MarkPinCreated(playerID, worldUID);
				ModLog.LogDebug($"Added removable start pin at {point}.");
			}
		}
	}
}
internal static class SpawnPointStore
{
	private struct SpawnRecord
	{
		internal readonly Vector3 Point;

		internal readonly bool PinCreated;

		internal SpawnRecord(Vector3 point, bool pinCreated)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			Point = point;
			PinCreated = pinCreated;
		}
	}

	private const string Header = "IronLabs.ScatteredVikings:1";

	private static readonly Dictionary<long, Dictionary<long, SpawnRecord>> Cache = new Dictionary<long, Dictionary<long, SpawnRecord>>();

	internal static bool TryGet(long playerId, long worldId, out Vector3 point)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		SpawnRecord value;
		bool flag = GetPlayerSpawns(playerId).TryGetValue(worldId, out value);
		point = (flag ? value.Point : Vector3.zero);
		return flag;
	}

	internal static void Set(long playerId, long worldId, Vector3 point)
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<long, SpawnRecord> playerSpawns = GetPlayerSpawns(playerId);
		playerSpawns[worldId] = new SpawnRecord(point, pinCreated: false);
		Save(playerId, playerSpawns);
	}

	internal static bool NeedsPin(long playerId, long worldId)
	{
		if (GetPlayerSpawns(playerId).TryGetValue(worldId, out var value))
		{
			return !value.PinCreated;
		}
		return false;
	}

	internal static void MarkPinCreated(long playerId, long worldId)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<long, SpawnRecord> playerSpawns = GetPlayerSpawns(playerId);
		if (playerSpawns.TryGetValue(worldId, out var value))
		{
			playerSpawns[worldId] = new SpawnRecord(value.Point, pinCreated: true);
			Save(playerId, playerSpawns);
		}
	}

	internal static void ClearCache()
	{
		Cache.Clear();
	}

	private static Dictionary<long, SpawnRecord> GetPlayerSpawns(long playerId)
	{
		if (Cache.TryGetValue(playerId, out var value))
		{
			return value;
		}
		value = Load(playerId);
		Cache.Add(playerId, value);
		return value;
	}

	private static Dictionary<long, SpawnRecord> Load(long playerId)
	{
		Dictionary<long, SpawnRecord> dictionary = new Dictionary<long, SpawnRecord>();
		string path = GetPath(playerId);
		if (!File.Exists(path))
		{
			return dictionary;
		}
		try
		{
			ParseLines(File.ReadAllLines(path), dictionary);
			ModLog.LogDebug($"Loaded {dictionary.Count} scattered spawn(s) for player {playerId}.");
		}
		catch (Exception ex)
		{
			ModLog.LogWarning("Failed to load scattered spawns from " + path + ": " + ex.Message);
		}
		return dictionary;
	}

	private static void ParseLines(string[] lines, Dictionary<long, SpawnRecord> spawns)
	{
		if (lines.Length == 0 || lines[0] != "IronLabs.ScatteredVikings:1")
		{
			throw new InvalidDataException("Unsupported scattered spawn file format.");
		}
		for (int i = 1; i < lines.Length; i++)
		{
			if (TryParse(lines[i], out var worldId, out var record))
			{
				spawns[worldId] = record;
			}
			else
			{
				ModLog.LogWarning("Ignored malformed scattered spawn entry: " + lines[i]);
			}
		}
	}

	private static bool TryParse(string line, out long worldId, out SpawnRecord record)
	{
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		string[] array = line.Split(new char[1] { '|' });
		CultureInfo invariantCulture = CultureInfo.InvariantCulture;
		worldId = 0L;
		float result = 0f;
		float result2 = 0f;
		float result3 = 0f;
		bool result4 = false;
		bool result5 = array.Length == 5 && long.TryParse(array[0], NumberStyles.Integer, invariantCulture, out worldId) && float.TryParse(array[1], NumberStyles.Float, invariantCulture, out result) && float.TryParse(array[2], NumberStyles.Float, invariantCulture, out result2) && float.TryParse(array[3], NumberStyles.Float, invariantCulture, out result3) && bool.TryParse(array[4], out result4);
		record = new SpawnRecord(new Vector3(result, result2, result3), result4);
		return result5;
	}

	private static void Save(long playerId, Dictionary<long, SpawnRecord> spawns)
	{
		string path = GetPath(playerId);
		string text = path + ".new";
		try
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			File.WriteAllLines(text, CreateLines(spawns));
			ReplaceFile(text, path);
			ModLog.LogDebug($"Saved {spawns.Count} scattered spawn(s) for player {playerId}.");
		}
		catch (Exception ex)
		{
			ModLog.LogWarning("Failed to save scattered spawns to " + path + ": " + ex.Message);
			DeleteTemporaryFile(text);
		}
	}

	private static string[] CreateLines(Dictionary<long, SpawnRecord> spawns)
	{
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: 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)
		List<string> list = new List<string> { "IronLabs.ScatteredVikings:1" };
		foreach (KeyValuePair<long, SpawnRecord> spawn in spawns)
		{
			SpawnRecord value = spawn.Value;
			list.Add(string.Join("|", spawn.Key, Format(value.Point.x), Format(value.Point.y), Format(value.Point.z), value.PinCreated));
		}
		return list.ToArray();
	}

	private static string Format(float value)
	{
		return value.ToString("R", CultureInfo.InvariantCulture);
	}

	private static void ReplaceFile(string temporaryPath, string path)
	{
		if (File.Exists(path))
		{
			File.Replace(temporaryPath, path, null);
		}
		else
		{
			File.Move(temporaryPath, path);
		}
	}

	private static void DeleteTemporaryFile(string path)
	{
		if (File.Exists(path))
		{
			File.Delete(path);
		}
	}

	private static string GetPath(long playerId)
	{
		return Path.Combine(Paths.ConfigPath, "IronLabs.ScatteredVikings", $"{playerId}.spawns");
	}
}