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.15.0
plugins/RossQoL.Core.dll
Decompiled 5 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; [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+15e702664b55bcc257f097082a96453b6d0fa5c6")] [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.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 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"; 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" }, { "BlackMetalScrap", "defeated_goblinking" }, { "BlackMetal", "defeated_goblinking" } }; 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; } } } 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 readonly struct TameCandidate { public Vec3 Position { get; } public bool IsTamed { get; } public bool IsFollowingPlayer { get; } public bool IsBusy { get; } public TameCandidate(Vec3 position, bool isTamed, bool isFollowingPlayer, bool isBusy) { Position = position; IsTamed = isTamed; IsFollowingPlayer = isFollowingPlayer; IsBusy = isBusy; } } public static class TameEligibility { public static bool Qualifies(TameCandidate candidate, Vec3 playerPosition, float radius) { if (radius <= 0f) { return false; } if (!candidate.IsTamed) { 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 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.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 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"; } } } 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.Crafting { 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(); } } }
plugins/RossQoL.dll
Decompiled 5 hours 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 BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GUIFramework; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using RossQoL.Core.Crafting; using RossQoL.Core.Framework; using RossQoL.Core.Interface; using RossQoL.Core.Portals; using RossQoL.Core.Production; using RossQoL.Core.Progression; using RossQoL.Core.Startup; using RossQoL.Core.Terrain; using RossQoL.Game.Combat; using RossQoL.Game.Crafting; using RossQoL.Game.Fires; using RossQoL.Game.Framework; using RossQoL.Game.Interface; using RossQoL.Game.Items; 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+15e702664b55bcc257f097082a96453b6d0fa5c6")] [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.15.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.15.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.15.0 loaded"); } private void Update() { if (_hotReloadEnabled.Value) { _hotReload?.Pump(); } } private void OnDestroy() { _hotReload?.Dispose(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } 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 { 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 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 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. 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, turningOnRequiresRestart: false, (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 9999))); MinimumPerItem = config.Bind<string>(section, "MinimumPerItem", "", ConfigText.Description("Per-item amounts to leave across the containers near a producer, overriding MinimumLeftBehind. Counted as a total, not per container. Prefab names with a number, comma-separated, e.g. \"Wood:50, Barley:20\".", scope, requiresRestart: false)); KilnFuel = config.Bind<string>(section, "KilnFuel", "Wood", ConfigText.Description("What a charcoal kiln may be fed, comma-separated prefab names, e.g. \"Wood, FineWood\". Empty allows anything the kiln accepts. Only limits what this mod feeds it; you can always add fuel by hand.", scope, requiresRestart: false)); MaxOutput = config.Bind<string>(section, "MaxOutput", "", ConfigText.Description("Stop feeding a producer once this many of what it makes are in nearby containers. Prefab names with a number, comma-separated, e.g. \"Coal:200, BarleyFlour:500\". Smelted metals are never capped, so ore is always processed.", scope, requiresRestart: false)); } } internal sealed class AutoFeedFeature : Feature { public const string FeatureName = "Production/AutoFeed"; public static AutoFeedFeature Instance { get; private set; } public override string Key => "AutoFeed"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Producers near a player take what they need from containers within FeedRadius: ore and fuel for smelters, kilns, blast furnaces, windmills and spinning wheels, fuel for ovens and shield generators, and a mead base for an empty fermenter. MinimumLeftBehind keeps a reserve in your chests, KilnFuel limits what a kiln may burn, and MaxOutput stops a producer once you have enough."; public override IEnumerable<Type> PatchClasses => new Type[5] { typeof(ContainerAwakeRegistryPatch), typeof(SmelterFeedPatch), typeof(CookingStationFeedPatch), typeof(ShieldGeneratorFeedPatch), typeof(FermenterFeedPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[28] { new CompatMember("Container", "Awake", "finding the containers to take from"), new CompatMember("Container", "GetInventory", "taking items out of containers"), new CompatMember("Container", "Save", "writing a container back after taking from it"), new CompatMember("Inventory", "CountItems", "how much a container holds"), new CompatMember("Inventory", "GetAllItems", "reading the cheat flag off what is taken"), new CompatMember("Inventory", "RemoveItem", "taking items out of containers"), new CompatMember("PrivateArea", "CheckAccess", "respecting wards"), new CompatMember("Smelter", "UpdateSmelter", "the moment a smelter is fed"), new CompatMember("Smelter", "m_conversion", "what a smelter takes and makes"), new CompatMember("Smelter", "m_maxOre", "how much ore a smelter still has room for"), new CompatMember("Smelter", "m_maxFuel", "how much fuel a smelter still has room for"), new CompatMember("Smelter", "m_fuelItem", "what a smelter burns"), new CompatMember("Smelter", "m_windmill", "telling a windmill from the other smelters"), new CompatMember("Smelter", "GetQueueSize", "how much ore is already queued"), new CompatMember("Smelter", "GetFuel", "how much fuel is already in"), new CompatMember("CookingStation", "UpdateFuel", "the moment an oven is fed"), new CompatMember("CookingStation", "m_useFuel", "ovens that burn nothing are left alone"), new CompatMember("CookingStation", "m_fuelItem", "what an oven burns"), new CompatMember("CookingStation", "m_maxFuel", "how much fuel an oven still has room for"), new CompatMember("CookingStation", "GetFuel", "how much fuel is already in"), new CompatMember("ShieldGenerator", "UpdateShield", "the moment a shield generator is fed"), new CompatMember("ShieldGenerator", "m_fuelItems", "what a shield generator burns"), new CompatMember("ShieldGenerator", "m_maxFuel", "how much fuel it still has room for"), new CompatMember("ShieldGenerator", "m_defaultFuel", "what it holds before anyone fuels it"), new CompatMember("Fermenter", "SlowUpdate", "the moment a fermenter is fed"), new CompatMember("Fermenter", "m_conversion", "which mead bases a fermenter accepts"), new CompatMember("ZDOVars", "s_fuel", "reading how much fuel a producer holds"), new CompatMember("ZDOVars", "s_content", "telling an empty fermenter from a working one") }; public AutoFeedFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) AutoFeedConfig.Bind(config, section, Scope); } } internal sealed class AutoHarvestFeature : Feature { public const string FeatureName = "Production/AutoHarvest"; public static AutoHarvestFeature Instance { get; private set; } public override string Key => "AutoHarvest"; public override FeatureScope Scope => (FeatureScope)1; public override string Description => "Beehives, sap collectors, fermenters and windmills near a player empty themselves into containers within HarvestRadius: first containers already holding that item, then the nearest with room. Output that fits nowhere stays in the producer."; public override IEnumerable<Type> PatchClasses => new Type[6] { typeof(ContainerAwakeRegistryPatch), typeof(BeehiveHarvestPatch), typeof(SapCollectorHarvestPatch), typeof(FermenterHarvestPatch), typeof(WindmillHarvestPatch), typeof(WindmillSpawnPatch) }; public override IEnumerable<CompatMember> RequiredMembers => new CompatMember[42] { new CompatMember("Container", "Awake", "finding the containers output can go into"), new CompatMember("Container", "m_nview", "checking who owns a container"), new CompatMember("Container", "m_wagon", "leaving cart storage alone"), new CompatMember("Container", "m_rootObjectOverride", "leaving ship and cart storage alone"), new CompatMember("Container", "m_privacy", "respecting private containers"), new CompatMember("Container", "m_piece", "respecting private containers"), new CompatMember("Container", "CheckAccess", "respecting private containers"), new CompatMember("Container", "IsInUse", "leaving open containers alone"), new CompatMember("Container", "GetInventory", "putting output into containers"), new CompatMember("Inventory", "FindFreeStackSpace", "measuring a container's room"), new CompatMember("Inventory", "GetEmptySlots", "measuring a container's room"), new CompatMember("Inventory", "CountItems", "counting what landed in a container"), new CompatMember("Inventory", "HaveItem", "preferring containers that already hold the item"), new CompatMember("PrivateArea", "CheckAccess", "respecting wards"), new CompatMember("Beehive", "UpdateBees", "the moment a beehive is harvested"), new CompatMember("Beehive", "m_nview", "harvesting only beehives this client owns"), new CompatMember("Beehive", "m_honeyItem", "what a beehive produces"), new CompatMember("Beehive", "m_spawnPoint", "where rounding leftovers drop"), new CompatMember("SapCollector", "UpdateTick", "the moment a sap collector is harvested"), new CompatMember("SapCollector", "m_nview", "harvesting only sap collectors this client owns"), new CompatMember("SapCollector", "m_spawnItem", "what a sap collector produces"), new CompatMember("SapCollector", "m_spawnPoint", "where rounding leftovers drop"), new CompatMember("SapCollector", "RPC_UpdateEffects", "updating a sap collector's look after emptying it"), new CompatMember("Fermenter", "SlowUpdate", "the moment a fermenter is harvested"), new CompatMember("Fermenter", "m_nview", "harvesting only fermenters this client owns"), new CompatMember("Fermenter", "m_fermentationDuration", "knowing when a batch is ready"), new CompatMember("Fermenter", "GetItemConversion", "what a batch turns into"), new CompatMember("Fermenter", "m_outputPoint", "where an unplaced part of a batch drops"), new CompatMember("Smelter", "UpdateSmelter", "the moment a windmill is emptied"), new CompatMember("Smelter", "Spawn", "catching a windmill's flour before it drops"), new CompatMember("Smelter", "m_windmill", "harvesting windmills and no other smelter"), new CompatMember("Smelter", "m_nview", "harvesting only windmills this client owns"), new CompatMember("Smelter", "GetItemConversion", "what a windmill's grain turns into"), new CompatMember("Game", "ScaleDrops", "matching vanilla's honey and sap per level"), new CompatMember("PlayerProfile", "s_bypassCheatChecks", "keeping vanilla's cheated flag on output"), new CompatMember("ZDOVars", "s_level", "reading and emptying beehives and sap collectors"), new CompatMember("ZDOVars", "s_content", "reading and emptying fermenters"), new CompatMember("ZDOVars", "s_startTime", "reading and emptying fermenters"), new CompatMember("ZDOVars", "s_spawnOre", "reading and emptying windmills"), new CompatMember("ZDOVars", "s_spawnAmount", "reading and emptying windmills"), new CompatMember("ZDOVars", "s_cheatedQueued", "keeping vanilla's cheated flag on output"), new CompatMember("ZDOVars", "s_cheated", "keeping vanilla's cheated flag on output") }; public AutoHarvestFeature() { Instance = this; } public override void BindSettings(ConfigFile config, string section) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) ProductionConfig.Bind(config, section, Scope); } } internal static class ContainerAccess { public static bool MayUse(Container container, long playerId) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)container == (Object)null) { return false; } ZNetView nview = container.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return false; } if (container.IsInUse()) { return false; } if ((int)container.m_privacy != 2 && (Object)(object)container.m_piece == (Object)null) { return false; } if (!container.CheckAccess(playerId)) { return false; } if (container.m_checkGuardStone) { return PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false); } return true; } public static bool IsFresh(Container container) { container.Load(); if (((object)container).GetType() != typeof(Container)) { return true; } return container.m_lastRevision == container.m_nview.GetZDO().DataRevision; } } internal static class ContainerOwnership { private sealed class Seen { public ushort Revision; public float Since; } internal const float SettleSeconds = 2f; private static readonly ConditionalWeakTable<Container, Seen> OwnerRevisions = new ConditionalWeakTable<Container, Seen>(); public static void Observe(Container container, ZDO zdo) { ushort ownerRevision = zdo.OwnerRevision; if (OwnerRevisions.TryGetValue(container, out var value)) { if (value.Revision != ownerRevision) { value.Revision = ownerRevision; value.Since = Time.time; } } else { OwnerRevisions.Add(container, new Seen { Revision = ownerRevision, Since = Time.time }); } } public static bool IsSettled(Container container, ZNetView nview) { ZDO zDO = nview.GetZDO(); Observe(container, zDO); if (!nview.IsOwner()) { return false; } if (OwnerRevisions.TryGetValue(container, out var value)) { return Time.time - value.Since >= 2f; } return false; } } internal static class ContainerRegistry { private sealed class ReferenceComparer : IEqualityComparer<Container> { public static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(Container a, Container b) { return a == b; } public int GetHashCode(Container c) { return RuntimeHelpers.GetHashCode(c); } } private static readonly List<Container> Containers = new List<Container>(); private static readonly HashSet<Container> Known = new HashSet<Container>(ReferenceComparer.Instance); private static readonly Predicate<Container> IsDestroyed = (Container c) => (Object)(object)c == (Object)null; private const int PruneEvery = 64; private static int _addsSincePrune; private static void Prune() { Known.RemoveWhere((Container c) => (Object)(object)c == (Object)null); Containers.RemoveAll(IsDestroyed); } public static void Register(Container container) { if (IsStatic(container) && Known.Add(container)) { if (++_addsSincePrune >= 64) { _addsSincePrune = 0; Prune(); } Containers.Add(container); } } public static bool IsStatic(Container container) { if ((Object)(object)container == (Object)null) { return false; } ZNetView nview = container.m_nview; if ((Object)(object)nview == (Object)null || nview.GetZDO() == null) { return false; } if ((Object)(object)container.m_wagon != (Object)null || (Object)(object)container.m_rootObjectOverride != (Object)null) { return false; } if ((Object)(object)((Component)container).GetComponent<Piece>() == (Object)null) { return false; } if ((Object)(object)((Component)container).GetComponentInParent<Ship>() != (Object)null) { return false; } if ((Object)(object)((Component)container).GetComponentInParent<Vagon>() != (Object)null) { return false; } return true; } public static void Near(Vector3 point, float radius, List<Container> results) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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) results.Clear(); Prune(); float num = radius * radius; foreach (Container container in Containers) { ZNetView nview = container.m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid()) { Vector3 val = ((Component)container).transform.position - point; if (((Vector3)(ref val)).sqrMagnitude <= num) { results.Add(container); } } } } } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakeRegistryPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Container), "Awake", "container registry"); } private static void Postfix(Container __instance) { try { ContainerRegistry.Register(__instance); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"Container registry: registering a container failed and it was skipped: {arg}"); } } } internal static class ContainerSource { private static readonly List<Container> Nearby = new List<Container>(); private static readonly List<Container> Usable = new List<Container>(); private static readonly List<ItemData> Found = new List<ItemData>(); private static string _minimumsText; private static Dictionary<string, int> _minimums = new Dictionary<string, int>(FeedRules.NameComparer); private static string _capsText; private static Dictionary<string, int> _caps = new Dictionary<string, int>(FeedRules.NameComparer); public static IDictionary<string, int> Minimums() { string text = AutoFeedConfig.MinimumPerItem?.Value ?? string.Empty; if ((object)text != _minimumsText && text != _minimumsText) { _minimums = FeedRules.ParseAmounts(text); _minimumsText = text; } return _minimums; } public static IDictionary<string, int> Caps() { string text = AutoFeedConfig.MaxOutput?.Value ?? string.Empty; if ((object)text != _capsText && text != _capsText) { _caps = FeedRules.ParseAmounts(text); _capsText = text; } return _caps; } public static int Take(Vector3 origin, ItemDrop item, int wanted, out bool cheated) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) cheated = false; if ((Object)(object)item == (Object)null || wanted <= 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return 0; } string name = item.m_itemData.m_shared.m_name; string name2 = ((Object)((Component)item).gameObject).name; int num = AutoFeedConfig.MinimumLeftBehind?.Value ?? 0; int num2 = FeedRules.MinimumFor(Minimums(), name2, num); long playerID = Game.instance.GetPlayerProfile().GetPlayerID(); ContainerRegistry.Near(origin, AutoFeedConfig.FeedRadius?.Value ?? 40f, Nearby); Usable.Clear(); int num3 = 0; foreach (Container item2 in Nearby) { if (!ContainerAccess.MayUse(item2, playerID) || !ContainerAccess.IsFresh(item2)) { continue; } Inventory inventory = item2.GetInventory(); if (inventory != null) { int num4 = inventory.CountItems(name, -1, false); if (num4 > 0) { Usable.Add(item2); num3 += num4; } } } int num5 = FeedRules.Takeable(num3, wanted, num2); if (num5 <= 0) { Usable.Clear(); return 0; } int num6 = 0; foreach (Container item3 in Usable) { if (num6 >= num5) { break; } if (!ContainerOwnership.IsSettled(item3, item3.m_nview)) { continue; } Inventory inventory2 = item3.GetInventory(); if (inventory2 == null) { continue; } int num7 = Math.Min(inventory2.CountItems(name, -1, false), num5 - num6); if (num7 <= 0) { continue; } Found.Clear(); inventory2.GetAllItems(name, Found); foreach (ItemData item4 in Found) { if (item4 != null && item4.m_cheated) { cheated = true; } } Found.Clear(); inventory2.RemoveItem(name, num7, -1, false); item3.Save(); num6 += num7; } Usable.Clear(); return num6; } public static int CountNearby(Vector3 origin, string sharedName) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(sharedName) || (Object)(object)Player.m_localPlayer == (Object)null) { return 0; } long playerID = Game.instance.GetPlayerProfile().GetPlayerID(); ContainerRegistry.Near(origin, AutoFeedConfig.FeedRadius?.Value ?? 40f, Nearby); int num = 0; foreach (Container item in Nearby) { if (ContainerAccess.MayUse(item, playerID)) { Inventory inventory = item.GetInventory(); if (inventory != null) { num += inventory.CountItems(sharedName, -1, false); } } } return num; } } [HarmonyPatch(typeof(CookingStation), "UpdateFuel")] internal static class CookingStationFeedPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(CookingStation), "UpdateFuel", "Production/AutoFeed"); } private static void Postfix(CookingStation __instance) { AutoFeedFeature instance = AutoFeedFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (__instance.m_useFuel && !((Object)(object)__instance.m_fuelItem == (Object)null) && FeedGate.ShouldRun((MonoBehaviour)(object)__instance, __instance.m_nview, AutoFeedFeature.Instance, AutoFeedConfig.FeedOvens)) { Feed(__instance); FeedGate.Succeeded((MonoBehaviour)(object)__instance); } } catch (Exception ex) { FeedGate.LogFailure((MonoBehaviour)(object)__instance, ex); } } private static void Feed(CookingStation oven) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (oven.m_maxFuel - Mathf.CeilToInt(oven.GetFuel()) > 0 && ContainerSource.Take(((Component)oven).transform.position, oven.m_fuelItem, 1, out var _) > 0) { oven.m_nview.InvokeRPC("RPC_AddFuel", Array.Empty<object>()); } } } internal static class FeedGate { private static readonly ConditionalWeakTable<MonoBehaviour, StrongBox<double>> LastAttempt = new ConditionalWeakTable<MonoBehaviour, StrongBox<double>>(); private static readonly ConditionalWeakTable<MonoBehaviour, StrongBox<string>> LastFailure = new ConditionalWeakTable<MonoBehaviour, StrongBox<string>>(); public static bool ShouldRun(MonoBehaviour producer, ZNetView nview, Feature owner, ConfigEntry<bool> kindEnabled = null) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (owner == null || !owner.IsActive) { return false; } if (kindEnabled != null && !kindEnabled.Value) { return false; } if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { return false; } if (!PrivateArea.CheckAccess(((Component)producer).transform.position, 0f, false, false)) { return false; } double num = Time.time; float num2 = AutoFeedConfig.FeedInterval?.Value ?? 5f; StrongBox<double> value; bool flag = LastAttempt.TryGetValue(producer, out value); if (!HarvestMath.IsDue(flag ? new double?(value.Value) : ((double?)null), num, (double)num2)) { return false; } if (flag) { value.Value = num; } else { LastAttempt.Add(producer, new StrongBox<double>(num)); } return true; } public static void LogFailure(MonoBehaviour producer, Exception ex) { string fullName = ex.GetType().FullName; if ((Object)(object)producer != (Object)null) { if (LastFailure.TryGetValue(producer, out var value)) { if (value.Value == fullName) { return; } value.Value = fullName; } else { LastFailure.Add(producer, new StrongBox<string>(fullName)); } } string arg = (Object.op_Implicit((Object)(object)producer) ? ((Object)producer).name : "a destroyed producer"); RossQoLPlugin.Log.LogError((object)$"AutoFeed: feeding {arg} failed and was skipped (repeats of this error are not logged): {ex}"); } public static void Succeeded(MonoBehaviour producer) { LastFailure.Remove(producer); } } [HarmonyPatch(typeof(Fermenter), "SlowUpdate")] internal static class FermenterFeedPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Fermenter), "SlowUpdate", "Production/AutoFeed"); } private static void Postfix(Fermenter __instance) { AutoFeedFeature instance = AutoFeedFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (FeedGate.ShouldRun((MonoBehaviour)(object)__instance, __instance.m_nview, AutoFeedFeature.Instance, AutoFeedConfig.FeedFermenters)) { Feed(__instance); FeedGate.Succeeded((MonoBehaviour)(object)__instance); } } catch (Exception ex) { FeedGate.LogFailure((MonoBehaviour)(object)__instance, ex); } } private static void Feed(Fermenter fermenter) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (fermenter.m_nview.GetZDO().GetInt(ZDOVars.s_content, 0) != 0) { return; } Vector3 position = ((Component)fermenter).transform.position; foreach (ItemConversion item in fermenter.m_conversion) { if (!((Object)(object)item?.m_from == (Object)null) && ContainerSource.Take(position, item.m_from, 1, out var cheated) > 0) { fermenter.m_nview.InvokeRPC("RPC_AddItem", new object[2] { StringExtensionMethods.GetStableHashCode(((Object)((Component)item.m_from).gameObject).name), cheated }); break; } } } } internal static class Harvester { private static readonly List<Container> Nearby = new List<Container>(); private static readonly List<Inventory> Destinations = new List<Inventory>(); private static readonly List<DestinationCandidate> Candidates = new List<DestinationCandidate>(); public static void HarvestBeehive(Beehive hive) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (PrivateArea.CheckAccess(((Component)hive).transform.position, 0f, false, false)) { HarvestLevels(hive.m_nview, hive.m_honeyItem, ((Component)hive).transform.position, hive.m_spawnPoint); } } public static void HarvestSapCollector(SapCollector collector) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (PrivateArea.CheckAccess(((Component)collector).transform.position, 0f, false, false) && HarvestLevels(collector.m_nview, collector.m_spawnItem, ((Component)collector).transform.position, collector.m_spawnPoint)) { collector.m_nview.InvokeRPC(ZNetView.Everybody, "RPC_UpdateEffects", Array.Empty<object>()); } } public static void HarvestFermenter(Fermenter fermenter) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if (!PrivateArea.CheckAccess(((Component)fermenter).transform.position, 0f, false, false)) { return; } ZDO zDO = fermenter.m_nview.GetZDO(); int num = zDO.GetInt(ZDOVars.s_content, 0); long num2 = zDO.GetLong(ZDOVars.s_startTime, 0L); if (!FermenterReadiness.IsReady(num, num2, ZNet.instance.GetTime().Ticks, fermenter.m_fermentationDuration)) { return; } ItemConversion itemConversion = fermenter.GetItemConversion(num); if (itemConversion == null || (Object)(object)itemConversion.m_to == (Object)null || itemConversion.m_producedItems <= 0) { return; } bool cheated = (zDO.GetBool(ZDOVars.s_cheatedQueued, false) || zDO.GetBool(ZDOVars.s_cheated, false)) && !PlayerProfile.s_bypassCheatChecks; int num3 = itemConversion.m_producedItems * Math.Max(1, itemConversion.m_to.m_itemData.m_stack); int placed = 0; try { Place(itemConversion.m_to, num3, num3, wholeOnly: true, cheated, ((Component)fermenter).transform.position, ref placed); } finally { if (placed > 0) { zDO.Set(ZDOVars.s_content, 0, false); zDO.Set(ZDOVars.s_startTime, 0L); zDO.Set(ZDOVars.s_cheatedQueued, false); Vector3 position = (Object.op_Implicit((Object)(object)fermenter.m_outputPoint) ? fermenter.m_outputPoint.position : ((Component)fermenter).transform.position); int num4 = Math.Max(0, num3 - placed); try { DropAt(itemConversion.m_to, num4, position, cheated); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"AutoHarvest: dropping {num4} x {((Object)itemConversion.m_to).name} at a fermenter failed: {arg}"); } } } } private static bool HarvestLevels(ZNetView nview, ItemDrop item, Vector3 origin, Transform spawnPoint) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null) { return false; } ZDO zDO = nview.GetZDO(); int num = zDO.GetInt(ZDOVars.s_level, 0); if (num <= 0) { return false; } int num2 = Math.Max(1, Game.instance.ScaleDrops(item.m_itemData, 1)); int placed = 0; try { Place(item, num * num2, num2, wholeOnly: false, cheated: false, origin, ref placed); } finally { if (placed > 0) { int num3 = Math.Min(num, HarvestMath.UnitsTaken(placed, num2)); zDO.Set(ZDOVars.s_level, Math.Max(0, num - num3), false); Vector3 position = (Object.op_Implicit((Object)(object)spawnPoint) ? spawnPoint.position : origin); int num4 = HarvestMath.Shortfall(placed, num2); try { DropAt(item, num4, position, cheated: false); } catch (Exception arg) { RossQoLPlugin.Log.LogError((object)$"AutoHarvest: dropping {num4} x {((Object)item).name} at a producer failed: {arg}"); } } } return placed > 0; } public static int PlaceWholeStack(ItemDrop item, int amount, bool cheated, Vector3 origin) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) int placed = 0; Place(item, amount, amount, wholeOnly: true, cheated, origin, ref placed); return placed; } private static void Place(ItemDrop item, int amount, int unitSize, bool wholeOnly, bool cheated, Vector3 origin, ref int placed) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) if (amount <= 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return; } SharedData shared = item.m_itemData.m_shared; string name = shared.m_name; int num = Math.Max(1, shared.m_maxStackSize); float radius = ProductionConfig.HarvestRadius?.Value ?? 40f; long playerID = Game.instance.GetPlayerProfile().GetPlayerID(); ContainerRegistry.Near(origin, radius, Nearby); Destinations.Clear(); Candidates.Clear(); foreach (Container item2 in Nearby) { if (!ContainerAccess.MayUse(item2, playerID) || !ContainerAccess.IsFresh(item2)) { continue; } Inventory inventory = item2.GetInventory(); if (inventory == null) { continue; } int num2 = HarvestMath.Room(inventory.FindFreeStackSpace(name, (float)Game.m_worldLevel), inventory.GetEmptySlots(), num); if (num2 > 0) { ZNetView nview = item2.m_nview; if (ContainerOwnership.IsSettled(item2, nview)) { List<DestinationCandidate> candidates = Candidates; int count = Destinations.Count; bool num3 = inventory.HaveItem(name, false); Vector3 val = ((Component)item2).transform.position - origin; candidates.Add(new DestinationCandidate(count, num3, num2, ((Vector3)(ref val)).sqrMagnitude)); Destinations.Add(inventory); } } } if (Candidates.Count == 0) { return; } foreach (Placement item3 in HarvestPlan.Plan(amount, unitSize, (IReadOnlyList<DestinationCandidate>)HarvestPlan.Rank((IEnumerable<DestinationCandidate>)Candidates), wholeOnly)) { Placement current2 = item3; AddTo(Destinations[((Placement)(ref current2)).Index], item, ((Placement)(ref current2)).Amount, cheated, num, ref placed); } } private static void AddTo(Inventory inventory, ItemDrop item, int amount, bool cheated, int maxStack, ref int placed) { string name = item.m_itemData.m_shared.m_name; int num = inventory.CountItems(name, -1, false); try { int num2 = amount; while (num2 > 0) { int num3 = Math.Min(num2, maxStack); ItemData val = item.m_itemData.Clone(); val.m_dropPrefab = ((Component)item).gameObject; val.m_stack = num3; val.m_worldLevel = (byte)Game.m_worldLevel; val.m_cheated = cheated; if (!inventory.AddItem(val)) { break; } num2 -= num3; } } finally { int num4 = inventory.CountItems(name, -1, false) - num; placed += Mathf.Clamp(num4, 0, amount); } } private static void DropAt(ItemDrop item, int amount, Vector3 position, bool cheated) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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_0037: Unknown result type (might be due to invalid IL or missing references) int val = Math.Max(1, item.m_itemData.m_shared.m_maxStackSize); while (amount > 0) { int num = Math.Min(amount, val); ItemDrop obj = Object.Instantiate<ItemDrop>(item, position + Vector3.up * 0.25f, Quaternion.identity); ItemDrop.OnCreateNew(obj, cheated); obj.SetStack(num); amount -= num; } } } internal static class HarvestGate { private static readonly ConditionalWeakTable<MonoBehaviour, StrongBox<double>> LastAttempt = new ConditionalWeakTable<MonoBehaviour, StrongBox<double>>(); private static readonly ConditionalWeakTable<MonoBehaviour, StrongBox<string>> LastFailure = new ConditionalWeakTable<MonoBehaviour, StrongBox<string>>(); public static bool ShouldRun(MonoBehaviour producer, ZNetView nview, ConfigEntry<bool> kindEnabled) { AutoHarvestFeature instance = AutoHarvestFeature.Instance; if (instance == null || !instance.IsActive) { return false; } if (kindEnabled == null || !kindEnabled.Value) { return false; } if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { return false; } double num = Time.time; float num2 = ProductionConfig.HarvestInterval?.Value ?? 10f; StrongBox<double> value; bool flag = LastAttempt.TryGetValue(producer, out value); if (!HarvestMath.IsDue(flag ? new double?(value.Value) : ((double?)null), num, (double)num2)) { return false; } if (flag) { value.Value = num; } else { LastAttempt.Add(producer, new StrongBox<double>(num)); } return true; } public static void LogFailure(MonoBehaviour producer, ZNetView nview, Exception ex) { string fullName = ex.GetType().FullName; if ((Object)(object)producer != (Object)null) { if (LastFailure.TryGetValue(producer, out var value)) { if (value.Value == fullName) { return; } value.Value = fullName; } else { LastFailure.Add(producer, new StrongBox<string>(fullName)); } } string arg = (Object.op_Implicit((Object)(object)producer) ? ((Object)producer).name : "a destroyed producer"); string arg2 = (((Object)(object)nview != (Object)null && nview.GetZDO() != null) ? ((object)Unsafe.As<ZDOID, ZDOID>(ref nview.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString() : "no ZDO"); RossQoLPlugin.Log.LogError((object)$"AutoHarvest: harvesting {arg} ({arg2}) failed and was skipped (repeats of this error are not logged): {ex}"); } public static void Succeeded(MonoBehaviour producer) { LastFailure.Remove(producer); } } [HarmonyPatch(typeof(Beehive), "UpdateBees")] internal static class BeehiveHarvestPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Beehive), "UpdateBees", "Production/AutoHarvest"); } private static void Postfix(Beehive __instance) { AutoHarvestFeature instance = AutoHarvestFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (HarvestGate.ShouldRun((MonoBehaviour)(object)__instance, __instance.m_nview, ProductionConfig.HarvestBeehives)) { Harvester.HarvestBeehive(__instance); HarvestGate.Succeeded((MonoBehaviour)(object)__instance); } } catch (Exception ex) { HarvestGate.LogFailure((MonoBehaviour)(object)__instance, __instance.m_nview, ex); } } } [HarmonyPatch(typeof(SapCollector), "UpdateTick")] internal static class SapCollectorHarvestPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(SapCollector), "UpdateTick", "Production/AutoHarvest"); } private static void Postfix(SapCollector __instance) { AutoHarvestFeature instance = AutoHarvestFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (HarvestGate.ShouldRun((MonoBehaviour)(object)__instance, __instance.m_nview, ProductionConfig.HarvestSapCollectors)) { Harvester.HarvestSapCollector(__instance); HarvestGate.Succeeded((MonoBehaviour)(object)__instance); } } catch (Exception ex) { HarvestGate.LogFailure((MonoBehaviour)(object)__instance, __instance.m_nview, ex); } } } [HarmonyPatch(typeof(Fermenter), "SlowUpdate")] internal static class FermenterHarvestPatch { private static bool Prepare() { return ValheimCompat.RequireMethod(typeof(Fermenter), "SlowUpdate", "Production/AutoHarvest"); } private static void Postfix(Fermenter __instance) { AutoHarvestFeature instance = AutoHarvestFeature.Instance; if (instance == null || !instance.IsActive) { return; } try { if (HarvestGate.ShouldRun((MonoBehaviour)(object)__instance, __instance.m_nview, ProductionConfig.HarvestFermenters)) { Harvester.HarvestFermenter(__instance); HarvestGate.Succeeded((MonoBehaviour)(object)__instance); } } catch (Exception ex) { HarvestGate.LogFailure((MonoBehaviour)(object)__instance, __instance.m_nview, ex); } } } public static class ProductionConfig { public static ConfigEntry<bool> HarvestBeehives; public static ConfigEntry<bool> HarvestSapCollectors; public static ConfigEntry<bool> HarvestFermenters; public static ConfigEntry<bool> HarvestWindmills; public static ConfigEntry<float> HarvestRadius; public static ConfigEntry<float> HarvestInterval; 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) HarvestBeehives = config.Bind<bool>(section, "HarvestBeehives", true, ConfigText.Description("Beehives empty their honey into nearby containers.", scope, requiresRestart: false)); HarvestSapCollectors = config.Bind<bool>(section, "HarvestSapCollectors", true, ConfigText.Description("Sap collectors empty their sap into nearby containers.", scope, requiresRestart: false)); HarvestFermenters = config.Bind<bool>(section, "HarvestFermenters", true, ConfigText.Description("Fermenters empty finished batches into nearby containers. A batch moves whole or not at all.", scope, requiresRestart: false)); HarvestWindmills = con