Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RossQoL v0.30.0
plugins/RossQoL.Core.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; using RossQoL.Core.Progression; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RossQoL.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+49848834b6e16f04c86962471088bb7e32df65a6")] [assembly: AssemblyProduct("RossQoL.Core")] [assembly: AssemblyTitle("RossQoL.Core")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RossQoL.Core.Terrain { public static class HeightLimits { public const float Vanilla = 8f; public const float MinSetting = 1f; public const float MaxSetting = 200f; public static float Effective(bool active, float configured) { if (!active) { return 8f; } if (float.IsNaN(configured) || float.IsInfinity(configured)) { return 8f; } return Math.Min(Math.Max(configured, 1f), 200f); } } } namespace RossQoL.Core.Tames { public readonly struct SummonCullCandidate { public float Health { get; } public float MaxHealth { get; } public double SecondsSinceSpawned { get; } public float HealthFraction { get { if (MaxHealth <= 0f) { return 1f; } float num = Health / MaxHealth; if (num < 0f) { return 0f; } if (num > 1f) { return 1f; } return num; } } public SummonCullCandidate(float health, float maxHealth, double secondsSinceSpawned) { Health = health; MaxHealth = maxHealth; SecondsSinceSpawned = secondsSinceSpawned; } } public static class SummonCullOrder { public static int Compare(SummonCullCandidate a, SummonCullCandidate b) { int num = a.HealthFraction.CompareTo(b.HealthFraction); if (num != 0) { return num; } return b.SecondsSinceSpawned.CompareTo(a.SecondsSinceSpawned); } public static List<int> Order(IReadOnlyList<SummonCullCandidate> candidates) { List<int> list = new List<int>(); if (candidates == null) { return list; } for (int i = 0; i < candidates.Count; i++) { list.Add(i); } for (int j = 1; j < list.Count; j++) { int num = list[j]; SummonCullCandidate b = candidates[num]; int num2 = j - 1; while (num2 >= 0 && Compare(candidates[list[num2]], b) > 0) { list[num2 + 1] = list[num2]; num2--; } list[num2 + 1] = num; } return list; } } } namespace RossQoL.Core.Startup { public static class ContinuePolicy { public const int MaxLabelLength = 40; private static readonly string[] JoinArguments = new string[4] { "+connect", "+connect_lobby", "-joincode", "-joinserverwithcharacter" }; public static bool HasJoinArguments(IEnumerable<string> args) { if (args == null) { return false; } foreach (string arg in args) { string[] joinArguments = JoinArguments; foreach (string b in joinArguments) { if (string.Equals(arg, b, StringComparison.Ordinal)) { return true; } } } return false; } public static bool ShouldShow(LastSession session, bool characterExists, bool worldLoadable, bool launchedWithJoinArguments) { if (session == null || !session.IsComplete) { return false; } if (launchedWithJoinArguments) { return false; } if (!characterExists) { return false; } if (session.Kind == SessionKind.LocalWorld && !worldLoadable) { return false; } return true; } public static string Label(string characterName, LastSession session) { string text = "Continue: " + characterName + " on " + session.DisplayName; if (text.Length <= 40) { return text; } int num = 39; if (num > 0 && char.IsHighSurrogate(text[num - 1])) { num--; } return text.Substring(0, num) + "…"; } } public enum SessionKind { LocalWorld, Server } public enum ServerKind { None, Dedicated, SteamUser, PlayFab } public sealed class LastSession : IEquatable<LastSession> { public SessionKind Kind { get; } public string CharacterFile { get; } public string CharacterSource { get; } public string WorldName { get; } public string WorldSource { get; } public ServerKind ServerKind { get; } public string ServerAddress { get; } public string JoinCode { get; } public string DisplayName { get; } public bool IsComplete { get { if (CharacterFile.Length == 0 || CharacterSource.Length == 0) { return false; } if (Kind != SessionKind.LocalWorld) { if (ServerKind != ServerKind.None) { return ServerAddress.Length > 0; } return false; } if (WorldName.Length > 0) { return WorldSource.Length > 0; } return false; } } private LastSession(SessionKind kind, string characterFile, string characterSource, string worldName, string worldSource, ServerKind serverKind, string serverAddress, string joinCode, string displayName) { Kind = kind; CharacterFile = characterFile ?? ""; CharacterSource = characterSource ?? ""; WorldName = worldName ?? ""; WorldSource = worldSource ?? ""; ServerKind = serverKind; ServerAddress = serverAddress ?? ""; JoinCode = joinCode ?? ""; DisplayName = displayName ?? ""; } public static LastSession LocalWorld(string characterFile, string characterSource, string worldName, string worldSource) { return new LastSession(SessionKind.LocalWorld, characterFile, characterSource, worldName, worldSource, ServerKind.None, "", "", worldName); } public static LastSession Server(string characterFile, string characterSource, ServerKind serverKind, string serverAddress, string joinCode, string displayName) { return new LastSession(SessionKind.Server, characterFile, characterSource, "", "", serverKind, serverAddress, joinCode, string.IsNullOrEmpty(displayName) ? serverAddress : displayName); } public bool Equals(LastSession other) { if (other != null && Kind == other.Kind && CharacterFile == other.CharacterFile && CharacterSource == other.CharacterSource && WorldName == other.WorldName && WorldSource == other.WorldSource && ServerKind == other.ServerKind && ServerAddress == other.ServerAddress && JoinCode == other.JoinCode) { return DisplayName == other.DisplayName; } return false; } public override bool Equals(object obj) { return Equals(obj as LastSession); } public override int GetHashCode() { return ((((((int)Kind * 397) ^ CharacterFile.GetHashCode()) * 397) ^ WorldName.GetHashCode()) * 397) ^ ServerAddress.GetHashCode(); } public override string ToString() { return $"{Kind} '{DisplayName}' as {CharacterFile}"; } } public static class LastSessionFormat { private const string Version = "1"; private const char Separator = '|'; private const char Escape = '\\'; private const int FieldCount = 10; public static string Write(LastSession session) { if (session == null) { throw new ArgumentNullException("session"); } string[] array = new string[10] { "1", session.Kind.ToString(), session.CharacterFile, session.CharacterSource, session.WorldName, session.WorldSource, session.ServerKind.ToString(), session.ServerAddress, session.JoinCode, session.DisplayName }; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < array.Length; i++) { if (i > 0) { stringBuilder.Append('|'); } string text = array[i]; foreach (char c in text) { if (c == '|' || c == '\\') { stringBuilder.Append('\\'); } stringBuilder.Append(c); } } return stringBuilder.ToString(); } public static LastSession Read(string text) { if (string.IsNullOrEmpty(text)) { return null; } List<string> list = Split(text); if (list == null || list.Count != 10 || list[0] != "1") { return null; } if (!TryParseEnum<SessionKind>(list[1], out var value)) { return null; } if (!TryParseEnum<ServerKind>(list[6], out var value2)) { return null; } LastSession lastSession = ((value == SessionKind.LocalWorld) ? LastSession.LocalWorld(list[2], list[3], list[4], list[5]) : LastSession.Server(list[2], list[3], value2, list[7], list[8], list[9])); if (!lastSession.IsComplete) { return null; } return lastSession; } private static bool TryParseEnum<T>(string text, out T value) where T : struct { value = default(T); if (text.Length == 0) { return false; } if (Array.IndexOf(Enum.GetNames(typeof(T)), text) < 0) { return false; } return Enum.TryParse<T>(text, ignoreCase: false, out value); } private static List<string> Split(string text) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < text.Length; i++) { char c = text[i]; switch (c) { case '\\': if (i + 1 >= text.Length) { return null; } stringBuilder.Append(text[++i]); break; case '|': list.Add(stringBuilder.ToString()); stringBuilder.Clear(); break; default: stringBuilder.Append(c); break; } } list.Add(stringBuilder.ToString()); return list; } } public sealed class SessionCapture { private ServerKind _kind; private string _address; private string _joinCode; private string _displayName; public void ServerJoinRequested(ServerKind kind, string address, string joinCode, string displayName) { _kind = kind; _address = address; _joinCode = joinCode; _displayName = displayName; } public void LocalWorldStartRequested() { Clear(); } public LastSession CommitOnInitialSpawn(bool hostingLocalWorld, string characterFile, string characterSource, string worldName, string worldSource) { LastSession lastSession = (hostingLocalWorld ? LastSession.LocalWorld(characterFile, characterSource, worldName, worldSource) : ((_kind == ServerKind.None) ? null : LastSession.Server(characterFile, characterSource, _kind, _address, _joinCode, _displayName))); Clear(); if (lastSession == null || !lastSession.IsComplete) { return null; } return lastSession; } private void Clear() { _kind = ServerKind.None; _address = null; _joinCode = null; _displayName = null; } } } namespace RossQoL.Core.Progression { public static class BiomeBosses { private static readonly Dictionary<string, string> Bosses = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "BlackForest", "defeated_gdking" }, { "Swamp", "defeated_bonemass" }, { "Mountain", "defeated_dragon" }, { "Plains", "defeated_goblinking" } }; public static IEnumerable<string> KnownBiomes => Bosses.Keys; public static string KeyFor(string biomeName) { if (string.IsNullOrEmpty(biomeName)) { return null; } if (!Bosses.TryGetValue(biomeName, out var value)) { return null; } return value; } public static bool IsCleared(string biomeName, Func<string, bool> isKeySet) { if (isKeySet == null) { return false; } string text = KeyFor(biomeName); if (text != null) { return isKeySet(text); } return false; } } public static class DungeonBosses { public const string Queen = "defeated_queen"; private static readonly Dictionary<string, string> Bosses = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "BlackForest", "defeated_gdking" }, { "Swamp", "defeated_bonemass" }, { "Mountain", "defeated_dragon" }, { "Mistlands", "defeated_queen" } }; public static IEnumerable<string> KnownBiomes => Bosses.Keys; public static string KeyFor(string biomeName) { if (string.IsNullOrEmpty(biomeName)) { return null; } if (!Bosses.TryGetValue(biomeName, out var value)) { return null; } return value; } public static bool IsCleared(string biomeName, Func<string, bool> isKeySet) { if (isKeySet == null) { return false; } string text = KeyFor(biomeName); if (text != null) { return isKeySet(text); } return false; } } public static class DungeonRespawnMath { public static bool IsDue(int? lastVisitDay, int today, int respawnDays) { if (!lastVisitDay.HasValue) { return false; } if (respawnDays <= 0) { return false; } if (today < lastVisitDay.Value) { return false; } return today - lastVisitDay.Value >= respawnDays; } public static int DaysRemaining(int? lastVisitDay, int today, int respawnDays) { if (!lastVisitDay.HasValue || respawnDays <= 0) { if (respawnDays <= 0) { return 0; } return respawnDays; } int num = today - lastVisitDay.Value; if (num < 0) { num = 0; } int num2 = respawnDays - num; if (num2 <= 0) { return 0; } return num2; } } public static class DungeonUnlock { private static readonly char[] Separators = new char[4] { ',', ' ', '\t', '|' }; private static readonly Dictionary<string, string[]> ThemeBosses = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) { { "SunkenCrypt", new string[1] { "defeated_bonemass" } }, { "ForestCrypt", new string[1] { "defeated_gdking" } }, { "ForestCryptHildir", new string[1] { "defeated_gdking" } }, { "Crypt", new string[2] { "defeated_gdking", "defeated_bonemass" } }, { "Cave", new string[2] { "defeated_gdking", "defeated_dragon" } }, { "CaveHildir", new string[2] { "defeated_gdking", "defeated_dragon" } }, { "DvergerTown", new string[1] { "defeated_queen" } }, { "DvergerBoss", new string[1] { "defeated_queen" } } }; public static IEnumerable<string> KnownThemes => ThemeBosses.Keys; public static IReadOnlyList<string> KeysForBiomes(string biomeNames) { List<string> list = Split(biomeNames); if (list.Count == 0) { return null; } List<string> list2 = new List<string>(); foreach (string item in list) { string text = DungeonBosses.KeyFor(item); if (text == null) { return null; } if (!list2.Contains(text)) { list2.Add(text); } } if (list2.Count <= 0) { return null; } return list2; } public static IReadOnlyList<string> KeysForThemes(string themeNames) { List<string> list = Split(themeNames); if (list.Count == 0) { return null; } List<string> list2 = new List<string>(); foreach (string item2 in list) { if (!ThemeBosses.TryGetValue(item2, out var value)) { return null; } string[] array = value; foreach (string item in array) { if (!list2.Contains(item)) { list2.Add(item); } } } if (list2.Count <= 0) { return null; } return list2; } public static IReadOnlyList<string> RequiredKeys(string biomeNames, string themeNames) { return KeysForBiomes(biomeNames) ?? KeysForThemes(themeNames); } public static bool IsCleared(string biomeNames, string themeNames, Func<string, bool> isKeySet) { if (isKeySet == null) { return false; } IReadOnlyList<string> readOnlyList = RequiredKeys(biomeNames, themeNames); if (readOnlyList == null || readOnlyList.Count == 0) { return false; } foreach (string item in readOnlyList) { if (!isKeySet(item)) { return false; } } return true; } public static string Describe(string biomeNames, string themeNames) { IReadOnlyList<string> readOnlyList = RequiredKeys(biomeNames, themeNames); if (readOnlyList != null && readOnlyList.Count != 0) { return string.Join(" + ", ToArray(readOnlyList)); } return "nothing we recognise"; } private static List<string> Split(string names) { List<string> list = new List<string>(); if (string.IsNullOrEmpty(names)) { return list; } string[] array = names.Split(Separators, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { if (!string.Equals(text, "None", StringComparison.OrdinalIgnoreCase)) { list.Add(text); } } return list; } private static string[] ToArray(IReadOnlyList<string> keys) { string[] array = new string[keys.Count]; for (int i = 0; i < keys.Count; i++) { array[i] = keys[i]; } return array; } } public static class TeleportUnlocks { public const string Elder = "defeated_gdking"; public const string Bonemass = "defeated_bonemass"; public const string Moder = "defeated_dragon"; public const string Yagluth = "defeated_goblinking"; public const string Queen = "defeated_queen"; public const string Fader = "defeated_fader"; private static readonly Dictionary<string, string> Unlocks = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "CopperOre", "defeated_gdking" }, { "TinOre", "defeated_gdking" }, { "Copper", "defeated_gdking" }, { "Tin", "defeated_gdking" }, { "Bronze", "defeated_gdking" }, { "CopperScrap", "defeated_gdking" }, { "IronOre", "defeated_bonemass" }, { "IronScrap", "defeated_bonemass" }, { "Iron", "defeated_bonemass" }, { "SilverOre", "defeated_dragon" }, { "Silver", "defeated_dragon" }, { "DragonEgg", "defeated_dragon" }, { "BlackMetalScrap", "defeated_goblinking" }, { "BlackMetal", "defeated_goblinking" }, { "MechanicalSpring", "defeated_queen" }, { "DvergrNeedle", "defeated_queen" }, { "FlametalOreNew", "defeated_fader" }, { "FlametalNew", "defeated_fader" } }; public static IEnumerable<string> KnownItems => Unlocks.Keys; public static string KeyFor(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return null; } if (!Unlocks.TryGetValue(prefabName, out var value)) { return null; } return value; } public static bool IsUnlocked(string prefabName, Func<string, bool> isKeySet) { if (isKeySet == null) { return false; } string text = KeyFor(prefabName); if (text != null) { return isKeySet(text); } return false; } } public static class WorldFrontier { public const string Eikthyr = "defeated_eikthyr"; public const string Queen = "defeated_queen"; public const string Fader = "defeated_fader"; private static readonly (string Tier, string Key)[] Ladder = new(string, string)[8] { ("Meadows", null), ("BlackForest", "defeated_eikthyr"), ("Swamp", "defeated_gdking"), ("Mountain", "defeated_bonemass"), ("Plains", "defeated_dragon"), ("Mistlands", "defeated_goblinking"), ("Ashlands", "defeated_queen"), ("DeepNorth", "defeated_fader") }; public static IReadOnlyList<string> Tiers { get { List<string> list = new List<string>(Ladder.Length); (string, string)[] ladder = Ladder; for (int i = 0; i < ladder.Length; i++) { string item = ladder[i].Item1; list.Add(item); } return list; } } public static string KeyForTier(string tier) { (string, string)[] ladder = Ladder; for (int i = 0; i < ladder.Length; i++) { var (a, result) = ladder[i]; if (string.Equals(a, tier, StringComparison.OrdinalIgnoreCase)) { return result; } } return null; } public static string TierFor(Func<string, bool> isKeySet) { if (isKeySet == null) { return Ladder[0].Tier; } string result = Ladder[0].Tier; (string, string)[] ladder = Ladder; for (int i = 0; i < ladder.Length; i++) { var (text, text2) = ladder[i]; if (text2 != null && isKeySet(text2)) { result = text; } } return result; } } } namespace RossQoL.Core.Production { public static class FeedRules { public static readonly StringComparer NameComparer = StringComparer.OrdinalIgnoreCase; public static Dictionary<string, int> ParseAmounts(string setting) { Dictionary<string, int> dictionary = new Dictionary<string, int>(NameComparer); if (string.IsNullOrWhiteSpace(setting)) { return dictionary; } string[] array = setting.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.LastIndexOf(':'); if (num > 0 && num != text.Length - 1) { string text2 = text.Substring(0, num).Trim(); string s = text.Substring(num + 1).Trim(); if (text2.Length != 0 && int.TryParse(s, out var result) && result >= 0) { dictionary[text2] = result; } } } return dictionary; } public static HashSet<string> ParseNames(string setting) { HashSet<string> hashSet = new HashSet<string>(NameComparer); if (string.IsNullOrWhiteSpace(setting)) { return hashSet; } string[] array = setting.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } public static bool Allowed(ICollection<string> allowList, string name) { if (allowList != null && allowList.Count != 0) { if (name != null) { return allowList.Contains(name); } return false; } return true; } public static int MinimumFor(IDictionary<string, int> perItem, string name, int fallback) { if (name != null && perItem != null && perItem.TryGetValue(name, out var value)) { return Math.Max(0, value); } return Math.Max(0, fallback); } public static int Takeable(int available, int wanted, int minimum) { if (wanted <= 0) { return 0; } int num = available - Math.Max(0, minimum); if (num <= 0) { return 0; } return Math.Min(num, wanted); } public static bool AtCap(IDictionary<string, int> caps, string product, int existing) { if (product == null || caps == null) { return false; } if (!caps.TryGetValue(product, out var value) || value <= 0) { return false; } return existing >= value; } } public static class FermenterReadiness { public static bool IsReady(int content, long startTicks, long nowTicks, float durationSeconds) { if (content == 0 || startTicks == 0L) { return false; } return (double)(nowTicks - startTicks) / 10000000.0 > (double)durationSeconds; } } public static class HarvestMath { public static int Room(int freeStackSpace, int emptySlots, int maxStackSize) { long num = Math.Max(0, freeStackSpace) + (long)Math.Max(0, emptySlots) * (long)Math.Max(1, maxStackSize); if (num <= int.MaxValue) { return (int)num; } return int.MaxValue; } public static int UnitsTaken(int placed, int unitSize) { if (placed <= 0) { return 0; } long num = Math.Max(1, unitSize); return (int)((placed + num - 1) / num); } public static int Shortfall(int placed, int unitSize) { if (placed <= 0) { return 0; } return (int)((long)UnitsTaken(placed, unitSize) * (long)Math.Max(1, unitSize) - placed); } public static bool IsDue(double? lastAttempt, double now, double intervalSeconds) { if (!lastAttempt.HasValue) { return true; } double num = now - lastAttempt.Value; if (!(num < 0.0)) { return num >= Math.Max(0.0, intervalSeconds); } return true; } } public readonly struct DestinationCandidate { public int Index { get; } public bool HoldsItem { get; } public int Room { get; } public float DistanceSquared { get; } public DestinationCandidate(int index, bool holdsItem, int room, float distanceSquared) { Index = index; HoldsItem = holdsItem; Room = room; DistanceSquared = distanceSquared; } } public readonly struct Placement { public int Index { get; } public int Amount { get; } public Placement(int index, int amount) { Index = index; Amount = amount; } } public static class HarvestPlan { public static List<DestinationCandidate> Rank(IEnumerable<DestinationCandidate> candidates) { if (candidates == null) { return new List<DestinationCandidate>(); } return (from c in candidates where c.Room > 0 orderby c.HoldsItem descending, c.DistanceSquared, c.Index select c).ToList(); } public static List<Placement> Plan(int amount, int unitSize, IReadOnlyList<DestinationCandidate> ranked, bool wholeOnly) { List<Placement> list = new List<Placement>(); if (amount <= 0 || ranked == null) { return list; } int num = Math.Max(1, unitSize); int num2 = amount - amount % num; foreach (DestinationCandidate item in ranked) { if (num2 <= 0) { break; } int num3 = Math.Min(num2, item.Room); num3 -= num3 % num; if (num3 > 0) { list.Add(new Placement(item.Index, num3)); num2 -= num3; } } if (wholeOnly && list.Sum((Placement p) => p.Amount) < amount) { list.Clear(); } return list; } } } namespace RossQoL.Core.Portals { public static class ArrivalPlacement { private const float RingStep = 1.5f; private static readonly float[] AngleOffsetsDegrees = new float[14] { 0f, 25f, -25f, 50f, -50f, 75f, -75f, 100f, -100f, 130f, -130f, 160f, -160f, 180f }; public static Vec3[] Compute(Vec3 arrival, Vec3 facing, int count, float searchDistance, Func<Vec3, bool> isFree) { if (count <= 0) { return Array.Empty<Vec3>(); } if (isFree == null) { isFree = (Vec3 _) => true; } (float X, float Z) tuple = Normalise(facing); float item = tuple.X; float item2 = tuple.Z; Vec3[] array = new Vec3[count]; List<Vec3> list = new List<Vec3>(count); for (int num = 0; num < count; num++) { array[num] = FindSpot(arrival, item, item2, searchDistance, isFree, list); list.Add(array[num]); } return array; } private static Vec3 FindSpot(Vec3 arrival, float fx, float fz, float searchDistance, Func<Vec3, bool> isFree, List<Vec3> taken) { if (searchDistance <= 0f) { return arrival; } for (float num = Math.Min(1.5f, searchDistance); num <= searchDistance + 0.001f; num += 1.5f) { float[] angleOffsetsDegrees = AngleOffsetsDegrees; foreach (float degrees in angleOffsetsDegrees) { Vec3 vec = Rotate(arrival, fx, fz, num, degrees); if (isFree(vec) && !IsTaken(taken, vec)) { return vec; } } } return arrival; } private static bool IsTaken(List<Vec3> taken, Vec3 candidate) { for (int i = 0; i < taken.Count; i++) { if (Vec3.DistanceSquared(taken[i], candidate) < 1f) { return true; } } return false; } private static Vec3 Rotate(Vec3 origin, float fx, float fz, float radius, float degrees) { double num = (double)degrees * Math.PI / 180.0; double num2 = Math.Cos(num); double num3 = Math.Sin(num); float num4 = (float)((double)fx * num2 - (double)fz * num3); float num5 = (float)((double)fx * num3 + (double)fz * num2); return new Vec3(origin.X + num4 * radius, origin.Y, origin.Z + num5 * radius); } private static (float X, float Z) Normalise(Vec3 facing) { float num = (float)Math.Sqrt(facing.X * facing.X + facing.Z * facing.Z); if (!float.IsFinite(num) || num < 0.0001f) { return (X: 0f, Z: 1f); } return (X: facing.X / num, Z: facing.Z / num); } } public static class PlacementFooting { public const float SameFloorToleranceMetres = 1.5f; public const float UnderTerrainMarginMetres = 1f; public static bool IsAcceptable(bool floorFound, float floorHeight, float playerHeight, float toleranceMetres, bool terrainFound, float terrainHeight, bool playerIsUnderTerrain, float underTerrainMarginMetres) { if (!IsStandingRoom(floorFound, floorHeight, playerHeight, toleranceMetres)) { return false; } if (playerIsUnderTerrain) { return true; } return !IsUnderTerrain(floorHeight, terrainFound, terrainHeight, underTerrainMarginMetres); } public static bool IsStandingRoom(bool floorFound, float floorHeight, float playerHeight, float toleranceMetres) { if (!floorFound) { return false; } if (!IsFinite(floorHeight) || !IsFinite(playerHeight)) { return false; } return Math.Abs(floorHeight - playerHeight) <= Math.Max(0f, toleranceMetres); } public static bool IsUnderTerrain(float height, bool terrainFound, float terrainHeight, float marginMetres) { if (!terrainFound) { return false; } if (!IsFinite(height) || !IsFinite(terrainHeight)) { return false; } return height < terrainHeight - Math.Max(0f, marginMetres); } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public readonly struct TameCandidate { public Vec3 Position { get; } public bool IsTamed { get; } public bool IsFollowingPlayer { get; } public bool IsBusy { get; } public bool IsSummon { get; } public TameCandidate(Vec3 position, bool isTamed, bool isFollowingPlayer, bool isBusy, bool isSummon = false) { Position = position; IsTamed = isTamed; IsFollowingPlayer = isFollowingPlayer; IsBusy = isBusy; IsSummon = isSummon; } } public static class TameEligibility { public static bool Qualifies(TameCandidate candidate, Vec3 playerPosition, float radius) { if (radius <= 0f) { return false; } if (!candidate.IsTamed && !candidate.IsSummon) { return false; } if (!candidate.IsFollowingPlayer) { return false; } if (candidate.IsBusy) { return false; } return Vec3.DistanceSquared(candidate.Position, playerPosition) <= radius * radius; } public static List<int> SelectIndices(IReadOnlyList<TameCandidate> candidates, Vec3 playerPosition, float radius) { List<int> list = new List<int>(); if (candidates == null) { return list; } for (int i = 0; i < candidates.Count; i++) { if (Qualifies(candidates[i], playerPosition, radius)) { list.Add(i); } } return list; } } public enum TameMoveOutcome { Moved, NoId, NoZdoMan, ZdoMissing, OwnershipRefused } public static class TameMoveOutcomes { public static TameMoveOutcome Classify(bool hasId, bool networkReady, bool zdoFound, bool ownershipHeld) { if (!hasId) { return TameMoveOutcome.NoId; } if (!networkReady) { return TameMoveOutcome.NoZdoMan; } if (!zdoFound) { return TameMoveOutcome.ZdoMissing; } if (!ownershipHeld) { return TameMoveOutcome.OwnershipRefused; } return TameMoveOutcome.Moved; } public static bool Arrived(TameMoveOutcome outcome) { return outcome == TameMoveOutcome.Moved; } } public readonly struct Vec3 : IEquatable<Vec3> { public static readonly Vec3 Zero = new Vec3(0f, 0f, 0f); public float X { get; } public float Y { get; } public float Z { get; } public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; } public Vec3 WithY(float y) { return new Vec3(X, y, Z); } public static Vec3 operator +(Vec3 a, Vec3 b) { return new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); } public static Vec3 operator *(Vec3 a, float s) { return new Vec3(a.X * s, a.Y * s, a.Z * s); } public static float DistanceSquared(Vec3 a, Vec3 b) { float num = a.X - b.X; float num2 = a.Y - b.Y; float num3 = a.Z - b.Z; return num * num + num2 * num2 + num3 * num3; } public bool Equals(Vec3 other) { if (X == other.X && Y == other.Y) { return Z == other.Z; } return false; } public override bool Equals(object obj) { if (obj is Vec3 other) { return Equals(other); } return false; } public override int GetHashCode() { return (((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode(); } public override string ToString() { return $"({X:F2}, {Y:F2}, {Z:F2})"; } } } namespace RossQoL.Core.Items { public static class ItemDecayRule { public const double AutoDestroySeconds = 3600.0; public const float PlayerRangeMetres = 25f; public static bool ShouldDestroy(double ageSeconds, bool playerNearby, bool inTar, bool isDebris, bool ignoreBaseExemption) { if (!ignoreBaseExemption) { return false; } if (ageSeconds < 3600.0) { return false; } if (playerNearby) { return false; } if (inTar) { return false; } if (isDebris) { return false; } return true; } } public enum RecallAttackAction { PlayNow, RetireThenPlay, Refuse } public static class RecallAttackHandover { public static RecallAttackAction Decide(bool hasCurrentAttack, bool currentAttackDone) { if (!hasCurrentAttack) { return RecallAttackAction.PlayNow; } if (!currentAttackDone) { return RecallAttackAction.Refuse; } return RecallAttackAction.RetireThenPlay; } } public static class RecallCast { public static bool IsComplete(float startedAt, float now, float castSeconds) { if (castSeconds <= 0f) { return true; } return now - startedAt >= castSeconds; } } public static class RecallCooldown { public static bool CanFire(float? lastUsed, float now, float cooldownSeconds) { if (cooldownSeconds <= 0f) { return true; } if (!lastUsed.HasValue) { return true; } return now - lastUsed.Value >= cooldownSeconds; } } public static class SummonKinds { public const string SkeletonPrefab = "Skeleton_Friendly"; public const string SpiritCallerSuffix = "_spiritcaller"; public const string DeadRaiserStaff = "StaffSkeleton"; public const string SpiritCallerStaff = "StaffSpiritCaller"; private const string CloneSuffix = "(Clone)"; public static bool IsSummon(string prefabName) { string text = Strip(prefabName); if (text.Length == 0) { return false; } if (!text.Equals("Skeleton_Friendly", StringComparison.OrdinalIgnoreCase)) { if (text.Length > "_spiritcaller".Length) { return text.EndsWith("_spiritcaller", StringComparison.OrdinalIgnoreCase); } return false; } return true; } public static bool IsRecallStaff(string prefabName) { string text = Strip(prefabName); if (!text.Equals("StaffSkeleton", StringComparison.OrdinalIgnoreCase)) { return text.Equals("StaffSpiritCaller", StringComparison.OrdinalIgnoreCase); } return true; } private static string Strip(string name) { if (string.IsNullOrEmpty(name)) { return ""; } if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length); } } public readonly struct SummonKind { public bool Available { get; } public int Following { get; } public float WeakestHealthFraction { get; } public SummonKind(bool available, int following, float weakestHealthFraction) { Available = available; Following = following; WeakestHealthFraction = weakestHealthFraction; } } public static class SummonPick { public const float HealthTieTolerance = 0.005f; public static int Choose(IReadOnlyList<SummonKind> kinds, Func<int, int> randomBelow) { if (kinds == null || randomBelow == null) { return -1; } List<int> list = new List<int>(); List<int> list2 = new List<int>(); for (int i = 0; i < kinds.Count; i++) { if (kinds[i].Available) { ((kinds[i].Following <= 0) ? list : list2).Add(i); } } if (list.Count > 0) { return PickAmong(list, randomBelow); } if (list2.Count == 0) { return -1; } float num = float.MaxValue; foreach (int item in list2) { num = Math.Min(num, Clamp01(kinds[item].WeakestHealthFraction)); } List<int> list3 = new List<int>(); foreach (int item2 in list2) { if (Clamp01(kinds[item2].WeakestHealthFraction) - num <= 0.005f) { list3.Add(item2); } } return PickAmong(list3, randomBelow); } public static int MostPerCast(int minToSpawn, int maxToSpawn) { if (maxToSpawn <= minToSpawn) { return minToSpawn; } return maxToSpawn - 1; } private static int PickAmong(List<int> indices, Func<int, int> randomBelow) { if (indices.Count == 1) { return indices[0]; } int num = randomBelow(indices.Count); if (num < 0 || num >= indices.Count) { num = 0; } return indices[num]; } private static float Clamp01(float value) { if (float.IsNaN(value)) { return 1f; } if (value < 0f) { return 0f; } if (!(value > 1f)) { return value; } return 1f; } } } namespace RossQoL.Core.Interface { public static class ClockText { private const int MinutesPerDay = 1440; public static string Format(string dayLabel, float dayFraction, bool use24Hour) { string text = FormatTime(dayFraction, use24Hour); if (!string.IsNullOrEmpty(dayLabel)) { return dayLabel + " " + text; } return text; } private static string FormatTime(float dayFraction, bool use24Hour) { double num = ((float.IsNaN(dayFraction) || float.IsInfinity(dayFraction)) ? 0.0 : ((double)dayFraction)); int num2 = (int)Math.Floor((num - Math.Floor(num)) * 1440.0) % 1440; int num3 = num2 / 60; int num4 = num2 % 60; if (use24Hour) { return $"{num3:00}:{num4:00}"; } int num5 = ((num3 % 12 == 0) ? 12 : (num3 % 12)); return string.Format("{0}:{1:00} {2}", num5, num4, (num3 < 12) ? "AM" : "PM"); } } public enum NotificationStyle { Plain, Count, Progress } public sealed class Notification { public string Key { get; } public string Label { get; internal set; } public NotificationStyle Style { get; internal set; } public float Amount { get; internal set; } public float Share { get; internal set; } public float RefreshedAt { get; internal set; } public string Text { get { switch (Style) { case NotificationStyle.Count: { int num = (int)Math.Round(Amount, MidpointRounding.AwayFromZero); if (num <= 1) { return Label; } return Label + " x" + num.ToString(CultureInfo.InvariantCulture); } case NotificationStyle.Progress: return Label + " " + SkillProgress.FormatGain(Amount, Share); default: return Label; } } } internal Notification(string key, string label, NotificationStyle style, float amount, float share, float now) { Key = key; Label = label; Style = style; Amount = amount; Share = share; RefreshedAt = now; } } public sealed class NotificationList { public const int DefaultCapacity = 5; public const float DefaultHoldSeconds = 1f; public const float DefaultFadeSeconds = 4f; private readonly List<Notification> _entries = new List<Notification>(); public int Capacity { get; } public float HoldSeconds { get; } public float FadeSeconds { get; } public IReadOnlyList<Notification> Entries => _entries; public int Count => _entries.Count; public NotificationList(int capacity = 5, float holdSeconds = 1f, float fadeSeconds = 4f) { Capacity = Math.Max(1, capacity); HoldSeconds = Math.Max(0f, holdSeconds); FadeSeconds = Math.Max(0f, fadeSeconds); } public Notification Add(string key, string label, NotificationStyle style, float amount, float now) { return Fold(key, label, style, amount, 0f, now); } public Notification AddProgress(string key, string label, float gain, float percent, float now) { return Fold(key, label, NotificationStyle.Progress, gain, percent, now); } private Notification Fold(string key, string label, NotificationStyle style, float amount, float share, float now) { if (key == null) { throw new ArgumentNullException("key"); } Notification notification = Find(key); if (notification != null) { bool flag = notification.Style == style; notification.Label = label; notification.Amount = (flag ? (notification.Amount + amount) : amount); notification.Share = (flag ? (notification.Share + share) : share); notification.Style = style; notification.RefreshedAt = now; return notification; } if (_entries.Count >= Capacity) { _entries.RemoveAt(0); } Notification notification2 = new Notification(key, label, style, amount, share, now); _entries.Add(notification2); return notification2; } public Notification Find(string key) { for (int i = 0; i < _entries.Count; i++) { if (string.Equals(_entries[i].Key, key, StringComparison.Ordinal)) { return _entries[i]; } } return null; } public bool Remove(string key) { Notification notification = Find(key); if (notification != null) { return _entries.Remove(notification); } return false; } public void Clear() { _entries.Clear(); } public void Prune(float now) { for (int num = _entries.Count - 1; num >= 0; num--) { if (AlphaOf(_entries[num], now) <= 0f) { _entries.RemoveAt(num); } } } public float AlphaOf(Notification entry, float now) { if (entry == null) { return 0f; } float num = now - entry.RefreshedAt; if (num <= HoldSeconds) { return 1f; } if (FadeSeconds <= 0f) { return 0f; } float num2 = 1f - (num - HoldSeconds) / FadeSeconds; if (num2 <= 0f) { return 0f; } if (!(num2 >= 1f)) { return num2; } return 1f; } } public static class ProductionTimer { public static double SecondsToNextUnit(double product, double secPerUnit) { if (secPerUnit <= 0.0) { return 0.0; } double num = secPerUnit - product; if (!(num > 0.0)) { return 0.0; } return num; } public static double SecondsToFull(double product, double secPerUnit, int level, int maxLevel) { if (secPerUnit <= 0.0 || level >= maxLevel) { return 0.0; } double num = (double)(maxLevel - level) * secPerUnit - product; if (!(num > 0.0)) { return 0.0; } return num; } public static double SecondsUntilReady(long startTicks, long nowTicks, double durationSeconds) { if (startTicks <= 0) { return -1.0; } double num = (double)(nowTicks - startTicks) / 10000000.0; double num2 = durationSeconds - num; if (!(num2 > 0.0)) { return 0.0; } return num2; } public static string Format(double seconds) { if (seconds <= 0.0) { return "0s"; } long num = (long)Math.Ceiling(seconds); long num2 = num / 3600; long num3 = num % 3600 / 60; long num4 = num % 60; if (num2 > 0) { return $"{num2}h {num3:00}m"; } if (num3 > 0) { return $"{num3}m {num4:00}s"; } return $"{num4}s"; } } public static class SkillProgress { public const float MaxLevel = 100f; private const float SmallestValue = 0.1f; private const float SmallestPercent = 1f; public static float NextLevelRequirement(float level) { if (level < 0f) { level = 0f; } return (float)(Math.Pow(Math.Floor(level + 1f), 1.5) * 0.5 + 0.5); } public static float PercentOfLevel(float gain, float level) { if (gain <= 0f) { return 0f; } float num = NextLevelRequirement(level); if (num <= 0f) { return 0f; } return gain / num * 100f; } public static string FormatGain(float gain, float percent) { return "+" + FormatValue(gain) + " (" + FormatPercent(percent) + ")"; } public static string FormatValue(float gain) { if (gain <= 0f) { return "0"; } if (gain < 0.1f) { return "<0.1"; } return Math.Round(gain, 1, MidpointRounding.AwayFromZero).ToString("0.#", CultureInfo.InvariantCulture); } public static string FormatPercent(float percent) { if (percent <= 0f) { return "0%"; } if (percent < 1f) { return "<1%"; } return ((int)Math.Round(percent, MidpointRounding.AwayFromZero)).ToString(CultureInfo.InvariantCulture) + "%"; } } } namespace RossQoL.Core.Framework { public sealed class ConfigReloadSchedule { public static readonly TimeSpan Debounce = TimeSpan.FromSeconds(0.5); public static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(0.5); public static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(2.0); private DateTime _lastKnownWriteUtc; private DateTime _dueUtc; private DateTime _lastPollUtc = DateTime.MinValue; private bool _pending; public ConfigReloadSchedule(DateTime initialWriteUtc) { _lastKnownWriteUtc = initialWriteUtc; } public bool TakePollDue(DateTime nowUtc) { if (_lastPollUtc != DateTime.MinValue && nowUtc - _lastPollUtc < PollInterval) { return false; } _lastPollUtc = nowUtc; return true; } public void ObserveWriteTime(DateTime writeUtc, DateTime nowUtc) { if (!(writeUtc == DateTime.MinValue) && !(writeUtc == _lastKnownWriteUtc)) { _lastKnownWriteUtc = writeUtc; NotifyChanged(nowUtc); } } public void NotifyChanged(DateTime nowUtc) { _pending = true; _dueUtc = nowUtc + Debounce; } public bool TakeDueReload(DateTime nowUtc) { if (!_pending || nowUtc < _dueUtc) { return false; } _pending = false; return true; } public void Reloaded(DateTime nowUtc, DateTime writeUtc) { if (writeUtc != DateTime.MinValue) { _lastKnownWriteUtc = writeUtc; } } public void ReloadFailed(DateTime nowUtc) { _pending = true; _dueUtc = nowUtc + RetryDelay; } } public enum FeatureScope { Client, Synced } public static class FeatureRules { public static bool IsActive(bool categoryEnabled, bool featureEnabled) { return categoryEnabled && featureEnabled; } public static FeatureScope CategoryScope(IEnumerable<FeatureScope> featureScopes) { if (featureScopes == null) { return FeatureScope.Client; } foreach (FeatureScope featureScope in featureScopes) { if (featureScope == FeatureScope.Synced) { return FeatureScope.Synced; } } return FeatureScope.Client; } public static bool ShouldPatch(FeatureScope scope, bool activeAtStartup, bool requiredMembersPresent) { if (!requiredMembersPresent) { return false; } return scope == FeatureScope.Synced || activeAtStartup; } public static string Describe(string text, FeatureScope scope, bool requiresRestart, bool turningOnRequiresRestart = false) { string text2 = ((scope == FeatureScope.Synced) ? " Server-controlled when connected." : " Personal setting."); string text3 = (requiresRestart ? " Requires restart." : (turningOnRequiresRestart ? " Turning it on requires a restart." : "")); return text + text2 + text3; } } } namespace RossQoL.Core.Death { public static class CorpseRunStrength { public static float For(float distance, float minDistance, float fullDistance) { float num = Math.Max(0f, minDistance); float num2 = distance - num; if (num2 <= 0f) { return 0f; } if (fullDistance <= 0f) { return 1f; } return Clamp01(num2 / fullDistance); } public static float WithBaseline(float strength, float baseline) { float num = Clamp01(baseline); return num + (1f - num) * Clamp01(strength); } public static float RegenMultiplier(float strength, float maxRegenBonus) { return 1f + Clamp01(strength) * Math.Max(0f, maxRegenBonus); } public static float DrainModifier(float strength, float maxDrainReduction) { return (0f - Clamp01(strength)) * Clamp01(maxDrainReduction); } private static float Clamp01(float value) { if (!(value < 0f)) { if (!(value > 1f)) { return value; } return 1f; } return 0f; } } public static class CorpseRunTimeout { public static bool Expired(float startSeconds, float nowSeconds, float limitMinutes) { if (limitMinutes <= 0f) { return false; } return nowSeconds - startSeconds >= limitMinutes * 60f; } } public enum GraveAimTarget { Grave, Entrance } public static class GraveAim { public const float InteriorHeight = 3000f; public static bool InInterior(float y) { return y > 3000f; } public static GraveAimTarget Choose(float graveY, bool hasEntrance, float playerY) { if (!hasEntrance) { return GraveAimTarget.Grave; } if (!InInterior(graveY)) { return GraveAimTarget.Grave; } if (!InInterior(playerY)) { return GraveAimTarget.Entrance; } return GraveAimTarget.Grave; } } public enum GraveVerdict { Ignore, Observe, Forget } public static class GraveClearRule { public const float MatchRadius = 8f; public static bool Matches(float gx, float gy, float gz, float tx, float ty, float tz) { float num = gx - tx; float num2 = gy - ty; float num3 = gz - tz; return num * num + num2 * num2 + num3 * num3 <= 64f; } public static GraveVerdict Decide(bool inRange, bool tombstoneStanding, bool seenThisSession, bool armed) { if (!inRange) { return GraveVerdict.Ignore; } if (tombstoneStanding) { return GraveVerdict.Observe; } if (seenThisSession && armed) { return GraveVerdict.Forget; } return GraveVerdict.Ignore; } } public static class GraveConsumers { public const string GraveMarker = "GraveMarker"; public const string RespawnFood = "RespawnFood"; public const string RespawnRested = "RespawnRested"; public const string CorpseRun = "CorpseRun"; public static bool NeedsGraveRecord(string featureKey) { if (!(featureKey == "GraveMarker")) { return featureKey == "CorpseRun"; } return true; } public static bool NeedsDiedFlag(string featureKey) { if (!(featureKey == "RespawnFood")) { return featureKey == "RespawnRested"; } return true; } public static bool AnyNeedsGraveRecord(IEnumerable<string> activeFeatureKeys) { return Any(activeFeatureKeys, NeedsGraveRecord); } public static bool AnyNeedsDiedFlag(IEnumerable<string> activeFeatureKeys) { return Any(activeFeatureKeys, NeedsDiedFlag); } private static bool Any(IEnumerable<string> keys, Func<string, bool> wants) { if (keys == null) { return false; } foreach (string key in keys) { if (key != null && wants(key)) { return true; } } return false; } } public readonly struct GraveRecord { private const char FieldSeparator = ';'; private const int PlainFieldCount = 8; private const int WithEntranceFieldCount = 11; public long WorldId { get; } public float X { get; } public float Y { get; } public float Z { get; } public int Day { get; } public int Items { get; } public long ZdoUserId { get; } public uint ZdoId { get; } public bool HasEntrance { get; } public float EntranceX { get; } public float EntranceY { get; } public float EntranceZ { get; } public GraveRecord(long worldId, float x, float y, float z, int day, int items, long zdoUserId, uint zdoId) : this(worldId, x, y, z, day, items, zdoUserId, zdoId, hasEntrance: false, 0f, 0f, 0f) { } public GraveRecord(long worldId, float x, float y, float z, int day, int items, long zdoUserId, uint zdoId, float entranceX, float entranceY, float entranceZ) : this(worldId, x, y, z, day, items, zdoUserId, zdoId, hasEntrance: true, entranceX, entranceY, entranceZ) { } private GraveRecord(long worldId, float x, float y, float z, int day, int items, long zdoUserId, uint zdoId, bool hasEntrance, float entranceX, float entranceY, float entranceZ) { WorldId = worldId; X = x; Y = y; Z = z; Day = day; Items = items; ZdoUserId = zdoUserId; ZdoId = zdoId; HasEntrance = hasEntrance; EntranceX = entranceX; EntranceY = entranceY; EntranceZ = entranceZ; } public string Format() { string text = string.Join(';'.ToString(), WorldId.ToString(CultureInfo.InvariantCulture), X.ToString("R", CultureInfo.InvariantCulture), Y.ToString("R", CultureInfo.InvariantCulture), Z.ToString("R", CultureInfo.InvariantCulture), Day.ToString(CultureInfo.InvariantCulture), Items.ToString(CultureInfo.InvariantCulture), ZdoUserId.ToString(CultureInfo.InvariantCulture), ZdoId.ToString(CultureInfo.InvariantCulture)); if (!HasEntrance) { return text; } return string.Join(';'.ToString(), text, EntranceX.ToString("R", CultureInfo.InvariantCulture), EntranceY.ToString("R", CultureInfo.InvariantCulture), EntranceZ.ToString("R", CultureInfo.InvariantCulture)); } public static bool TryParse(string line, out GraveRecord record) { record = default(GraveRecord); if (string.IsNullOrWhiteSpace(line)) { return false; } string[] array = line.Split(';'); bool flag = array.Length == 11; if (array.Length != 8 && !flag) { return false; } CultureInfo invariantCulture = CultureInfo.InvariantCulture; if (!long.TryParse(array[0], NumberStyles.Integer, invariantCulture, out var result)) { return false; } if (!float.TryParse(array[1], NumberStyles.Float, invariantCulture, out var result2)) { return false; } if (!float.TryParse(array[2], NumberStyles.Float, invariantCulture, out var result3)) { return false; } if (!float.TryParse(array[3], NumberStyles.Float, invariantCulture, out var result4)) { return false; } if (!int.TryParse(array[4], NumberStyles.Integer, invariantCulture, out var result5)) { return false; } if (!int.TryParse(array[5], NumberStyles.Integer, invariantCulture, out var result6)) { return false; } if (!long.TryParse(array[6], NumberStyles.Integer, invariantCulture, out var result7)) { return false; } if (!uint.TryParse(array[7], NumberStyles.Integer, invariantCulture, out var result8)) { return false; } if (!flag) { record = new GraveRecord(result, result2, result3, result4, result5, result6, result7, result8); return true; } if (!float.TryParse(array[8], NumberStyles.Float, invariantCulture, out var result9)) { return false; } if (!float.TryParse(array[9], NumberStyles.Float, invariantCulture, out var result10)) { return false; } if (!float.TryParse(array[10], NumberStyles.Float, invariantCulture, out var result11)) { return false; } record = new GraveRecord(result, result2, result3, result4, result5, result6, result7, result8, result9, result10, result11); return true; } } public sealed class GraveSightings { private readonly HashSet<uint> _seen = new HashSet<uint>(); private long _worldId; public int Count => _seen.Count; public bool EnterWorld(long worldId) { if (worldId == _worldId) { return false; } _worldId = worldId; _seen.Clear(); return true; } public void Observe(uint zdoId) { _seen.Add(zdoId); } public bool HasSeen(uint zdoId) { return _seen.Contains(zdoId); } } public static class GraveStore { public const int DefaultPerWorld = 5; public const int DefaultTotal = 20; private const char LineSeparator = '\n'; public static IReadOnlyList<GraveRecord> Parse(string stored) { List<GraveRecord> list = new List<GraveRecord>(); if (string.IsNullOrEmpty(stored)) { return list; } string[] array = stored.Split('\n'); for (int i = 0; i < array.Length; i++) { if (GraveRecord.TryParse(array[i], out var record)) { list.Add(record); } } return list; } public static IReadOnlyList<GraveRecord> ForWorld(string stored, long worldId) { List<GraveRecord> list = new List<GraveRecord>(); foreach (GraveRecord item in Parse(stored)) { if (item.WorldId == worldId) { list.Add(item); } } return list; } public static string Add(string stored, GraveRecord record, int perWorld, int total) { List<GraveRecord> list = new List<GraveRecord>(Parse(stored)); list.Insert(0, record); int num = 0; int num2 = 0; while (num2 < list.Count) { if (list[num2].WorldId == record.WorldId && ++num > perWorld) { list.RemoveAt(num2); } else { num2++; } } while (list.Count > total) { list.RemoveAt(list.Count - 1); } return Format(list); } public static string Remove(string stored, long worldId, uint zdoId) { List<GraveRecord> list = new List<GraveRecord>(); foreach (GraveRecord item in Parse(stored)) { if (item.WorldId != worldId || item.ZdoId != zdoId) { list.Add(item); } } return Format(list); } private static string Format(IReadOnlyList<GraveRecord> records) { List<string> list = new List<string>(records.Count); foreach (GraveRecord record in records) { list.Add(record.Format()); } return string.Join('\n'.ToString(), list); } } public static class RespawnFoods { public const string DefaultTable = "Meadows:Honey|Raspberry|MushroomYellow,BlackForest:CarrotSoup|QueensJam|Honey,Swamp:TurnipStew|Sausages|CarrotSoup,Mountain:Eyescream|OnionSoup|TurnipStew,Plains:BloodPudding|Bread|Eyescream,Mistlands:FishAndBread|MushroomOmelette|Salad,Ashlands:RoastedCrustPie|ScorchingMedley|SpicyMarmalade,DeepNorth:OatmealLingonberryJam|OatMilk|KaleChips"; public static string Pick(string table, string tier, Func<string, bool> prefabExists) { if (prefabExists == null) { return null; } Dictionary<string, List<string>> dictionary = Parse(table); if (dictionary.Count == 0) { return null; } IReadOnlyList<string> tiers = WorldFrontier.Tiers; for (int num = IndexOf(tiers, tier); num >= 0; num--) { if (dictionary.TryGetValue(tiers[num], out var value)) { foreach (string item in value) { if (prefabExists(item)) { return item; } } } } return null; } private static int IndexOf(IReadOnlyList<string> tiers, string tier) { for (int i = 0; i < tiers.Count; i++) { if (string.Equals(tiers[i], tier, StringComparison.OrdinalIgnoreCase)) { return i; } } return 0; } private static Dictionary<string, List<string>> Parse(string table) { Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(table)) { return dictionary; } string[] array = table.Split(','); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(':'); if (array2.Length != 2) { continue; } string text = array2[0].Trim(); if (text.Length == 0) { continue; } List<string> list = new List<string>(); string[] array3 = array2[1].Split('|'); for (int j = 0; j < array3.Length; j++) { string text2 = array3[j].Trim(); if (text2.Length > 0) { list.Add(text2); } } if (list.Count > 0) { dictionary[text] = list; } } return dictionary; } } public readonly struct MarkerPlacement { public bool Visible { get; } public float X { get; } public float Y { get; } public bool OnScreen { get; } public float AngleDegrees { get; } public static MarkerPlacement Hidden => new MarkerPlacement(visible: false, 0f, 0f, onScreen: false, 0f); public MarkerPlacement(bool visible, float x, float y, bool onScreen, float angleDegrees) { Visible = visible; X = x; Y = y; OnScreen = onScreen; AngleDegrees = angleDegrees; } } public static class ScreenMarker { public static MarkerPlacement Place(float screenX, float screenY, float screenZ, float screenWidth, float screenHeight, float margin) { if (screenWidth <= 0f || screenHeight <= 0f) { return MarkerPlacement.Hidden; } float num = screenWidth / 2f; float num2 = screenHeight / 2f; float num3 = screenX; float num4 = screenY; if (screenZ < 0f) { num3 = num - (screenX - num); num4 = num2 - (screenY - num2); } if (screenZ >= 0f && num3 >= 0f && num3 <= screenWidth && num4 >= 0f && num4 <= screenHeight) { return new MarkerPlacement(visible: true, num3, num4, onScreen: true, 0f); } float num5 = Math.Min(margin, num * 0.9f); float num6 = Math.Min(margin, num2 * 0.9f); float num7 = num3 - num; float num8 = num4 - num2; if (num7 == 0f && num8 == 0f) { num8 = 1f; } float val = ((num7 == 0f) ? float.MaxValue : ((num - num5) / Math.Abs(num7))); float val2 = ((num8 == 0f) ? float.MaxValue : ((num2 - num6) / Math.Abs(num8))); float num9 = Math.Min(val, val2); float num10 = (float)(Math.Atan2(num7, num8) * 180.0 / Math.PI); if (num10 < 0f) { num10 += 360f; } return new MarkerPlacement(visible: true, num + num7 * num9, num2 + num8 * num9, onScreen: false, num10); } } } namespace RossQoL.Core.Crafting { public readonly struct TierChoice { public int Quality { get; } public int Available { get; } public TierChoice(int quality, int available) { Quality = quality; Available = available; } public bool Covers(int need) { return Available >= need; } } public static class CraftPayment { public static TierChoice ChooseTier(IReadOnlyList<int> heldByQuality, int need) { if (heldByQuality == null) { return new TierChoice(0, 0); } int quality = 0; int num = 0; for (int i = 0; i < heldByQuality.Count; i++) { int num2 = heldByQuality[i]; if (num2 > num) { num = num2; quality = i; } } if (need > 0) { for (int j = 0; j < heldByQuality.Count; j++) { if (heldByQuality[j] >= need) { return new TierChoice(j, num); } } } return new TierChoice(quality, num); } public static int Owing(int owed, int paidFromPack) { if (owed <= 0) { return 0; } int num = ((paidFromPack > 0) ? paidFromPack : 0); int num2 = owed - num; if (num2 <= 0) { return 0; } return num2; } public static int Shortfall(int owed, int paidFromPack, int takenFromContainers) { int num = Owing(owed, paidFromPack); int num2 = ((takenFromContainers > 0) ? takenFromContainers : 0); int num3 = num - num2; if (num3 <= 0) { return 0; } return num3; } } public static class RecipeCategories { private static readonly string[] None = Array.Empty<string>(); public static string[] WordsFor(string itemType) { switch (itemType) { case "Helmet": return new string[2] { "helmet", "armor" }; case "Chest": return new string[2] { "chest", "armor" }; case "Legs": return new string[2] { "legs", "armor" }; case "Shoulder": return new string[2] { "cape", "armor" }; case "Shield": return new string[1] { "shield" }; case "Utility": return new string[1] { "utility" }; case "Tool": return new string[1] { "tool" }; case "Torch": return new string[1] { "torch" }; case "Ammo": case "AmmoNonEquipable": return new string[1] { "ammo" }; case "Consumable": return new string[1] { "food" }; case "Material": return new string[1] { "material" }; case "Trinket": return new string[1] { "trinket" }; case "OneHandedWeapon": case "TwoHandedWeapon": case "TwoHandedWeaponLeft": case "Bow": return new string[1] { "weapon" }; default: return None; } } public static bool HasSkillWord(string itemType) { switch (itemType) { case "OneHandedWeapon": case "TwoHandedWeapon": case "TwoHandedWeaponLeft": case "Bow": case "Tool": return true; default: return false; } } } public static class RecipeSearch { public static bool IsActive(string term) { return Normalize(term).Length > 0; } public static bool Matches(string displayName, string term) { string text = Normalize(term); if (text.Length == 0) { return true; } if (displayName == null) { return false; } return Normalize(displayName).IndexOf(text, StringComparison.Ordinal) >= 0; } public static bool Matches(string displayName, IEnumerable<string> categoryWords, string term) { if (Matches(displayName, term)) { return true; } if (categoryWords == null) { return false; } string value = Normalize(term); foreach (string categoryWord in categoryWords) { if (categoryWord != null && Normalize(categoryWord).IndexOf(value, StringComparison.Ordinal) >= 0) { return true; } } return false; } private static string Normalize(string text) { if (string.IsNullOrEmpty(text)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(text.Length); foreach (char c in text) { if (!char.IsWhiteSpace(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } } public static class RequirementAmountFormat { public static string Format(int have, int need) { return have + "/" + need; } } } namespace RossQoL.Core.Combat { public sealed class SecondPower { private float _cooldown; public string Power { get; private set; } = ""; public bool HasPower => !string.IsNullOrEmpty(Power); public float Remaining { get { if (!(_cooldown > 0f)) { return 0f; } return _cooldown; } } public bool IsReady => _cooldown <= 0f; public bool Set(string power) { power = power ?? ""; if (power == Power) { return false; } Power = power; _cooldown = 0f; return true; } public bool Clear() { if (!HasPower) { return false; } Power = ""; _cooldown = 0f; return true; } public void StartCooldown(float seconds) { if (seconds > 0f) { _cooldown = seconds; } } public void Tick(float dt) { if (!(dt <= 0f) && !(_cooldown <= 0f)) { _cooldown -= dt; if (_cooldown < 0f) { _cooldown = 0f; } } } public string Serialize() { if (!HasPower) { return ""; } if (!(Remaining > 0f)) { return Power; } return Power + "|" + Remaining.ToString(CultureInfo.InvariantCulture); } public static SecondPower Parse(string data, Func<string, bool> isValid = null) { SecondPower secondPower = new SecondPower(); if (string.IsNullOrEmpty(data)) { return secondPower; } int num = data.IndexOf('|'); string text = ((num < 0) ? data : data.Substring(0, num)).Trim(); if (text.Length == 0) { return secondPower; } if (isValid != null && !isValid(text)) { return secondPower; } secondPower.Power = text; if (num >= 0 && float.TryParse(data.Substring(num + 1).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result > 0f) { secondPower._cooldown = result; } return secondPower; } } }
plugins/RossQoL.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GUIFramework; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using RossQoL.Core.Combat; using RossQoL.Core.Crafting; using RossQoL.Core.Death; using RossQoL.Core.Framework; using RossQoL.Core.Interface; using RossQoL.Core.Items; using RossQoL.Core.Portals; using RossQoL.Core.Production; using RossQoL.Core.Progression; using RossQoL.Core.Startup; using RossQoL.Core.Tames; using RossQoL.Core.Terrain; using RossQoL.Game.Combat; using RossQoL.Game.Crafting; using RossQoL.Game.Death; using RossQoL.Game.Fires; using RossQoL.Game.Framework; using RossQoL.Game.Interface; using RossQoL.Game.Items; using RossQoL.Game.Meals; using RossQoL.Game.Portals; using RossQoL.Game.Production; using RossQoL.Game.Progression; using RossQoL.Game.Startup; using RossQoL.Game.Tames; using RossQoL.Game.Terrain; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RossQoL")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+49848834b6e16f04c86962471088bb7e32df65a6")] [assembly: AssemblyProduct("RossQoL")] [assembly: AssemblyTitle("RossQoL")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RossQoL.Game { [BepInPlugin("com.rossdwest.rossqol", "RossQoL", "0.30.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class RossQoLPlugin : BaseUnityPlugin { public const string PluginGuid = "com.rossdwest.rossqol"; public const string PluginName = "RossQoL"; public const string PluginVersion = "0.30.0"; internal static ManualLogSource Log; private Harmony _harmony; private ConfigEntry<bool> _hotReloadEnabled; private ConfigHotReload _hotReload; private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("com.rossdwest.rossqol"); _hotReloadEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HotReload", true, ConfigText.Description("Apply edits to this file without a restart. On a server, any server-controlled settings changed this way are pushed to connected players. While connected to a server, local edits to server-controlled settings are ignored.", (FeatureScope)0, requiresRestart: false)); IReadOnlyList<Category> readOnlyList = FeatureRegistry.Create(); foreach (Category item in readOnlyList) { item.Bind(((BaseUnityPlugin)this).Config); } GameObject val = new GameObject("RossQoLManager"); Object.DontDestroyOnLoad((Object)(object)val); val.transform.SetParent(((Component)this).gameObject.transform); foreach (Category item2 in readOnlyList) { foreach (Feature feature in item2.Features) { FeatureActivator.Activate(feature, _harmony, val); } } try { _hotReload = new ConfigHotReload(((BaseUnityPlugin)this).Config, Log); } catch (Exception arg) { Log.LogError((object)$"Config reload unavailable; edits apply after a restart: {arg}"); } Log.LogInfo((object)"RossQoL 0.30.0 loaded"); } private void Update() { if (_hotReloadEnabled.Value) { _hotReload?.Pump(); } } private void OnDestroy() { _hotReload?.Dispose(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } FeatureActivator.Reset(); } } } namespace RossQoL.Game.Terrain { internal static class HeightLimitIL { private static bool IsActive => UnlimitedHeightFeature.Instance?.IsActive ?? false; public static float Raise() { return HeightLimits.Effective(IsActive, TerrainConfig.MaxRaise?.Value ?? 8f); } public static float Dig() { return HeightLimits.Effective(IsActive, TerrainConfig.MaxDig?.Value ?? 8f); } public static float NegativeDig() { return 0f - Dig(); } public static IEnumerable<CodeInstruction> Replace(IEnumerable<CodeInstruction> instructions, string where, params (float Value, string Method)[] expected) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); List<int> list2 = new List<int>(); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Ldc_R4 && list[i].operand is float value && Math.Abs(Math.Abs(value) - 8f) < 0.0001f) { list2.Add(i); } } if (list2.Count != expected.Length) { throw new InvalidOperationException($"{where}: expected {expected.Length} height limit constant(s), found {list2.Count}. " + "The game or another mod has changed this method; its terrain limit is left as it is."); } for (int j = 0; j < list2.Count; j++) { CodeInstruction val = list[list2[j]]; if (Math.Abs((float)val.operand - expected[j].Value) > 0.0001f) { throw new InvalidOperationException($"{where}: height limit constant {j + 1} is {val.operand}, expected {expected[j].Value}. " + "Its terrain limit is left as it is."); } val.opcode = OpCodes.Call; val.operand = AccessTools.Method(typeof(HeightLimitIL), expected[j].Method, (Type[])null, (Type[])null); } return list; } } [HarmonyPatch(typeof(TerrainComp), "ApplyToHeightmap")] internal static class ApplyToHeightmapLimitPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(TerrainComp), "ApplyToHeightmap", "Terrain/UnlimitedHeight"); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return HeightLimitIL.Replace(instructions, "TerrainComp.ApplyToHeightmap", (8f, "Dig"), (8f, "Raise")); } } [HarmonyPatch(typeof(TerrainComp), "LevelTerrain")] internal static class LevelTerrainLimitPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(TerrainComp), "LevelTerrain", "Terrain/UnlimitedHeight"); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return HeightLimitIL.Replace(instructions, "TerrainComp.LevelTerrain", (-8f, "NegativeDig"), (8f, "Raise")); } } [HarmonyPatch(typeof(TerrainComp), "RaiseTerrain")] internal static class RaiseTerrainLimitPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(TerrainComp), "RaiseTerrain", "Terrain/UnlimitedHeight"); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return HeightLimitIL.Replace(instructions, "TerrainComp.RaiseTerrain", (-8f, "NegativeDig"), (8f, "Raise")); } } public static class TerrainConfig { public static ConfigEntry<float> MaxRaise; public static ConfigEntry<float> MaxDig; internal static void Bind(ConfigFile config, string section, FeatureScope scope) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) AcceptableValueRange<float> range = new AcceptableValueRange<float>(1f, 200f); MaxRaise = config.Bind<float>(section, "MaxRaise", 200f, ConfigText.Description("How high, in metres, ground can be raised above its original height. Vanilla is 8.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)range)); MaxDig = config.Bind<float>(section, "MaxDig", 200f, ConfigText.Description("How deep, in metres, ground can be dug below its original height. Vanilla is 8.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)range)); } } internal sealed class UnlimitedHeightFeature : Feature { public const string FeatureName = "Terrain/UnlimitedHeight"; public static UnlimitedHeightFeature Instance { get; private set; } public override string Key => "UnlimitedHeight"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Raise and dig terrain beyond vanilla's 8 metres from the original ground, up to MaxRaise and MaxDig. Edits past 8 metres are saved in the world: with this off, or RossQoL removed, that ground is drawn at 8 metres until it is turned on again. Editing ground while this is off, or after lowering MaxRaise or MaxDig, permanently cuts nearby deeper edits down to the lower limit."; public override IEnumerable<Type> PatchClasses => new Type[3] { typeof(ApplyToHeightmapLimitPatch), typeof(LevelTerrainLimitPatch), typeof(RaiseTerrainLimitPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[3] { new CompatMember("TerrainComp", "ApplyToHeightmap", "drawing edited ground past 8 metres"), new CompatMember("TerrainComp", "LevelTerrain", "levelling ground past 8 metres"), new CompatMember("TerrainComp", "RaiseTerrain", "raising and digging ground past 8 metres") }; public UnlimitedHeightFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) TerrainConfig.Bind(config, section, Scope); } } } namespace RossQoL.Game.Tames { internal sealed class CullWoundedSummonsFeature : Feature { public const string FeatureName = "Tames/CullWoundedSummons"; public static CullWoundedSummonsFeature Instance { get; private set; } public override string Key => "CullWoundedSummons"; public override FeatureScope Scope => (FeatureScope)0; public override string Description => "When raising a new skeleton puts you over your summon limit, the one that disappears is the most badly wounded rather than the oldest. Equally hurt summons fall back to the oldest, as vanilla does. How many you may have at once is unchanged."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(CullWoundedSummonsPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[4] { new CompatMember("Tameable", "UnsummonMaxInstances", "the summon cap whose choice of which creature to despawn is being reordered"), new CompatMember("BaseAI", "GetTimeSinceSpawned", "breaking a tie between two equally wounded summons by age, as vanilla orders them"), new CompatMember("Character", "GetHealth", "reading how hurt a summon is"), new CompatMember("Character", "GetMaxHealth", "turning that health into a fraction, so a starred creature's larger pool is accounted for") }; public CullWoundedSummonsFeature() { Instance = this; } } [HarmonyPatch(typeof(Tameable), "UnsummonMaxInstances")] internal static class CullWoundedSummonsPatch { private const string MethodName = "UnsummonMaxInstances"; private static readonly MethodInfo VanillaSort = AccessTools.Method(typeof(List<BaseAI>), "Sort", new Type[1] { typeof(Comparison<BaseAI>) }, (Type[])null); private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Tameable), "UnsummonMaxInstances", "Tames/CullWoundedSummons"); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); if (VanillaSort == null) { throw new InvalidOperationException("Tameable.UnsummonMaxInstances: List<BaseAI>.Sort(Comparison<BaseAI>) could not be resolved. The summon cap keeps vanilla's oldest-first order."); } List<int> list2 = new List<int>(); for (int i = 0; i < list.Count; i++) { if ((list[i].opcode == OpCodes.Callvirt || list[i].opcode == OpCodes.Call) && list[i].operand is MethodInfo methodInfo && methodInfo == VanillaSort) { list2.Add(i); } } if (list2.Count != 1) { throw new InvalidOperationException($"Tameable.UnsummonMaxInstances: expected exactly 1 call to List<BaseAI>.Sort, found {list2.Count}. " + "The game or another mod has changed this method; the summon cap keeps vanilla's oldest-first order."); } CodeInstruction obj = list[list2[0]]; obj.opcode = OpCodes.Call; obj.operand = AccessTools.Method(typeof(CullWoundedSummonsPatch), "SortWoundedFirst", (Type[])null, (Type[])null); return list; } public static void SortWoundedFirst(List<BaseAI> list, Comparison<BaseAI> vanillaOrder) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (list == null) { return; } try { CullWoundedSummonsFeature instance = CullWoundedSummonsFeature.Instance; if (instance == null || !instance.IsActive) { SortVanilla(list, vanillaOrder); return; } List<SummonCullCandidate> list2 = new List<SummonCullCandidate>(list.Count); for (int i = 0; i < list.Count; i++) { list2.Add(Describe(list[i])); } List<int> list3 = SummonCullOrder.Order((IReadOnlyList<SummonCullCandidate>)list2); List<BaseAI> list4 = new List<BaseAI>(list.Count); for (int j = 0; j < list3.Count; j++) { list4.Add(list[list3[j]]); } list.Clear(); list.AddRange(list4); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)("CullWoundedSummons: could not order the summons by how hurt they are, " + $"leaving the cap to despawn the oldest as vanilla does: {arg}")); SortVanilla(list, vanillaOrder); } } private static void SortVanilla(List<BaseAI> list, Comparison<BaseAI> vanillaOrder) { if (list == null || vanillaOrder == null) { return; } try { list.Sort(vanillaOrder); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"CullWoundedSummons: vanilla's own summon ordering failed: {arg}"); } } private static SummonCullCandidate Describe(BaseAI ai) { //IL_001c: 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_004c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ai == (Object)null) { return new SummonCullCandidate(0f, 0f, 0.0); } double totalSeconds = ai.GetTimeSinceSpawned().TotalSeconds; Character component = ((Component)ai).GetComponent<Character>(); if ((Object)(object)component == (Object)null) { return new SummonCullCandidate(0f, 0f, totalSeconds); } return new SummonCullCandidate(component.GetHealth(), component.GetMaxHealth(), totalSeconds); } } public static class FeedConfig { public static ConfigEntry<float> FeedRadius; internal static void Bind(ConfigFile config, string section, FeatureScope scope) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) FeedRadius = config.Bind<float>(section, "FeedRadius", 10f, ConfigText.Description("How far from a hungry tame, in metres, to look for food in containers. Measured in three dimensions.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f))); } } internal sealed class FeedFromContainersFeature : Feature { public const string FeatureName = "Tames/FeedFromContainers"; public static FeedFromContainersFeature Instance { get; private set; } public override string Key => "FeedFromContainers"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "A hungry tame with no food on the ground near it eats one item it likes from a container within FeedRadius. Only creatures that are already tame."; public override IEnumerable<Type> PatchClasses => new Type[2] { typeof(ContainerAwakeRegistryPatch), typeof(FeedFromContainersPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[24] { new CompatMember("Container", "Awake", "finding the containers food can come from"), new CompatMember("Container", "m_nview", "checking who owns a container"), new CompatMember("Container", "CheckAccess", "respecting private containers"), new CompatMember("Container", "m_privacy", "respecting private containers"), new CompatMember("Container", "m_piece", "respecting private containers"), new CompatMember("Container", "m_checkGuardStone", "respecting wards"), new CompatMember("ZDO", "DataRevision", "reading a container's latest contents"), new CompatMember("ZDO", "OwnerRevision", "waiting for a container's ownership to settle"), new CompatMember("Container", "IsInUse", "leaving open containers alone"), new CompatMember("Container", "GetInventory", "taking food from containers"), new CompatMember("Container", "Load", "reading a container's latest contents"), new CompatMember("Container", "m_lastRevision", "reading a container's latest contents"), new CompatMember("Inventory", "RemoveOneItem", "taking one item of food"), new CompatMember("Inventory", "CountItems", "confirming the food was taken"), new CompatMember("PrivateArea", "CheckAccess", "respecting wards"), new CompatMember("MonsterAI", "UpdateConsumeItem", "the moment a tame looks for food"), new CompatMember("MonsterAI", "m_consumeItems", "what a tame eats"), new CompatMember("MonsterAI", "m_consumeSearchTimer", "looking in containers only when vanilla looks on the ground"), new CompatMember("MonsterAI", "m_onConsumedItem", "feeding a tame the way eating does"), new CompatMember("BaseAI", "m_tamable", "only tame creatures eat from containers"), new CompatMember("BaseAI", "m_nview", "only the tame's owner feeds it"), new CompatMember("BaseAI", "m_animator", "playing the eating animation"), new CompatMember("Humanoid", "m_consumeItemEffects", "playing the eating effect"), new CompatMember("Tameable", "IsHungry", "feeding only hungry tames") }; public FeedFromContainersFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) FeedConfig.Bind(config, section, Scope); } } [HarmonyPatch(typeof(MonsterAI), "UpdateConsumeItem")] internal static class FeedFromContainersPatch { private static readonly List<Container> Nearby = new List<Container>(); private static readonly HashSet<string> LoggedFailures = new HashSet<string>(); private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(MonsterAI), "UpdateConsumeItem", "Tames/FeedFromContainers"); } private static void Postfix(MonsterAI __instance, Humanoid humanoid, bool __result) { FeedFromContainersFeature instance = FeedFromContainersFeature.Instance; if (instance == null || !instance.IsActive || __result) { return; } try { if (__instance.m_consumeSearchTimer != 0f || (Object)(object)Player.m_localPlayer == (Object)null) { return; } Tameable tamable = ((BaseAI)__instance).m_tamable; if ((Object)(object)tamable == (Object)null || !tamable.IsTamed() || !tamable.IsHungry()) { return; } ZNetView nview = ((BaseAI)__instance).m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { List<ItemDrop> consumeItems = __instance.m_consumeItems; if (consumeItems != null && consumeItems.Count != 0) { FeedFromNearestContainer(__instance, humanoid, consumeItems); } } } catch (Exception ex) { string fullName = ex.GetType().FullName; if (LoggedFailures.Add(fullName)) { RossQoLPlugin.Log.LogError((object)$"FeedFromContainers: feeding {((Object)__instance).name} failed and was skipped (further {fullName} failures are not logged): {ex}"); } } } private static void FeedFromNearestContainer(MonsterAI ai, Humanoid humanoid, List<ItemDrop> foods) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0181: 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) Vector3 position = ((Component)ai).transform.position; float radius = FeedConfig.FeedRadius?.Value ?? 10f; long playerID = Game.instance.GetPlayerProfile().GetPlayerID(); ContainerRegistry.Near(position, radius, Nearby); Inventory val = null; ItemData val2 = null; float num = float.MaxValue; foreach (Container item in Nearby) { Vector3 val3 = ((Component)item).transform.position - position; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude >= num || !ContainerAccess.MayUse(item, playerID) || !ContainerOwnership.IsSettled(item, item.m_nview) || !ContainerAccess.IsFresh(item)) { continue; } Inventory inventory = item.GetInventory(); if (inventory != null) { ItemData val4 = FindFood(inventory, foods); if (val4 != null) { val = inventory; val2 = val4; num = sqrMagnitude; } } } if (val == null) { return; } string name = val2.m_shared.m_name; int num2 = val.CountItems(name, -1, false); val.RemoveOneItem(val2); int num3 = num2 - val.CountItems(name, -1, false); if (num3 != 1) { if (num3 > 1) { RossQoLPlugin.Log.LogWarning((object)$"FeedFromContainers: {num3} {name} left a container for one feeding."); } if (num3 <= 0) { return; } } ItemDrop obj = FoodDrop(val2, foods); ai.m_onConsumedItem?.Invoke(obj); humanoid.m_consumeItemEffects.Create(((Component)ai).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); ((BaseAI)ai).m_animator.SetTrigger("consume"); } private static ItemData FindFood(Inventory inventory, List<ItemDrop> foods) { foreach (ItemData allItem in inventory.GetAllItems()) { foreach (ItemDrop food in foods) { if ((Object)(object)food != (Object)null && food.m_itemData.m_shared.m_name == allItem.m_shared.m_name) { return allItem; } } } return null; } private static ItemDrop FoodDrop(ItemData item, List<ItemDrop> foods) { if ((Object)(object)item.m_dropPrefab != (Object)null) { ItemDrop component = item.m_dropPrefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { return component; } } foreach (ItemDrop food in foods) { if ((Object)(object)food != (Object)null && food.m_itemData.m_shared.m_name == item.m_shared.m_name) { return food; } } return foods[0]; } } internal sealed class FollowCommandFeature : Feature { public const string FeatureName = "Tames/FollowCommand"; public static FollowCommandFeature Instance { get; private set; } public override string Key => "FollowCommand"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Every tamed creature can be told to follow you or stay, like a wolf: press Use on it to switch. Creatures vanilla already lets you command are unchanged."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(FollowCommandPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[3] { new CompatMember("Tameable", "Interact", "switching follow and stay when a tame is used"), new CompatMember("Tameable", "m_commandable", "which tames vanilla lets you command"), new CompatMember("Tameable", "m_monsterAI", "only creatures that can follow are commanded") }; public FollowCommandFeature() { Instance = this; } } [HarmonyPatch(typeof(Tameable), "Interact")] internal static class FollowCommandPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Tameable), "Interact", "Tames/FollowCommand"); } private static void Prefix(Tameable __instance, ref bool __state) { __state = false; FollowCommandFeature instance = FollowCommandFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (!__instance.m_commandable && !((Object)(object)__instance.m_monsterAI == (Object)null) && __instance.IsTamed()) { __instance.m_commandable = true; __state = true; } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"FollowCommand: preparing a tame's command failed: {arg}"); } } private static void Finalizer(Tameable __instance, bool __state) { if (__state && Object.op_Implicit((Object)(object)__instance)) { __instance.m_commandable = false; } } } internal sealed class NoSummonCommandsFeature : Feature { public const string FeatureName = "Tames/NoSummonCommands"; public static NoSummonCommandsFeature Instance { get; private set; } public override string Key => "NoSummonCommands"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Summoned creatures -- raised skeletons and the Spirit Caller's spirits -- cannot be petted or told to stay, so they always follow you and always count against how many you may have at once. Tamed creatures are unaffected."; public override IEnumerable<Type> PatchClasses => new Type[2] { typeof(NoSummonCommandsPatch), typeof(NoSummonCommandsHoverPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[7] { new CompatMember("Tameable", "Interact", "the petting and follow/stay command a raised skeleton loses"), new CompatMember("Tameable", "GetHoverText", "removing the prompt for an interaction that no longer happens"), new CompatMember("Tameable", "IsTamed", "leaving the hover text alone for anything not shown as tame"), new CompatMember("Tameable", "GetName", "rebuilding the hover text without the prompt"), new CompatMember("Tameable", "GetStatusString", "rebuilding the hover text without the prompt"), new CompatMember("Tameable", "m_character", "telling a raised skeleton from every other tame"), new CompatMember("Localization", "Localize", "showing the creature's status in the player's language") }; public NoSummonCommandsFeature() { Instance = this; } } [HarmonyPatch(typeof(Tameable), "GetHoverText")] internal static class NoSummonCommandsHoverPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Tameable), "GetHoverText", "Tames/NoSummonCommands"); } private static void Postfix(Tameable __instance, ref string __result) { NoSummonCommandsFeature instance = NoSummonCommandsFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (!((Object)(object)__instance == (Object)null) && !string.IsNullOrEmpty(__result) && SummonedMinion.Is(((Component)__instance).gameObject) && __instance.IsTamed()) { string text = __instance.GetName(); if ((Object)(object)__instance.m_character != (Object)null) { text += Localization.instance.Localize(" ( $hud_tame, " + __instance.GetStatusString() + " )"); } __result = text; } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"NoSummonCommands: could not rewrite a hover text, leaving it alone: {arg}"); } } } [HarmonyPatch(typeof(Tameable), "Interact")] internal static class NoSummonCommandsPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Tameable), "Interact", "Tames/NoSummonCommands"); } private static bool Prefix(Tameable __instance, ref bool __result) { NoSummonCommandsFeature instance = NoSummonCommandsFeature.Instance; if (instance == null || !instance.IsActive) { return true; } try { if ((Object)(object)__instance == (Object)null) { return true; } if (!SummonedMinion.Is(((Component)__instance).gameObject)) { return true; } __result = false; return false; } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"NoSummonCommands: could not check a creature, leaving the interaction alone: {arg}"); return true; } } } internal sealed class QuietChickensFeature : Feature { public const string FeatureName = "Tames/QuietChickens"; public static QuietChickensFeature Instance { get; private set; } public override string Key => "QuietChickens"; public override FeatureScope Scope => (FeatureScope)0; public override string Description => "Chicks and hens make no sound: no peeping, clucking, wing flapping, pecking, mating, footsteps, hurt or death. Other creatures keep their own sounds."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(QuietChickensPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[1] { new CompatMember("ZSFX", "Play", "silencing a chicken sound whoever owns the bird") }; public QuietChickensFeature() { Instance = this; } } [HarmonyPatch(typeof(ZSFX), "Play")] internal static class QuietChickensPatch { private static readonly string[] SoundPrefabs = new string[2] { "sfx_chick", "fx_hen_love" }; private static readonly string[] BirdPrefabs = new string[2] { "Chicken", "Hen" }; private const int MaxDepth = 6; private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(ZSFX), "Play", "Tames/QuietChickens"); } private static bool Prefix(ZSFX __instance) { QuietChickensFeature instance = QuietChickensFeature.Instance; if (instance == null || !instance.IsActive) { return true; } try { if ((Object)(object)__instance == (Object)null) { return true; } return !IsChickenSound(__instance); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"QuietChickens: could not check a sound, leaving it alone: {arg}"); return true; } } private static bool IsChickenSound(ZSFX sfx) { Transform val = ((Component)sfx).transform; int num = 0; while ((Object)(object)val != (Object)null && num < 6) { string prefabName = Utils.GetPrefabName(((Component)val).gameObject); string[] soundPrefabs = SoundPrefabs; foreach (string value in soundPrefabs) { if (prefabName.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { return true; } } soundPrefabs = BirdPrefabs; foreach (string b in soundPrefabs) { if (string.Equals(prefabName, b, StringComparison.OrdinalIgnoreCase)) { return true; } } num++; val = val.parent; } return false; } } internal sealed class QuietWolvesFeature : Feature { public const string FeatureName = "Tames/QuietWolves"; public static QuietWolvesFeature Instance { get; private set; } public override string Key => "QuietWolves"; public override FeatureScope Scope => (FeatureScope)0; public override string Description => "Tamed wolves stop howling. Wild wolves still howl, and other creatures keep their own sounds."; public override IEnumerable<Type> PatchClasses => new Type[2] { typeof(QuietWolvesPatch), typeof(QuietWolvesSoundPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[5] { new CompatMember("BaseAI", "DoIdleSound", "where a creature's idle sound is played"), new CompatMember("BaseAI", "m_character", "telling a tamed wolf from a wild one"), new CompatMember("Character", "IsTamed", "telling a tamed wolf from a wild one"), new CompatMember("ZSFX", "Play", "silencing a howl another client sent"), new CompatMember("Character", "GetAllCharacters", "finding the wolf a howl came from") }; public QuietWolvesFeature() { Instance = this; } } [HarmonyPatch(typeof(BaseAI), "DoIdleSound")] internal static class QuietWolvesPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(BaseAI), "DoIdleSound", "Tames/QuietWolves"); } private static bool Prefix(BaseAI __instance) { QuietWolvesFeature instance = QuietWolvesFeature.Instance; if (instance == null || !instance.IsActive) { return true; } try { Character character = __instance.m_character; if ((Object)(object)character == (Object)null || !character.IsTamed()) { return true; } if (!Utils.GetPrefabName(((Component)__instance).gameObject).StartsWith("Wolf", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"QuietWolves: could not check a creature, leaving its sound alone: {arg}"); return true; } } } [HarmonyPatch(typeof(ZSFX), "Play")] internal static class QuietWolvesSoundPatch { private const string HowlPrefab = "sfx_wolf_haul"; private const float WolfSearchRadius = 8f; private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(ZSFX), "Play", "Tames/QuietWolves"); } private static bool Prefix(ZSFX __instance) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) QuietWolvesFeature instance = QuietWolvesFeature.Instance; if (instance == null || !instance.IsActive) { return true; } try { if ((Object)(object)__instance == (Object)null) { return true; } if (!((Object)((Component)__instance).gameObject).name.StartsWith("sfx_wolf_haul", StringComparison.Ordinal)) { return true; } Character val = NearestWolf(((Component)__instance).transform.position); if ((Object)(object)val == (Object)null) { return true; } return !val.IsTamed(); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"QuietWolves: could not check a howl, leaving it alone: {arg}"); return true; } } private static Character NearestWolf(Vector3 position) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) Character result = null; float num = 64f; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && Utils.GetPrefabName(((Component)allCharacter).gameObject).StartsWith("Wolf", StringComparison.OrdinalIgnoreCase)) { Vector3 val = ((Component)allCharacter).transform.position - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (!(sqrMagnitude > num)) { num = sqrMagnitude; result = allCharacter; } } } return result; } } internal sealed class SilentBirthsFeature : Feature { public const string FeatureName = "Tames/SilentBirths"; public static SilentBirthsFeature Instance { get; private set; } public override string Key => "SilentBirths"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Tames give birth without the birth sound. The birth's other effects still play."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(SilentBirthsPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[3] { new CompatMember("Procreation", "Procreate", "the moment a tame gives birth"), new CompatMember("Procreation", "m_birthEffects", "the effects played at a birth"), new CompatMember("EffectList", "m_effectPrefabs", "leaving out the sound from a birth's effects") }; public SilentBirthsFeature() { Instance = this; } } [HarmonyPatch(typeof(Procreation), "Procreate")] internal static class SilentBirthsPatch { private static readonly ConditionalWeakTable<EffectList, EffectList> SilentCopies = new ConditionalWeakTable<EffectList, EffectList>(); private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Procreation), "Procreate", "Tames/SilentBirths"); } private static void Prefix(Procreation __instance, ref EffectList __state) { __state = null; SilentBirthsFeature instance = SilentBirthsFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { EffectList birthEffects = __instance.m_birthEffects; if (birthEffects != null) { __state = birthEffects; __instance.m_birthEffects = SilentCopies.GetValue(birthEffects, MakeSilent); } } catch (Exception arg) { __state = null; RossQoLPlugin.Log.LogError((object)$"SilentBirths: preparing a silent birth failed: {arg}"); } } private static void Finalizer(Procreation __instance, EffectList __state) { if (__state != null && Object.op_Implicit((Object)(object)__instance)) { __instance.m_birthEffects = __state; } } private static EffectList MakeSilent(EffectList original) { //IL_0010: 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_0046: Expected O, but got Unknown EffectData[] source = original.m_effectPrefabs ?? Array.Empty<EffectData>(); return new EffectList { m_effectPrefabs = source.Where((EffectData e) => e == null || !IsSound(e.m_prefab)).ToArray() }; } private static bool IsSound(GameObject prefab) { if ((Object)(object)prefab != (Object)null) { return (Object)(object)prefab.GetComponentInChildren<ZSFX>(true) != (Object)null; } return false; } } } namespace RossQoL.Game.Startup { internal sealed class ContinueButtonFeature : Feature { public const string FeatureName = "Startup/ContinueButton"; public static ContinueButtonFeature Instance { get; private set; } public override string Key => "ContinueButton"; public override FeatureScope Scope => (FeatureScope)0; public override string Description => "Adds a Continue button to the main menu that resumes your last world or server with the character you used. Local worlds resume private; server passwords are never stored."; public override IEnumerable<Type> PatchClasses => new Type[3] { typeof(JoinServerRecordingPatch), typeof(WorldStartRecordingPatch), typeof(ContinueButtonPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[12] { new CompatMember("FejdStartup", "JoinServer", "recording which server you joined"), new CompatMember("FejdStartup", "GetServerToJoin", "recording which server you joined"), new CompatMember("FejdStartup", "OnWorldStart", "recording and resuming local worlds"), new CompatMember("Game", "m_playerInitialSpawn", "knowing a session actually started"), new CompatMember("FejdStartup", "SetupGui", "adding the button to the main menu"), new CompatMember("FejdStartup", "OnStartGame", "finding Start game, and the fallback when a session cannot resume"), new CompatMember("FejdStartup", "SelectCharacter", "selecting the recorded character"), new CompatMember("FejdStartup", "SetServerToJoin", "joining the recorded server"), new CompatMember("FejdStartup", "m_menuButtons", "keyboard and gamepad navigation of the new button"), new CompatMember("FejdStartup", "m_world", "resuming a local world"), new CompatMember("FejdStartup", "m_profileIndex", "selecting the recorded character"), new CompatMember("ZPlayFabMatchmaking", "ResolveJoinCode", "rejoining crossplay servers by join code") }; public ContinueButtonFeature() { Instance = this; } public override void OnActivated(GameObject host) { SessionRecorder.Subscribe(); } } [HarmonyPatch(typeof(FejdStartup), "SetupGui")] internal static class ContinueButtonPatch { private const string ButtonName = "RossQoL_Continue"; private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(FejdStartup), "SetupGui", "Startup/ContinueButton"); } private static void Postfix(FejdStartup __instance) { ContinueButtonFeature instance = ContinueButtonFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { AddButton(__instance); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"Continue: could not add the main menu button: {arg}"); } } private static void AddButton(FejdStartup menu) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Invalid comparison between Unknown and I4 //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Expected O, but got Unknown GameObject menuList = menu.m_menuList; if ((Object)(object)menuList == (Object)null) { return; } Button[] componentsInChildren = menuList.GetComponentsInChildren<Button>(true); Button[] array = componentsInChildren; for (int i = 0; i < array.Length; i++) { if (((Object)array[i]).name == "RossQoL_Continue") { return; } } ManualLogSource log = RossQoLPlugin.Log; LastSession session = SessionStore.Load(); PlayerProfile val = ContinueLauncher.FindProfile(SaveSystem.GetAllPlayerProfiles(), session); LastSession obj = session; bool flag = obj != null && (int)obj.Kind == 0 && ContinueLauncher.FindWorld(session) != null; if (!ContinuePolicy.ShouldShow(session, val != null, flag, ContinuePolicy.HasJoinArguments((IEnumerable<string>)Environment.GetCommandLineArgs()))) { log.LogInfo((object)((session == null) ? "Continue: no previous session recorded; button hidden." : $"Continue: {session} cannot be resumed right now; button hidden.")); return; } Button val2 = FindStartGameButton(componentsInChildren); if ((Object)(object)val2 == (Object)null) { log.LogWarning((object)"Continue: could not find the Start game button; button not added."); return; } GameObject val3 = Object.Instantiate<GameObject>(((Component)val2).gameObject, ((Component)val2).transform.parent); try { ((Object)val3).name = "RossQoL_Continue"; val3.transform.SetSiblingIndex(((Component)val2).transform.GetSiblingIndex()); Localize[] componentsInChildren2 = val3.GetComponentsInChildren<Localize>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren2[i]); } TMP_Text componentInChildren = val3.GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = ContinuePolicy.Label(val.GetName(), session); } Button button = val3.GetComponent<Button>(); button.onClick = new ButtonClickedEvent(); ((UnityEvent)button.onClick).AddListener((UnityAction)delegate { ContinueLauncher.Resume(menu, session, button); }); menu.m_menuButtons = menuList.GetComponentsInChildren<Button>(); if ((Object)(object)menu.m_merchStoreButton != (Object)null) { GuiUtils.SetNavigationRight((Selectable)(object)button, (Selectable)(object)menu.m_merchStoreButton); } log.LogInfo((object)$"Continue: button added for {session}."); } catch { Object.Destroy((Object)(object)val3); throw; } } private static Button FindStartGameButton(Button[] buttons) { Button[] array = buttons; foreach (Button val in array) { for (int j = 0; j < ((UnityEventBase)val.onClick).GetPersistentEventCount(); j++) { if (((UnityEventBase)val.onClick).GetPersistentMethodName(j) == "OnStartGame") { return val; } } } array = buttons; foreach (Button val2 in array) { if (((Component)val2).gameObject.activeInHierarchy) { return val2; } } return null; } } internal static class ContinueLauncher { private static Button _button; public static PlayerProfile FindProfile(List<PlayerProfile> profiles, LastSession session) { if (profiles == null || session == null) { return null; } return profiles.Find((PlayerProfile p) => p.GetFilename() == session.CharacterFile && ((object)Unsafe.As<FileSource, FileSource>(ref p.m_fileSource)/*cast due to .constrained prefix*/).ToString() == session.CharacterSource); } public static World FindWorld(LastSession session) { if (session == null) { return null; } return SaveSystem.GetWorldList().Find((World w) => w.m_name == session.WorldName && ((object)Unsafe.As<FileSource, FileSource>(ref w.m_fileSource)/*cast due to .constrained prefix*/).ToString() == session.WorldSource && (int)w.m_dataError == 0); } public static void Resume(FejdStartup menu, LastSession session, Button button) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) _button = button; if ((Object)(object)_button != (Object)null) { ((Selectable)_button).interactable = false; } List<PlayerProfile> allPlayerProfiles = SaveSystem.GetAllPlayerProfiles(); PlayerProfile val = FindProfile(allPlayerProfiles, session); if (val == null) { FallBack(menu, "the recorded character is gone"); return; } menu.m_profiles = allPlayerProfiles; menu.m_profileIndex = allPlayerProfiles.IndexOf(val); menu.SelectCharacter(val.GetFilename(), val.m_fileSource); if ((int)session.Kind == 0) { StartLocalWorld(menu, session); } else { JoinServer(menu, session); } } private static void StartLocalWorld(FejdStartup menu, LastSession session) { World val = FindWorld(session); if (val == null) { FallBack(menu, "the recorded world can no longer be loaded"); return; } menu.m_world = val; menu.m_openServerToggle.SetIsOnWithoutNotify(false); menu.m_publicServerToggle.SetIsOnWithoutNotify(false); int num = PlatformPrefs.GetInt("crossplay", 1); menu.m_crossplayServerToggle.SetIsOnWithoutNotify(false); ((TMP_InputField)menu.m_serverPassword).text = ""; RossQoLPlugin.Log.LogInfo((object)$"Continue: starting {session}."); menu.OnWorldStart(); if (((Selectable)menu.m_crossplayServerToggle).IsInteractable()) { PlatformPrefs.SetInt("crossplay", num); PlatformPrefs.Save(); } ((MonoBehaviour)menu).StartCoroutine(ReenableAfterDelay(_button)); } private static void JoinServer(FejdStartup menu, LastSession session) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0046: Expected I4, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_0173: 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_0189: Expected O, but got Unknown //IL_0189: Expected O, but got Unknown ServerKind serverKind = session.ServerKind; switch (serverKind - 1) { case 0: Join(menu, new ServerJoinData(new ServerJoinDataDedicated(session.ServerAddress)), session); break; case 1: { if (!ulong.TryParse(session.ServerAddress, NumberStyles.None, CultureInfo.InvariantCulture, out var result)) { FallBack(menu, "'" + session.ServerAddress + "' is not a Steam id"); } else { Join(menu, new ServerJoinData(new ServerJoinDataSteamUser(result)), session); } break; } case 2: { ServerJoinData byId = new ServerJoinData(new ServerJoinDataPlayFabUser(session.ServerAddress)); if (session.JoinCode.Length == 0 || !PlayFabManager.IsLoggedIn) { Join(menu, byId, session); break; } ZPlayFabMatchmaking.ResolveJoinCode(session.JoinCode, (ZPlayFabMatchmakingSuccessCallback)delegate(PlayFabMatchmakingServerData data) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) Join(menu, new ServerJoinData(new ServerJoinDataPlayFabUser(data.remotePlayerId)), session); }, (ZPlayFabMatchmakingFailedCallback)delegate(ZPLayFabMatchmakingFailReason reason) { //IL_001a: 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) RossQoLPlugin.Log.LogInfo((object)$"Continue: join code {session.JoinCode} no longer resolves ({reason}); trying the server's id."); Join(menu, byId, session); }); break; } default: FallBack(menu, $"unknown server kind {session.ServerKind}"); break; } } private static void Join(FejdStartup menu, ServerJoinData data, LastSession session) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)menu == (Object)null || !menu.m_mainMenu.activeInHierarchy) { RossQoLPlugin.Log.LogInfo((object)$"Continue: {session} resolved after the main menu was left; not joining."); if ((Object)(object)menu != (Object)null) { ((MonoBehaviour)menu).StartCoroutine(ReenableAfterDelay(_button)); } } else if (!((ServerJoinData)(ref data)).IsValid) { FallBack(menu, "'" + session.ServerAddress + "' is not a usable address"); } else { RossQoLPlugin.Log.LogInfo((object)$"Continue: joining {session}."); menu.SetServerToJoin(data); menu.JoinServer(); ((MonoBehaviour)menu).StartCoroutine(ReenableAfterDelay(_button)); } } private static IEnumerator ReenableAfterDelay(Button button) { yield return (object)new WaitForSecondsRealtime(3f); if ((Object)(object)button != (Object)null) { ((Selectable)button).interactable = true; } } private static void FallBack(FejdStartup menu, string reason) { RossQoLPlugin.Log.LogWarning((object)("Continue: " + reason + "; opening character selection instead.")); menu.OnStartGame(); if ((Object)(object)_button != (Object)null) { ((Selectable)_button).interactable = true; } } } internal static class SessionRecorder { private static readonly SessionCapture Capture = new SessionCapture(); private static bool _subscribed; public static void Subscribe() { if (!_subscribed) { Game.m_playerInitialSpawn += OnInitialSpawn; _subscribed = true; } } public static void ServerJoinRequested(ServerJoinData data) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected I4, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_00a1: Unknown result type (might be due to invalid IL or missing references) if (((ServerJoinData)(ref data)).IsValid) { string text = ""; ServerJoinDataType type = data.m_type; ServerKind val; string text2; switch (type - 1) { default: return; case 2: val = (ServerKind)1; text2 = ((object)((ServerJoinData)(ref data)).Dedicated/*cast due to .constrained prefix*/).ToString(); break; case 0: val = (ServerKind)2; text2 = ((ServerJoinData)(ref data)).SteamUser.m_joinUserID.m_SteamID.ToString(CultureInfo.InvariantCulture); break; case 1: val = (ServerKind)3; text2 = ((ServerJoinData)(ref data)).PlayFabUser.m_remotePlayerId; text = MultiBackendMatchmaking.GetServerMatchmakingData(data, default(DateTime)).m_joinCode ?? ""; break; } Capture.ServerJoinRequested(val, text2, text, MultiBackendMatchmaking.GetServerName(data)); } } public static void LocalWorldStartRequested() { Capture.LocalWorldStartRequested(); } private static void OnInitialSpawn() { try { ContinueButtonFeature instance = ContinueButtonFeature.Instance; if (instance == null || !instance.IsActive) { return; } ZNet instance2 = ZNet.instance; PlayerProfile val = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null); if (!((Object)(object)instance2 == (Object)null) && val != null) { bool flag = instance2.IsServer() && !instance2.IsDedicated(); World val2 = (flag ? ZNet.World : null); LastSession val3 = Capture.CommitOnInitialSpawn(flag, val.GetFilename(), ((object)Unsafe.As<FileSource, FileSource>(ref val.m_fileSource)/*cast due to .constrained prefix*/).ToString(), val2?.m_name, ((object)Unsafe.As<FileSource, FileSource>(ref val2?.m_fileSource)/*cast due to .constrained prefix*/).ToString()); if (val3 == null) { RossQoLPlugin.Log.LogInfo((object)"Continue: this session cannot be resumed later; keeping the previous record."); return; } SessionStore.Save(val3); RossQoLPlugin.Log.LogInfo((object)$"Continue: recorded {val3}."); } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"Continue: recording the session on spawn failed: {arg}"); } } } [HarmonyPatch(typeof(FejdStartup), "JoinServer")] internal static class JoinServerRecordingPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(FejdStartup), "JoinServer", "Startup/ContinueButton"); } private static void Postfix(FejdStartup __instance) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) try { ContinueButtonFeature instance = ContinueButtonFeature.Instance; if (instance != null && instance.IsActive) { SessionRecorder.ServerJoinRequested(__instance.GetServerToJoin()); } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"Continue: recording the join request failed: {arg}"); } } } [HarmonyPatch(typeof(FejdStartup), "OnWorldStart")] internal static class WorldStartRecordingPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(FejdStartup), "OnWorldStart", "Startup/ContinueButton"); } private static void Prefix() { ContinueButtonFeature instance = ContinueButtonFeature.Instance; if (instance != null && instance.IsActive) { SessionRecorder.LocalWorldStartRequested(); } } } internal static class SessionStore { private const string Key = "RossQoL.LastSession"; public static LastSession Load() { return LastSessionFormat.Read(PlatformPrefs.GetString("RossQoL.LastSession", "")); } public static void Save(LastSession session) { PlatformPrefs.SetString("RossQoL.LastSession", LastSessionFormat.Write(session)); PlatformPrefs.Save(); } } internal sealed class SkipSplashFeature : Feature { public const string FeatureName = "Startup/SkipSplash"; public static SkipSplashFeature Instance { get; private set; } public override string Key => "SkipSplash"; public override FeatureScope Scope => (FeatureScope)0; public override string Description => "Skips the logos at launch and the main menu intro video."; public override IEnumerable<Type> PatchClasses => new Type[2] { typeof(SceneLoaderLogosPatch), typeof(MenuIntroVideoPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[4] { new CompatMember("SceneLoader", "Awake", "skipping the launch logos"), new CompatMember("SceneLoader", "_showLogos", "skipping the launch logos"), new CompatMember("FejdStartup", "Start", "skipping the menu intro video"), new CompatMember("CinematicsManager", "m_introOnStartup", "skipping the menu intro video") }; public SkipSplashFeature() { Instance = this; } public override void OnActivated(GameObject host) { SceneLoader[] array = Object.FindObjectsByType<SceneLoader>((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { array[i]._showLogos = false; RossQoLPlugin.Log.LogInfo((object)"SkipSplash: launch logo skip requested (loader already awake)."); } } } [HarmonyPatch(typeof(SceneLoader), "Awake")] internal static class SceneLoaderLogosPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(SceneLoader), "Awake", "Startup/SkipSplash"); } private static void Postfix(SceneLoader __instance) { SkipSplashFeature instance = SkipSplashFeature.Instance; if (instance != null && instance.IsActive) { __instance._showLogos = false; RossQoLPlugin.Log.LogInfo((object)"SkipSplash: launch logos skipped."); } } } [HarmonyPatch(typeof(FejdStartup), "Start")] internal static class MenuIntroVideoPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(FejdStartup), "Start", "Startup/SkipSplash"); } private static void Prefix() { SkipSplashFeature instance = SkipSplashFeature.Instance; if (instance != null && instance.IsActive) { CinematicsManager s_instance = CinematicsManager.s_instance; if (!((Object)(object)s_instance == (Object)null) && s_instance.m_introOnStartup) { s_instance.m_introOnStartup = false; RossQoLPlugin.Log.LogInfo((object)"SkipSplash: menu intro video skipped."); } } } } internal sealed class SkipValkyrieFeature : Feature { public const string FeatureName = "Startup/SkipValkyrie"; public static SkipValkyrieFeature Instance { get; private set; } public override string Key => "SkipValkyrie"; public override FeatureScope Scope => (FeatureScope)0; public override string Description => "Skips the Valkyrie flight and the intro text on a new character's first spawn."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(ValkyrieIntroPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[2] { new CompatMember("Game", "Start", "stopping the intro from being queued"), new CompatMember("Game", "m_queuedIntro", "stopping the intro from being queued") }; public SkipValkyrieFeature() { Instance = this; } } [HarmonyPatch(typeof(Game), "Start")] internal static class ValkyrieIntroPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Game), "Start", "Startup/SkipValkyrie"); } private static void Postfix(Game __instance) { SkipValkyrieFeature instance = SkipValkyrieFeature.Instance; if (instance != null && instance.IsActive && __instance.m_queuedIntro) { __instance.m_queuedIntro = false; RossQoLPlugin.Log.LogInfo((object)"SkipValkyrie: intro skipped for a new character."); } } } } namespace RossQoL.Game.Progression { internal sealed class ClearMistFeature : Feature { public const string FeatureName = "Progression/ClearMist"; public const string QueenKey = "defeated_queen"; public static ClearMistFeature Instance { get; private set; } public override string Key => "ClearMist"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Killing the Queen clears the mist from the Mistlands. Until then it is exactly as vanilla, and turning this off brings the mist back."; public override IEnumerable<Type> PatchClasses => new Type[2] { typeof(ClearMistPatch), typeof(ClearMistEnvPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[5] { new CompatMember("ParticleMist", "Update", "where the Mistlands mist is emitted"), new CompatMember("ParticleMist", "m_ps", "clearing the mist already in the air"), new CompatMember("ParticleMist", "instance", "the one mist system to switch off"), new CompatMember("EnvMan", "SetEnv", "the moment to decide whether mist exists"), new CompatMember("ZoneSystem", "GetGlobalKey", "reading whether the Queen is dead") }; public ClearMistFeature() { Instance = this; } } [HarmonyPatch(typeof(ParticleMist), "Update")] internal static class ClearMistPatch { private static bool _cleared; private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(ParticleMist), "Update", "Progression/ClearMist"); } private static bool Prefix(ParticleMist __instance) { try { if (!ShouldClear()) { _cleared = false; return true; } if (!_cleared) { if ((Object)(object)__instance.m_ps != (Object)null) { __instance.m_ps.Clear(); } _cleared = true; RossQoLPlugin.Log.LogInfo((object)"ClearMist: the Queen is dead; the mist is cleared."); } return false; } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"ClearMist: leaving the mist to vanilla: {arg}"); return true; } } internal static bool ShouldClear() { ClearMistFeature instance = ClearMistFeature.Instance; if (instance == null || !instance.IsActive) { return false; } ZoneSystem instance2 = ZoneSystem.instance; if ((Object)(object)instance2 != (Object)null) { return instance2.GetGlobalKey("defeated_queen"); } return false; } } [HarmonyPatch(typeof(EnvMan), "SetEnv")] internal static class ClearMistEnvPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(EnvMan), "SetEnv", "Progression/ClearMist"); } private static void Postfix() { try { ParticleMist instance = ParticleMist.instance; if (!((Object)(object)instance == (Object)null)) { bool flag = !ClearMistPatch.ShouldClear(); if (((Component)instance).gameObject.activeSelf != flag) { ((Component)instance).gameObject.SetActive(flag); } } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"ClearMist: leaving the mist object alone: {arg}"); } } } internal static class DungeonRegistry { private static readonly List<DungeonGenerator> Loaded = new List<DungeonGenerator>(); public static int Count => Loaded.Count; public static void Add(DungeonGenerator generator) { if (!((Object)(object)generator == (Object)null) && !Loaded.Contains(generator)) { Loaded.Add(generator); } } public static void Remove(DungeonGenerator generator) { if (!((Object)(object)generator == (Object)null)) { Loaded.Remove(generator); } } public static void Current(List<DungeonGenerator> results) { results.Clear(); Loaded.RemoveAll((DungeonGenerator g) => (Object)(object)g == (Object)null); results.AddRange(Loaded); } } [HarmonyPatch(typeof(DungeonGenerator), "Awake")] internal static class DungeonRegistryAwakePatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(DungeonGenerator), "Awake", "Progression/DungeonRespawn"); } private static void Postfix(DungeonGenerator __instance) { DungeonRegistry.Add(__instance); DungeonVisits.Listen(__instance); } } [HarmonyPatch(typeof(DungeonGenerator), "OnDestroy")] internal static class DungeonRegistryDestroyPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(DungeonGenerator), "OnDestroy", "Progression/DungeonRespawn"); } private static void Prefix(DungeonGenerator __instance) { DungeonRegistry.Remove(__instance); } } internal static class DungeonReset { private static readonly Vector3 DefaultZoneSize = new Vector3(64f, 64f, 64f); private const float Margin = 1.25f; private const float PlayerSafety = 96f; public static Bounds VolumeOf(DungeonGenerator generator) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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) //IL_0033: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Vector3 val = generator.m_zoneSize; if (val.x <= 0f || val.y <= 0f || val.z <= 0f) { val = DefaultZoneSize; } return new Bounds(((Component)generator).transform.position, val * 1.25f); } public static bool IsSafe(DungeonGenerator generator, out string why) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) why = null; Bounds val = VolumeOf(generator); Bounds val2 = val; ((Bounds)(ref val2)).Expand(192f); foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && ((Bounds)(ref val2)).Contains(((Component)allPlayer).transform.position)) { why = "a player is inside or close to it"; return false; } } ConfigEntry<bool> protectPlayerBuilds = ProgressionConfig.ProtectPlayerBuilds; if ((protectPlayerBuilds == null || protectPlayerBuilds.Value) && HasPlayerBuild(val)) { why = "it holds something a player built"; return false; } return true; } public static bool Run(DungeonGenerator generator, string biome) { //IL_0021: 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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) try { ZNetView component = ((Component)generator).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } Bounds volume = VolumeOf(generator); int stubborn; int num = ClearInside(generator, volume, out stubborn); if (stubborn > 0) { RossQoLPlugin.Log.LogWarning((object)($"DungeonRespawn: leaving the {biome} dungeon at {((Component)generator).transform.position} alone -- " + $"{stubborn} of its objects would not clear, and rebuilding over them would double them.")); return false; } int seed = generator.GetSeed(); generator.Generate(seed, (SpawnMode)0); RossQoLPlugin.Log.LogInfo((object)($"DungeonRespawn: rebuilt the {biome} dungeon at {((Component)generator).transform.position} " + $"from seed {seed}, clearing {num} objects.")); return true; } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"DungeonRespawn: could not rebuild a dungeon, leaving it as it is: {arg}"); return false; } } private static int ClearInside(DungeonGenerator generator, Bounds volume, out int stubborn) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)generator).GetComponent<ZNetView>(); List<ZNetView> list = new List<ZNetView>(); ZNetView[] array = Object.FindObjectsByType<ZNetView>((FindObjectsSortMode)0); foreach (ZNetView val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)component) && val.IsValid() && ((Bounds)(ref volume)).Contains(((Component)val).transform.position) && !((Object)(object)((Component)val).GetComponent<Player>() != (Object)null)) { list.Add(val); } } int num = 0; stubborn = 0; foreach (ZNetView item in list) { try { if (!((Object)(object)item == (Object)null) && item.IsValid()) { if (!item.IsOwner()) { item.ClaimOwnership(); } if (!item.IsOwner()) { stubborn++; continue; } item.Destroy(); num++; } } catch (Exception arg) { stubborn++; RossQoLPlugin.Log.LogWarning((object)$"DungeonRespawn: one object would not clear: {arg}"); } } return num; } private static bool HasPlayerBuild(Bounds volume) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) Piece[] array = Object.FindObjectsByType<Piece>((FindObjectsSortMode)0); foreach (Piece val in array) { if (!((Object)(object)val == (Object)null) && val.IsPlacedByPlayer() && ((Bounds)(ref volume)).Contains(((Component)val).transform.position)) { return true; } } return false; } } internal sealed class DungeonRespawnFeature : Feature { public const string FeatureName = "Progression/DungeonRespawn"; public static DungeonRespawnFeature Instance { get; private set; } public override string Key => "DungeonRespawn"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Dungeons in a biome whose boss you have killed come back RespawnDays after your last visit: burial chambers and troll caves once the Elder is down, sunken crypts once Bonemass is, frost caves once Moder is, infested mines once the Queen is. The layout is rebuilt from the dungeon's own seed, so it returns exactly as it was."; public override IEnumerable<Type> PatchClasses => new Type[2] { typeof(DungeonRegistryAwakePatch), typeof(DungeonRegistryDestroyPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[15] { new CompatMember("DungeonGenerator", "Awake", "noticing a dungeon load"), new CompatMember("DungeonGenerator", "Generate", "rebuilding a dungeon"), new CompatMember("DungeonGenerator", "GetSeed", "rebuilding it exactly as it was"), new CompatMember("DungeonGenerator", "m_themes", "what kind of dungeon it is, when its location says nothing"), new CompatMember("DungeonGenerator", "m_algorithm", "telling a real dungeon from a surface camp"), new CompatMember("Location", "m_hasInterior", "telling a dungeon from a surface ruin"), new CompatMember("Location", "m_interiorRadius", "how far the dungeon reaches"), new CompatMember("Location", "m_biome", "the biome the dungeon belongs to, rather than the ground under it"), new CompatMember("ZoneSystem+ZoneLocation", "m_biome", "the same, from the world record, when the location is not loaded"), new CompatMember("EnvMan", "GetDay", "what day it is"), new CompatMember("ZoneSystem", "GetGlobalKey", "reading which bosses are dead"), new CompatMember("ZNetView", "ClaimOwnership", "taking charge of what is being replaced"), new CompatMember("ZNetView", "Register", "listening for a visit stamp on a dungeon"), new CompatMember("ZNetView", "InvokeRPC", "asking a dungeon's owner to stamp it"), new CompatMember("Piece", "IsPlacedByPlayer", "leaving dungeons you have built in alone") }; public DungeonRespawnFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) ProgressionConfig.BindDungeonRespawn(config, section, Scope); } public override void OnActivated(GameObject host) { host.AddComponent<DungeonRespawnManager>(); } } internal sealed class DungeonRespawnManager : MonoBehaviour { private const float CheckInterval = 10f; private readonly List<DungeonGenerator> _loaded = new List<DungeonGenerator>(); private void Awake() { ((MonoBehaviour)this).InvokeRepeating("Sweep", 10f, 10f); } private void Sweep() { DungeonRespawnFeature instance = DungeonRespawnFeature.Instance; if (instance == null || !instance.IsActive || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)EnvMan.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return; } try { int day = EnvMan.instance.GetDay(); int span = ProgressionConfig.RespawnDays?.Value ?? 24; DungeonRegistry.Current(_loaded); foreach (DungeonGenerator item in _loaded) { Consider(item, day, span); } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"DungeonRespawn: leaving the dungeons alone this round: {arg}"); } } private void Consider(DungeonGenerator generator, int today, int span) { //IL_0024: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)generator == (Object)null) { return; } ZNetView component = ((Component)generator).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || (int)generator.m_algorithm != 0 || !DungeonSite.Resolve(generator, out var site)) { return; } bool flag = DungeonUnlock.IsCleared(site.DeclaredBiomes, site.Themes, (Func<string, bool>)HasKey); Bounds val = DungeonReset.VolumeOf(generator); if (((Bounds)(ref val)).Contains(((Component)Player.m_localPlayer).transform.position)) { Stamp(component, today); return; } int? num = ReadVisitDay(component); string why; if (!num.HasValue) { Stamp(component, today); } else if (DungeonRespawnMath.IsDue(num, today, span) && flag && DungeonReset.IsSafe(generator, out why)) { if (!component.IsOwner()) { component.ClaimOwnership(); } if (component.IsOwner() && DungeonRespawnMath.IsDue(ReadVisitDay(component), today, span) && DungeonReset.Run(generator, site.Label)) { Stamp(component, today); } } } private static void Stamp(ZNetView nview, int today) { DungeonVisits.Stamp(nview, today); } private static int? ReadVisitDay(ZNetView nview) { return DungeonVisits.Read(nview); } private static bool HasKey(string key) { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(key); } return false; } } internal readonly struct DungeonSite { public string DeclaredBiomes { get; } public string Themes { get; } public string TerrainBiome { get; } public string LocationName { get; } public string Label { get { if (!string.IsNullOrEmpty(DeclaredBiomes)) { return DeclaredBiomes; } return TerrainBiome; } } private DungeonSite(string declaredBiomes, string themes, string terrainBiome, string locationName) { DeclaredBiomes = declaredBiomes; Themes = themes; TerrainBiome = terrainBiome; LocationName = locationName; } public static bool Resolve(DungeonGenerator generator, out DungeonSite site) { //IL_0020: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_00b7: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: 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_0104: Unknown result type (might be due to invalid IL or missing references) site = default(DungeonSite); if ((Object)(object)generator == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return false; } string themes = FlagNames.Of((Enum)(object)generator.m_themes); Vector2s zone = ZoneSystem.GetZone(((Component)generator).transform.position); Location zoneLocation = Location.GetZoneLocation(zone); if ((Object)(object)zoneLocation != (Object)null && zoneLocation.m_hasInterior) { site = new DungeonSite(FlagNames.Of((Enum)(object)zoneLocation.m_biome), themes, ((object)Heightmap.FindBiome(((Component)zoneLocation).transform.position)/*cast due to .constrained prefix*/).ToString(), ((Object)((Component)zoneLocation).gameObject).name); return true; } if (!ZoneSystem.instance.m_locationInstances.TryGetValue(zone, out var value)) { return false; } if (value.m_location == null || value.m_location.m_interiorRadius <= 0f) { return false; } site = new DungeonSite(FlagNames.Of((Enum)(object)value.m_location.m_biome), themes, ((object)Heightmap.FindBiome(value.m_position)/*cast due to .constrained prefix*/).ToString(), value.m_location.m_prefabName); return true; } } internal static class FlagNames { public static string Of(Enum flags) { if (flags == null) { return ""; } Type type = flags.GetType(); long num = Convert.ToInt64(flags); if (num == 0L) { return ""; } List<string> list = new List<string>(); foreach (object value in Enum.GetValues(type)) { long num2 = Convert.ToInt64(value); if (num2 != 0L && (num2 & (num2 - 1)) == 0L && (num & num2) == num2) { string name = Enum.GetName(type, value); if (!string.IsNullOrEmpty(name)) { list.Add(name); } } } return string.Join(", ", list.ToArray()); } } internal static class DungeonVisits { private const string VisitRpc = "RossQoL_DungeonVisit"; public static readonly int VisitDayKey = StringExtensionMethods.GetStableHashCode("rossqol_dungeonVisitDay"); public static void Listen(DungeonGenerator generator) { if ((Object)(object)generator == (Object)null) { return; } try { ZNetView nview = ((Component)generator).GetComponent<ZNetView>(); if (!((Object)(object)nview == (Object)null) && nview.IsValid()) { nview.Register<int>("RossQoL_DungeonVisit", (Action<long, int>)delegate(long sender, int day) { Receive(nview, day); }); } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"DungeonRespawn: could not listen for visits on a dungeon: {arg}"); } } public static int? Read(ZNetView nview) { ZDO val = (((Object)(object)nview != (Object)null && nview.IsValid()) ? nview.GetZDO() : null); if (val == null) { return null; } int value = default(int); if (!val.GetInt(VisitDayKey, ref value)) { return null; } return value; } public static void Stamp(ZNetView nview, int today) { if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return; } try { if (nview.IsOwner()) { Receive(nview, today); return; } ZDO zDO = nview.GetZDO(); if (zDO != null && !zDO.HasOwner()) { nview.ClaimOwnership(); if (nview.IsOwner()) { Receive(nview, today); return; } } nview.InvokeRPC("RossQoL_DungeonVisit", new object[1] { today }); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"DungeonRespawn: could not stamp a dungeon's visit: {arg}"); } } private static void Receive(ZNetView nview, int day) { if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { ZDO zDO = nview.GetZDO(); int num = default(int); if (zDO != null && (!zDO.GetInt(VisitDayKey, ref num) || num != day)) { zDO.Set(VisitDayKey, day, false); } } } } internal sealed class MiningPowerFeature : Feature { public const string FeatureName = "Progression/MiningPower"; public static MiningPowerFeature Instance { get; private set; } public override string Key => "MiningPower"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Rock and ore in a biome whose boss you have killed take MiningMultiplier times the damage, so the Black Forest gives up its copper faster once the Elder is down, the swamp its scrap once Bonemass is, and so on. Trees are unaffected."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(MiningPowerPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[5] { new CompatMember("MineRock5", "Damage", "where a swing at ore lands"), new CompatMember("MineRock", "Damage", "where a swing at older rock lands"), new CompatMember("Destructible", "Damage", "where a swing at a deposit lands"), new CompatMember("Heightmap", "FindBiome", "which biome the rock stands in"), new CompatMember("ZoneSystem", "GetGlobalKey", "reading which bosses are dead") }; public MiningPowerFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) ProgressionConfig.BindMining(config, section, Scope); } } [HarmonyPatch] internal static class MiningPowerPatch { private static readonly (Type Type, string Method)[] Targets = new(Type, string)[3] { (typeof(MineRock5), "Damage"), (typeof(MineRock), "Damage"), (typeof(Destructible), "Damage") }; private static bool Prepare() { bool flag = true; (Type, string)[] targets = Targets; for (int i = 0; i < targets.Length; i++) { (Type, string) tuple = targets[i]; Type item = tuple.Item1; string item2 = tuple.Item2; flag &= ValheimCompat.RequireMethod(item, item2, "Progression/MiningPower"); } return flag; } private static IEnumerable<MethodBase> TargetMethods() { (Type Type, string Method)[] targets = Targets; for (int i = 0; i < targets.Length; i++) { var (type, text) = targets[i]; yield return AccessTools.Method(type, text, new Type[1] { typeof(HitData) }, (Type[])null); } } private static void Prefix(MonoBehaviour __instance, HitData hit) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 MiningPowerFeature instance = MiningPowerFeature.Instance; if (instance == null || !instance.IsActive || hit == null || (Object)(object)__instance == (Object)null) { return; } try { IDestructible val = (IDestructible)(object)((__instance is IDestructible) ? __instance : null); if ((val == null || (int)val.GetDestructibleType() != 2) && BiomeBosses.IsCleared(BiomeAt(((Component)__instance).transform.position), (Func<string, bool>)HasKey)) { float num = ProgressionConfig.MiningMultiplier?.Value ?? 2f; if (!(num <= 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"MiningPower: leaving this swing at vanilla strength: {arg}"); } } private static string BiomeAt(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) return ((object)Heightmap.FindBiome(point)/*cast due to .constrained prefix*/).ToString(); } private static bool HasKey(string key) { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(key); } return false; } } public static class ProgressionConfig { public static ConfigEntry<float> MiningMultiplier; public static ConfigEntry<int> SmeltingMultiplier; public static ConfigEntry<int> RespawnDays; public static ConfigEntry<bool> ProtectPlayerBuilds; internal static void BindMining(ConfigFile config, string section, FeatureScope scope) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) MiningMultiplier = config.Bind<float>(section, "MiningMultiplier", 2f, ConfigText.Description("How much harder you hit rock and ore in a biome whose boss you have killed. 2 means twice the damage, so half the swings.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f))); } internal static void BindDungeonRespawn(ConfigFile config, string section, FeatureScope scope) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) RespawnDays = config.Bind<int>(section, "RespawnDays", 24, ConfigText.Description("In-game days after your last visit before a dungeon is rebuilt as it was first found. A day is about half an hour of play.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000))); ProtectPlayerBuilds = config.Bind<bool>(section, "ProtectPlayerBuilds", true, ConfigText.Description("Leave a dungeon alone entirely once anything is built inside it. A rebuild destroys everything in the dungeon, a portal or a stash included, so by default a dungeon you have made your own stops respawning rather than being cleared out.", scope, requiresRestart: false)); } internal static void BindSmelting(ConfigFile config, string section, FeatureScope scope) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) SmeltingMultiplier = config.Bind<int>(section, "SmeltingMultiplier", 2, ConfigText.Description("How many bars one ore yields once the boss of the biome it comes from is dead, and how many refined eitr the Eitr Refinery yields once the Queen is dead. 2 doubles the output for the same inputs.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10))); } } internal sealed class SmeltingYieldFeature : Feature { public const string FeatureName = "Progression/SmeltingYield"; public static SmeltingYieldFeature Instance { get; private set; } public override string Key => "SmeltingYield"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Ore from a biome whose boss you have killed smelts into SmeltingMultiplier bars instead of one, for the same ore and fuel, and the Eitr Refinery yields that many refined eitr once the Queen is dead. Only the metals tied to a boss and refined eitr are affected; coal, flour and everything else is unchanged."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(SmeltingYieldPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[2] { new CompatMember("Smelter", "Spawn", "where a smelter hands over what it made"), new CompatMember("ZoneSystem", "GetGlobalKey", "reading which bosses are dead") }; public SmeltingYieldFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) ProgressionConfig.BindSmelting(config, section, Scope); } } [HarmonyPatch(typeof(Smelter), "Spawn")] internal static class SmeltingYieldPatch { private const string RefineryPrefab = "eitrrefinery"; private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Smelter), "Spawn", "Progression/SmeltingYield"); } private static void Prefix(Smelter __instance, string ore, ref int stack) { SmeltingYieldFeature instance = SmeltingYieldFeature.Instance; if (instance == null || !instance.IsActive || stack <= 0) { return; } try { if (ShouldEnrich(__instance, ore)) { int num = ProgressionConfig.SmeltingMultiplier?.Value ?? 2; if (num > 1) { stack *= num; } } } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"SmeltingYield: leaving this batch at vanilla size: {arg}"); } } private static bool ShouldEnrich(Smelter smelter, string ore) { if (TeleportUnlocks.IsUnlocked(ore, (Func<string, bool>)HasKey)) { return true; } if (IsEitrRefinery(smelter)) { return HasKey("defeated_queen"); } return false; } private static bool IsEitrRefinery(Smelter smelter) { ZNetView val = (((Object)(object)smelter == (Object)null) ? null : ((Component)smelter).GetComponentInParent<ZNetView>()); if ((Object)(object)val == (Object)null) { return false; } return string.Equals(Utils.GetPrefabName(((Component)val).gameObject), "eitrrefinery", StringComparison.OrdinalIgnoreCase); } private static bool HasKey(string key) { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(key); } return false; } } internal sealed class TeleportUnlocksFeature : Feature { public const string FeatureName = "Progression/TeleportUnlocks"; public static TeleportUnlocksFeature Instance { get; private set; } public override string Key => "TeleportUnlocks"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Metal and ore may be carried through a portal once you have killed the boss of the biome it comes from: the Elder for copper and tin, Bonemass for iron, Moder for silver, Yagluth for black metal, Fader for flametal. Everything else vanilla refuses to teleport, it still refuses."; public override IEnumerable<Type> PatchClasses => new Type[1] { typeof(TeleportUnlocksPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[2] { new CompatMember("Inventory", "IsTeleportable", "the check that refuses ore at a portal"), new CompatMember("ZoneSystem", "GetGlobalKey", "reading which bosses are dead") }; public TeleportUnlocksFeature() { Instance = this; } } [HarmonyPatch(typeof(Inventory), "IsTeleportable")] internal static class TeleportUnlocksPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Inventory), "IsTeleportable", "Progression/TeleportUnlocks"); } private static bool Prefix(Inventory __instance, bool allowAllItems, ref bool __result) { TeleportUnlocksFeature instance = TeleportUnlocksFeature.Instance; if (instance == null || !instance.IsActive) { return true; } try { foreach (ItemData item in __instance.m_inventory) { if (item?.m_shared != null && item.m_shared.m_toolTier >= 1000) { __result = false; return false; } } if (allowAllItems || ZoneSystem.instance.GetGlobalKey((GlobalKeys)35)) { __result = true; return false; } foreach (ItemData item2 in __instance.m_inventory) { if (item2?.m_shared != null && !item2.m_shared.m_teleportable && !IsUnlocked(item2)) { __result = false; return false; } } __result = true; return false; } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"TeleportUnlocks: could not check an inventory, leaving vanilla's answer: {arg}"); return true; } } private static bool IsUnlocked(ItemData item) { string text = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : null); if (string.IsNullOrEmpty(text)) { return false; } return TeleportUnlocks.IsUnlocked(text, (Func<string, bool>)HasKey); } private static bool HasKey(string key) { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(key); } return false; } } } namespace RossQoL.Game.Production { public static class AutoFeedConfig { public static ConfigEntry<bool> FeedSmelters; public static ConfigEntry<bool> FeedKilns; public static ConfigEntry<bool> FeedBlastFurnaces; public static ConfigEntry<bool> FeedWindmills; public static ConfigEntry<bool> FeedSpinningWheels; public static ConfigEntry<bool> FeedOvens; public static ConfigEntry<bool> FeedShieldGenerators; public static ConfigEntry<bool> FeedFermenters; public static ConfigEntry<float> FeedRadius; public static ConfigEntry<float> FeedInterval; public static ConfigEntry<int> MinimumLeftBehind; public static ConfigEntry<string> MinimumPerItem; public static ConfigEntry<string> KilnFuel; public static ConfigEntry<string> MaxOutput; internal static void Bind(ConfigFile config, string section, FeatureScope scope) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) FeedSmelters = config.Bind<bool>(section, "FeedSmelters", true, ConfigText.Description("Smelters take ore and fuel from nearby containers.", scope, requiresRestart: false)); FeedKilns = config.Bind<bool>(section, "FeedKilns", true, ConfigText.Description("Charcoal kilns take wood from nearby containers. See KilnFuel.", scope, requiresRestart: false)); FeedBlastFurnaces = config.Bind<bool>(section, "FeedBlastFurnaces", true, ConfigText.Description("Blast furnaces take ore and fuel from nearby containers.", scope, requiresRestart: false)); FeedWindmills = config.Bind<bool>(section, "FeedWindmills", true, ConfigText.Description("Windmills take barley from nearby containers.", scope, requiresRestart: false)); FeedSpinningWheels = config.Bind<bool>(section, "FeedSpinningWheels", true, ConfigText.Description("Spinning wheels take flax from nearby containers.", scope, requiresRestart: false)); FeedOvens = config.Bind<bool>(section, "FeedOvens", true, ConfigText.Description("Ovens take fuel from nearby containers. Food is still put in by hand.", scope, requiresRestart: false)); FeedShieldGenerators = config.Bind<bool>(section, "FeedShieldGenerators", true, ConfigText.Description("Shield generators take fuel from nearby containers.", scope, requiresRestart: false)); FeedFermenters = config.Bind<bool>(section, "FeedFermenters", true, ConfigText.Description("Empty fermenters take a mead base from nearby containers and start it.", scope, requiresRestart: false)); FeedRadius = config.Bind<float>(section, "FeedRadius", 40f, ConfigText.Description("How far from a producer, in metres, to look for containers to take from. Measured in three dimensions. Fires use this too.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f))); FeedInterval = config.Bind<float>(section, "FeedInterval", 1f, ConfigText.Description("Seconds between feed attempts for each producer. One item moves per attempt, so this is also how fast a producer fills. Fires use this too.", scope, requiresRestart: false, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3600f))); MinimumLeftBehind = config.Bind<int>(section, "MinimumLeftBehind", 0, ConfigText.Description("How many of an item to leave across the containers near a producer rather than feed it in. Counted as a total, not per container: 50 wood means 50 in the area, however many chests it is spread over. Applies to every item without its own entry in MinimumPerItem, and to fires too.", scope, requiresRestart: false, turningOnR