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 Hearthhold v0.1.0
plugins\Hearthhold.dll
Decompiled 3 days 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.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Charter; using ConfigReload; using HarmonyLib; using Microsoft.CodeAnalysis; using PatchGuard; using SyncedConfig; using UnityEngine; using YamlConfig; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Hearthhold")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Hearthhold: Stardew Valley style quality stars for Valheim food")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+ad278ecf323901c3645eb1f9f6fac6833a0f8cb1")] [assembly: AssemblyProduct("Hearthhold")] [assembly: AssemblyTitle("Hearthhold")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.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 Hearthhold { public sealed class AgingCask : MonoBehaviour { public const float Interval = 5f; private Container container; private uint hoverRevision = uint.MaxValue; private float hoverAt = -1f; private string hoverLine = ""; private Container Container { get { if ((Object)(object)container == (Object)null) { container = ((Component)this).GetComponent<Container>(); } return container; } } private void Awake() { ((MonoBehaviour)this).InvokeRepeating("Tick", Random.Range(1f, 5f), 5f); } private void Tick() { HookGuard.Run("aging cask", delegate(AgingCask cask) { cask.Age(); }, this); } private void Age() { ZDO val = OwnedIdle(); if (val == null) { return; } Inventory inventory = container.GetInventory(); List<CaskRecord> list = CaskRecords.Read(val); double timeSeconds = ZNet.instance.GetTimeSeconds(); bool raised; List<CaskRecord> list2 = CaskAging.Age(inventory, list, timeSeconds, CaskAging.Period(EnvMan.instance), out raised); if (raised || !CaskRecords.Equal(list, list2)) { CaskRecords.Write(val, list2); if (raised) { inventory.Changed(false, false); } else { container.m_lastRevision = val.DataRevision; } } } private ZDO OwnedIdle() { if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)EnvMan.instance == (Object)null || (Object)(object)Container == (Object)null) { return null; } ZNetView nview = container.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || container.IsInUse() || container.GetInventory() == null) { return null; } ZDO zDO = nview.GetZDO(); if (zDO.DataRevision != container.m_lastRevision) { return null; } return zDO; } public string HoverLine() { ZNetView val = (((Object)(object)Container != (Object)null) ? container.m_nview : null); if ((Object)(object)val == (Object)null || !val.IsValid() || (Object)(object)ZNet.instance == (Object)null || (Object)(object)EnvMan.instance == (Object)null) { return "Meads and wines gain a star every 2 days"; } ZDO zDO = val.GetZDO(); if (zDO.DataRevision == hoverRevision && Time.time - hoverAt < 1f) { return hoverLine; } hoverRevision = zDO.DataRevision; hoverAt = Time.time; hoverLine = CaskHover.Line(CaskRecords.Read(zDO), ZNet.instance.GetTimeSeconds(), EnvMan.instance); return hoverLine; } } public static class CaskAging { public const float DaysPerStar = 2f; public static double Period(EnvMan env) { return 2.0 * (double)env.m_dayLengthSec; } public static List<CaskRecord> Age(Inventory inventory, List<CaskRecord> old, double now, double period, out bool raised) { raised = false; List<CaskRecord> list = new List<CaskRecord>(); foreach (ItemData allItem in inventory.GetAllItems()) { if (Ages(allItem)) { CaskRecord record = Track(allItem, old, now); raised |= Raise(allItem, ref record, now, period); list.Add(record); } } return list; } private static bool Ages(ItemData item) { if ((Object)(object)item?.m_dropPrefab != (Object)null && Kitchen.IsFermented(((Object)item.m_dropPrefab).name)) { return Stars.IsStarItem(item); } return false; } private static CaskRecord Track(ItemData item, List<CaskRecord> old, double now) { CaskRecord result = new CaskRecord { X = item.m_gridPos.x, Y = item.m_gridPos.y, Item = ((Object)item.m_dropPrefab).name, Stars = Stars.Get(item), Stack = item.m_stack, Start = now }; foreach (CaskRecord item2 in old) { if (item2.X == result.X && item2.Y == result.Y && item2.Item == result.Item && item2.Stars == result.Stars && result.Stack <= item2.Stack && item2.Start <= now) { result.Start = item2.Start; } } return result; } private static bool Raise(ItemData item, ref CaskRecord record, double now, double period) { int stars = record.Stars; while (record.Stars < 3 && now - record.Start >= period) { record.Stars++; record.Start += period; } if (record.Stars == stars) { return false; } Stars.Set(item, record.Stars); return true; } } public static class CaskHover { [HarmonyPatch(typeof(Container), "GetHoverText")] private static class HoverText { [HarmonyPostfix] private static void Postfix(Container __instance, ref string __result) { AgingCask agingCask = default(AgingCask); if (((Component)__instance).TryGetComponent<AgingCask>(ref agingCask)) { __result = __result + "\n" + agingCask.HoverLine(); } } } public const string Rule = "Meads and wines gain a star every 2 days"; public static string Line(List<CaskRecord> records, double now, EnvMan env) { double num = CaskAging.Period(env); double num2 = double.MaxValue; foreach (CaskRecord record in records) { if (record.Stars < 3) { num2 = Math.Min(num2, record.Start + num - now); } } if (num2 == double.MaxValue || env.m_dayLengthSec <= 0) { return "Meads and wines gain a star every 2 days"; } double num3 = Math.Max(0.0, num2) / (double)env.m_dayLengthSec; return string.Format("{0}\nNext star in {1:0.0} days", "Meads and wines gain a star every 2 days", num3); } } public static class CaskKeys { public const string Prefab = "hearthhold_aging_cask"; public const string Aging = "hearthhold_cask_aging"; } public static class CaskPiece { [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class SceneAwake { [HarmonyPrefix] private static void Prefix() { ClonedPieces.Add(Piece); } } public const string DisplayName = "Aging Cask"; public const string Description = "Meads and wines stored inside gain a star every 2 days, up to gold."; public static readonly ClonedPiece Piece = new ClonedPiece("hearthhold_aging_cask", "piece_chest_barrel", "Aging Cask", "Meads and wines stored inside gain a star every 2 days, up to gold.", ("FineWood", 10), ("BronzeNails", 10), ("Resin", 5)) { Dress = DressCopy }; private static void DressCopy(GameObject copy) { Container componentInChildren = copy.GetComponentInChildren<Container>(true); if ((Object)(object)componentInChildren == (Object)null) { Hearthhold.Log.LogWarning((object)"The game's barrel has no container: the Aging Cask ages nothing."); return; } componentInChildren.m_name = "Aging Cask"; ((Component)componentInChildren).gameObject.AddComponent<AgingCask>(); } } public struct CaskRecord { public int X; public int Y; public string Item; public int Stars; public int Stack; public double Start; public bool SameAs(CaskRecord other) { if (X == other.X && Y == other.Y && Item == other.Item && Stars == other.Stars && Stack == other.Stack) { return Start == other.Start; } return false; } } public static class CaskRecords { private const int Version = 1; public static List<CaskRecord> Read(ZDO zdo) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown List<CaskRecord> list = new List<CaskRecord>(); byte[] array = ((zdo != null) ? zdo.GetByteArray("hearthhold_cask_aging", (byte[])null) : null); if (array == null || array.Length == 0) { return list; } ZPackage val = new ZPackage(array); if (val.ReadInt() != 1) { return list; } int num = val.ReadInt(); for (int i = 0; i < num; i++) { list.Add(ReadOne(val)); } return list; } public static void Write(ZDO zdo, List<CaskRecord> records) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(records.Count); foreach (CaskRecord record in records) { WriteOne(val, record); } zdo.Set("hearthhold_cask_aging", val.GetArray()); } public static bool Equal(List<CaskRecord> a, List<CaskRecord> b) { if (a.Count != b.Count) { return false; } for (int i = 0; i < a.Count; i++) { if (!a[i].SameAs(b[i])) { return false; } } return true; } private static CaskRecord ReadOne(ZPackage package) { return new CaskRecord { X = package.ReadInt(), Y = package.ReadInt(), Item = package.ReadString(), Stars = package.ReadInt(), Stack = package.ReadInt(), Start = package.ReadDouble() }; } private static void WriteOne(ZPackage package, CaskRecord record) { package.Write(record.X); package.Write(record.Y); package.Write(record.Item ?? ""); package.Write(record.Stars); package.Write(record.Stack); package.Write(record.Start); } } public static class Discovery { [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class SceneAwake { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { HookGuard.Run("discovery", Run); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class DatabaseAwake { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { HookGuard.Run("discovery", Run); } } private static void Run() { if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items.Count == 0) { return; } List<GameObject> prefabs = ZNetScene.instance.m_prefabs; foreach (GameObject item in prefabs) { if ((Object)(object)item != (Object)null) { AddKitchenAndPlants(item); } } foreach (Recipe recipe in ObjectDB.instance.m_recipes) { AddRecipe(recipe); } foreach (GameObject item2 in prefabs) { if ((Object)(object)item2 != (Object)null) { AddSources(item2); } } StarItems.RegisterAll(); } private static void AddRecipe(Recipe recipe) { if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null) && Kitchen.IsKitchen(recipe.m_craftingStation) && IsFoodish(recipe.m_item)) { Kitchen.AddProduct(recipe.m_item, fromFermenter: false); Requirement[] resources = recipe.m_resources; for (int i = 0; i < resources.Length; i++) { Kitchen.AddIngredient(resources[i]?.m_resItem); } } } private static void AddKitchenAndPlants(GameObject prefab) { CookingStation component = prefab.GetComponent<CookingStation>(); if (Kitchen.IsKitchen(component)) { foreach (ItemConversion item in component.m_conversion) { AddConversion(item.m_from, item.m_to, fermenter: false); } } Fermenter component2 = prefab.GetComponent<Fermenter>(); if ((Object)(object)component2 != (Object)null) { foreach (ItemConversion item2 in component2.m_conversion) { AddConversion(item2.m_from, item2.m_to, fermenter: true); } } Plant component3 = prefab.GetComponent<Plant>(); if (component3?.m_grownPrefabs == null) { return; } GameObject[] grownPrefabs = component3.m_grownPrefabs; foreach (GameObject val in grownPrefabs) { if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent<Pickable>() != (Object)null) { Sources.AddCropPickable(((Object)val).name); } } } private static void AddConversion(ItemDrop from, ItemDrop to, bool fermenter) { Kitchen.AddIngredient(from); Kitchen.AddProduct(to, fermenter); Kitchen.AddConversion(from, to); } private static void AddSources(GameObject prefab) { Pickable component = prefab.GetComponent<Pickable>(); if ((Object)(object)component != (Object)null && (Object)(object)component.m_itemPrefab != (Object)null) { AddPick(((Object)prefab).name, component.m_itemPrefab); } Beehive component2 = prefab.GetComponent<Beehive>(); if ((Object)(object)component2 != (Object)null && (Object)(object)component2.m_honeyItem != (Object)null) { AddProduce(((Component)component2.m_honeyItem).gameObject); } SapCollector component3 = prefab.GetComponent<SapCollector>(); if ((Object)(object)component3 != (Object)null && (Object)(object)component3.m_spawnItem != (Object)null) { AddProduce(((Component)component3.m_spawnItem).gameObject); } AddMeat(prefab.GetComponent<CharacterDrop>()); } private static void AddPick(string pickable, GameObject item) { if (Sources.IsCropPickable(pickable)) { if (IsFood(item) || Kitchen.IsIngredient(((Object)item).name)) { Sources.AddCrop(((Object)item).name); } } else if (IsFood(item) || Sources.ForageExtras.Contains(((Object)item).name)) { Sources.AddForage(((Object)item).name); } } private static void AddProduce(GameObject item) { if (IsFood(item) || Sources.ForageExtras.Contains(((Object)item).name)) { Sources.AddForage(((Object)item).name); } } private static void AddMeat(CharacterDrop drops) { if (drops?.m_drops == null) { return; } foreach (Drop drop in drops.m_drops) { if ((Object)(object)drop?.m_prefab != (Object)null && (IsFood(drop.m_prefab) || Sources.MeatExtras.Contains(((Object)drop.m_prefab).name)) && (Object)(object)drop.m_prefab.GetComponent<EggGrow>() == (Object)null) { Sources.AddMeat(((Object)drop.m_prefab).name); } } } private static bool IsFoodish(ItemDrop item) { SharedData val = item.m_itemData?.m_shared; if (val != null) { if (!(Kitchen.Value(val) > 0f)) { return Kitchen.IsStationInput(((Object)((Component)item).gameObject).name); } return true; } return false; } private static bool IsFood(GameObject item) { ItemDrop component = item.GetComponent<ItemDrop>(); if (component?.m_itemData?.m_shared == null) { return false; } if (!(Kitchen.Value(component.m_itemData.m_shared) > 0f)) { return Kitchen.IsStationInput(((Object)item).name); } return true; } } public static class Kitchen { public const SkillType Skill = (SkillType)105; private static readonly HashSet<string> products = new HashSet<string>(); private static readonly HashSet<string> ingredients = new HashSet<string>(); private static readonly HashSet<string> fermented = new HashSet<string>(); private static readonly HashSet<string> stationInputs = new HashSet<string>(); private static readonly Dictionary<string, float> values = new Dictionary<string, float>(); internal static int ProductCount => products.Count; public static bool IsKitchen(CookingStation station) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((Object)(object)station != (Object)null) { return (int)station.m_skill == 105; } return false; } public static bool IsKitchen(CraftingStation station) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((Object)(object)station != (Object)null) { return (int)station.m_craftingSkill == 105; } return false; } public static bool IsProduct(string prefabName) { if (prefabName != null) { return products.Contains(prefabName); } return false; } public static bool IsIngredient(string prefabName) { if (prefabName != null) { return ingredients.Contains(prefabName); } return false; } public static bool IsStationInput(string prefabName) { if (prefabName != null) { return stationInputs.Contains(prefabName); } return false; } public static bool IsFermented(string prefabName) { if (prefabName != null) { return fermented.Contains(prefabName); } return false; } public static float FoodValue(ItemData item) { if (item?.m_shared == null) { return 0f; } if (!values.TryGetValue(item.m_shared.m_name, out var value)) { return Value(item.m_shared); } return value; } public static float Value(SharedData shared) { return shared.m_food + shared.m_foodStamina + shared.m_foodEitr; } internal static void AddProduct(ItemDrop item, bool fromFermenter) { if (!((Object)(object)item == (Object)null)) { products.Add(((Object)((Component)item).gameObject).name); if (fromFermenter) { fermented.Add(((Object)((Component)item).gameObject).name); } } } internal static void AddIngredient(ItemDrop item) { if ((Object)(object)item != (Object)null) { ingredients.Add(((Object)((Component)item).gameObject).name); } } internal static void AddConversion(ItemDrop from, ItemDrop to) { if (from?.m_itemData?.m_shared != null && to?.m_itemData?.m_shared != null) { stationInputs.Add(((Object)((Component)from).gameObject).name); SharedData shared = from.m_itemData.m_shared; if (Value(shared) <= 0f && !values.ContainsKey(shared.m_name)) { values[shared.m_name] = Value(to.m_itemData.m_shared); } } } public static ItemDrop ItemPrefab(string prefabName) { GameObject val = (((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrEmpty(prefabName)) ? null : ObjectDB.instance.GetItemPrefab(prefabName)); if (!((Object)(object)val == (Object)null)) { return val.GetComponent<ItemDrop>(); } return null; } } public static class Sources { public static readonly HashSet<string> ForageExtras = new HashSet<string> { "Dandelion", "Thistle", "Fiddleheadfern", "MushroomSmokePuff", "RoyalJelly", "Sap" }; public static readonly HashSet<string> MeatExtras = new HashSet<string> { "Entrails", "Bloodbag" }; private static readonly HashSet<string> cropPickables = new HashSet<string>(); private static readonly HashSet<string> forageItems = new HashSet<string>(); private static readonly HashSet<string> cropItems = new HashSet<string>(); private static readonly HashSet<string> meatItems = new HashSet<string>(); public static bool IsCropPickable(string pickablePrefab) { if (pickablePrefab != null) { return cropPickables.Contains(pickablePrefab); } return false; } public static bool TryPickSource(Pickable pickable, out StarSource source) { source = StarSource.Forage; string text = (((Object)(object)pickable != (Object)null && (Object)(object)pickable.m_itemPrefab != (Object)null) ? ((Object)pickable.m_itemPrefab).name : null); if (text == null) { return false; } if (IsCropPickable(Utils.GetPrefabName(((Component)pickable).gameObject))) { source = StarSource.Crop; return cropItems.Contains(text); } return forageItems.Contains(text); } public static bool IsForageItem(string itemPrefab) { if (itemPrefab != null) { return forageItems.Contains(itemPrefab); } return false; } public static bool IsCropItem(string itemPrefab) { if (itemPrefab != null) { return cropItems.Contains(itemPrefab); } return false; } public static bool IsMeatItem(string itemPrefab) { if (itemPrefab != null) { return meatItems.Contains(itemPrefab); } return false; } internal static void AddCropPickable(string pickablePrefab) { cropPickables.Add(pickablePrefab); } internal static void AddForage(string itemPrefab) { forageItems.Add(itemPrefab); } internal static void AddCrop(string itemPrefab) { cropItems.Add(itemPrefab); } internal static void AddMeat(string itemPrefab) { meatItems.Add(itemPrefab); } internal static IEnumerable<string> AllItems() { foreach (string forageItem in forageItems) { yield return forageItem; } foreach (string cropItem in cropItems) { yield return cropItem; } foreach (string meatItem in meatItems) { yield return meatItem; } } } public static class StarItems { private static int registered; internal static void RegisterAll() { int num = 0; foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item != (Object)null && Wanted(((Object)item).name) && Register(item)) { num++; } } if (num != registered) { Hearthhold.Log.LogInfo((object)$"Items that carry stars: {num}."); } registered = num; } private static bool Wanted(string name) { if (!Kitchen.IsProduct(name) && !Sources.IsForageItem(name) && !Sources.IsCropItem(name)) { return Sources.IsMeatItem(name); } return true; } private static bool Register(GameObject prefab) { SharedData val = prefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared; if (val == null || val.m_maxStackSize <= 1 || (Object)(object)prefab.GetComponent<Feast>() != (Object)null) { return false; } val.m_scaleByQuality = 0f; val.m_scaleWeightByQuality = 0f; GrindstoneLink.AddStarItem(prefab); return true; } } public sealed class ClonedPiece { private const string Workbench = "piece_workbench"; private const string Hammer = "Hammer"; private static GameObject holder; private readonly string source; private readonly (string Item, int Amount)[] cost; public string PrefabName { get; } public int Hash { get; } public string DisplayName { get; } public string Description { get; } public Action<GameObject> Dress { get; set; } public Func<bool> Buildable { get; set; } public GameObject Prefab { get; private set; } private static GameObject Holder { get { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)holder != (Object)null) { return holder; } holder = new GameObject("Hearthhold_Prefabs"); holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)holder); return holder; } } public ClonedPiece(string prefabName, string source, string displayName, string description, params (string, int)[] cost) { PrefabName = prefabName; Hash = StringExtensionMethods.GetStableHashCode(prefabName); this.source = source; DisplayName = displayName; Description = description; this.cost = cost; } public bool Is(GameObject instance) { if ((Object)(object)instance != (Object)null && (Object)(object)Prefab != (Object)null) { return Utils.GetPrefabName(instance) == PrefabName; } return false; } internal void Install() { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } if ((Object)(object)Prefab == (Object)null) { Build(instance); } if (!((Object)(object)Prefab == (Object)null)) { if (!instance.m_prefabs.Contains(Prefab)) { instance.m_prefabs.Add(Prefab); } instance.m_namedPrefabs[Hash] = Prefab; RefreshHammer(); } } public void RefreshHammer() { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null && (Object)(object)Prefab != (Object)null) ? ObjectDB.instance.GetItemPrefab("Hammer") : null); PieceTable val2 = ((!((Object)(object)val != (Object)null)) ? null : val.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_buildPieces); if (!((Object)(object)val2 == (Object)null)) { bool flag = Buildable == null || Buildable(); if (flag && !val2.m_pieces.Contains(Prefab)) { val2.m_pieces.Add(Prefab); } else if (!flag) { val2.m_pieces.Remove(Prefab); } } } private void Build(ZNetScene scene) { GameObject prefab = scene.GetPrefab(source); if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent<Piece>() == (Object)null) { Hearthhold.Log.LogWarning((object)("The game's " + source + " was not found: there is no " + DisplayName + ".")); } else { GameObject val = Object.Instantiate<GameObject>(prefab, Holder.transform, false); ((Object)val).name = PrefabName; Configure(val.GetComponent<Piece>(), scene); Dress?.Invoke(val); Prefab = val; } } private void Configure(Piece piece, ZNetScene scene) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) piece.m_name = DisplayName; piece.m_description = Description; piece.m_category = (PieceCategory)0; piece.m_enabled = true; if ((Object)(object)piece.m_craftingStation == (Object)null) { GameObject prefab = scene.GetPrefab("piece_workbench"); piece.m_craftingStation = ((prefab != null) ? prefab.GetComponent<CraftingStation>() : null); } piece.m_resources = (from part in cost select Requirement(scene, part.Item, part.Amount) into r where (Object)(object)r.m_resItem != (Object)null select r).ToArray(); } private static Requirement Requirement(ZNetScene scene, string item, int amount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown Requirement val = new Requirement(); GameObject prefab = scene.GetPrefab(item); val.m_resItem = ((prefab != null) ? prefab.GetComponent<ItemDrop>() : null); val.m_amount = amount; val.m_recover = true; return val; } } public static class ClonedPieces { [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class SceneAwake { [HarmonyPostfix] private static void Postfix() { InstallAll(); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class DatabaseAwake { [HarmonyPostfix] private static void Postfix() { InstallAll(); } } private static readonly List<ClonedPiece> pieces = new List<ClonedPiece>(); public static ClonedPiece Add(ClonedPiece piece) { if (piece != null && !pieces.Contains(piece)) { pieces.Add(piece); } return piece; } private static void InstallAll() { foreach (ClonedPiece piece in pieces) { HookGuard.Run("piece " + piece.PrefabName, piece.Install); } } } public static class GrindstoneLink { public const string Guid = "com.GrindstoneSkills"; private const string StarsType = "GrindstoneSkills.Api.StarsApi"; private const string SkillsType = "GrindstoneSkills.Api.SkillsApi"; private const int Required = 1; private static bool bound; private static bool ready; private static Action<GameObject> addStarItem; private static Func<ItemData, bool> isStarItem; private static Func<ItemData, int> getStars; private static Action<ItemData, int> setStars; private static Func<int, string> starText; private static Func<string, float> localLevel; private static Func<Player, string, float> levelOf; public static bool Ready => Bind(); public static void AddStarItem(GameObject prefab) { if ((Object)(object)prefab != (Object)null && Bind()) { addStarItem(prefab); } } public static bool IsStarItem(ItemData item) { if (item != null && Bind()) { return isStarItem(item); } return false; } public static int GetStars(ItemData item) { if (item == null || item.m_quality <= 1 || !Bind()) { return 0; } return getStars(item); } public static void SetStars(ItemData item, int stars) { if (item != null && Bind()) { setStars(item, stars); } } public static string StarText(int stars) { if (stars <= 0 || !Bind()) { return ""; } return starText(stars); } public static float LocalLevel(string skill) { if (!Bind()) { return 0f; } return localLevel(skill); } public static float LevelOf(Player player, string skill) { if (!((Object)(object)player != (Object)null) || !Bind()) { return 0f; } return levelOf(player, skill); } private static bool Bind() { if (bound) { return ready; } bound = true; Assembly assembly = FindAssembly(); Type type = Api(assembly, "GrindstoneSkills.Api.StarsApi"); Type type2 = Api(assembly, "GrindstoneSkills.Api.SkillsApi"); if (type != null && type2 != null) { BindAll(type, type2); } if (!ready) { Hearthhold.Log.LogError((object)"GrindstoneSkills 0.15.0 or later is needed: without its API nothing gets stars."); } return ready; } private static void BindAll(Type stars, Type skills) { addStarItem = Create<Action<GameObject>>(stars, "AddStarItem"); isStarItem = Create<Func<ItemData, bool>>(stars, "IsStarItem"); getStars = Create<Func<ItemData, int>>(stars, "GetStars"); setStars = Create<Action<ItemData, int>>(stars, "SetStars"); starText = Create<Func<int, string>>(stars, "GetStarText"); localLevel = Create<Func<string, float>>(skills, "GetLocalLevel"); levelOf = Create<Func<Player, string, float>>(skills, "GetLevel"); ready = addStarItem != null && isStarItem != null && getStars != null && setStars != null && starText != null && localLevel != null && levelOf != null; } private static Assembly FindAssembly() { if (!Chainloader.PluginInfos.TryGetValue("com.GrindstoneSkills", out var value) || !((Object)(object)value.Instance != (Object)null)) { return null; } return ((object)value.Instance).GetType().Assembly; } private static Type Api(Assembly assembly, string name) { Type type = assembly?.GetType(name); Func<int> func = ((type != null) ? Create<Func<int>>(type, "GetApiVersion") : null); if (func == null || func() < 1) { return null; } return type; } private static T Create<T>(Type api, string name) where T : Delegate { MethodInfo method = typeof(T).GetMethod("Invoke"); Type[] types = Array.ConvertAll(method.GetParameters(), (ParameterInfo p) => p.ParameterType); MethodInfo method2 = api.GetMethod(name, BindingFlags.Static | BindingFlags.Public, null, types, null); if (!(method2 != null) || !(method2.ReturnType == method.ReturnType)) { return null; } return (T)Delegate.CreateDelegate(typeof(T), method2); } } public static class HookGuard { public static void Run(string context, Action action) { try { action(); } catch (Exception exception) { Guard.Report(exception, context); } } public static T Run<T>(string context, Func<T> func, T fallback) { try { return func(); } catch (Exception exception) { Guard.Report(exception, context); return fallback; } } public static void Run<TArg>(string context, Action<TArg> action, TArg arg) { try { action(arg); } catch (Exception exception) { Guard.Report(exception, context); } } } public static class Keys { public const string RpcMark = "hearthhold_mark"; } public static class Marks { public struct Mark { public float Level; public float Stars; public float Time; } [HarmonyPatch(typeof(ZNetView), "HandleRoutedRPC")] private static class Arrival { [HarmonyPrefix] private static void Prefix(ZNetView __instance, RoutedRPCData rpcData) { if (rpcData.m_methodHash == RpcHash && !__instance.m_functions.ContainsKey(RpcHash)) { ZNetView view = __instance; view.Register<float, float>("hearthhold_mark", (Action<long, float, float>)delegate(long sender, float level, float stars) { Receive(view, sender, level, stars); }); } } } private const float Lifetime = 10f; private static readonly int RpcHash = StringExtensionMethods.GetStableHashCode("hearthhold_mark"); private static readonly Dictionary<(ZDOID, long), Mark> marks = new Dictionary<(ZDOID, long), Mark>(); private static readonly List<(ZDOID, long)> stale = new List<(ZDOID, long)>(); public static void Send(ZNetView nview, float level, float stars) { if ((Object)(object)nview != (Object)null && nview.IsValid()) { nview.InvokeRPC("hearthhold_mark", new object[2] { level, stars }); } } public static bool TryTake(ZNetView nview, long sender, out Mark mark) { //IL_002b: 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) mark = default(Mark); ZDO val = (((Object)(object)nview != (Object)null && nview.IsValid()) ? nview.GetZDO() : null); if (val == null || !marks.TryGetValue((val.m_uid, sender), out mark)) { return false; } marks.Remove((val.m_uid, sender)); return Time.time - mark.Time <= 10f; } private static void Receive(ZNetView nview, long sender, float level, float stars) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { Prune(); Mark value = new Mark { Level = StarOdds.Sane(level), Stars = Mathf.Clamp(StarOdds.Sane(stars), 0f, 3f), Time = Time.time }; marks[(nview.GetZDO().m_uid, sender)] = value; } } private static void Prune() { foreach (KeyValuePair<(ZDOID, long), Mark> mark in marks) { if (Time.time - mark.Value.Time > 10f) { stale.Add(mark.Key); } } foreach (var item in stale) { marks.Remove(item); } stale.Clear(); } } public static class Professions { public const float Level = 100f; public static int Floor(StarSource source, float level) { if (!Has(level)) { return 0; } return FloorOf(source); } public static bool Has(float level) { return level >= 99.99f; } public static int FloorOf(StarSource source) { if (source != StarSource.Forage) { return 2; } return 3; } public static string Name(StarSource source) { return source switch { StarSource.Forage => "Botanist", StarSource.Crop => "Tiller", StarSource.Meat => "Rancher", StarSource.Fillet => "Angler", _ => "Gourmet", }; } public static string Description(StarSource source) { return source switch { StarSource.Forage => "Wild picks, honey and sap are always gold.", StarSource.Crop => "Crops you harvest are at least silver.", StarSource.Meat => "Meat from creatures you kill is at least silver.", StarSource.Fillet => "Raw fish you clean is at least silver.", _ => "Dishes and meads you make are at least silver.", }; } } public static class Settings { public const string General = "1 - General"; public const string Features = "2 - Features"; public static ConfigEntry<bool> LockConfiguration { get; private set; } public static ConfigEntry<bool> DailyFortune { get; private set; } public static ConfigEntry<bool> ShippingCrate { get; private set; } public static ConfigEntry<bool> TraderFriendship { get; private set; } public static void Initialize(SyncedConfiguration config) { LockConfiguration = config.BindLocking("1 - General", "Lock Configuration", defaultValue: true, "Server only. When on, every player uses the server's values for this file and cannot override them locally."); DailyFortune = config.Bind("2 - Features", "Daily Fortune", true, "Each day the Norns grant good or bad fortune, the same for everyone, which nudges every star roll up or down.", true, null); ShippingCrate = config.Bind("2 - Features", "Shipping Crate", true, "The Shipping Crate piece: what you leave in it is bought at dawn for coins, more for more stars. Off: the piece cannot be built and existing crates no longer pay.", true, null); TraderFriendship = config.Bind("2 - Features", "Trader Friendship", true, "Give a starred dish to Haldor, Hildir or the Bog Witch once a day to earn their friendship and a small discount.", true, null); } } public static class SpawnStars { public delegate int Roller(ItemData item); [HarmonyPatch(typeof(ItemDrop), "Awake")] private static class Woke { [HarmonyPostfix] private static void Postfix(ItemDrop __instance) { if (current != null) { HookGuard.Run("spawn stars", delegate(ItemDrop item) { Apply(item); }, __instance); } } } private static Roller current; public static Roller Open(Roller roller) { Roller result = current; current = roller; return result; } public static void Close(Roller previous) { current = previous; } private static void Apply(ItemDrop item) { if (!((Object)(object)item.m_nview == (Object)null) && item.m_nview.IsValid() && item.m_nview.IsOwner() && Stars.IsStarItem(item.m_itemData)) { int num = current(item.m_itemData); if (num > 0) { Stars.SetOnDrop(item, num); } } } } public static class StarOdds { public const float MaxEffective = 150f; public const float PerIngredientStar = 20f; public const float PerCreatureStar = 20f; public const float MaxCreatureBonus = 60f; public const float PerFishLevel = 15f; private const float Step = 50f; private static readonly float[][] Tables = new float[4][] { new float[4] { 0.9f, 0.09f, 0.01f, 0f }, new float[4] { 0.55f, 0.3f, 0.12f, 0.03f }, new float[4] { 0.25f, 0.38f, 0.26f, 0.11f }, new float[4] { 0.05f, 0.35f, 0.35f, 0.25f } }; public static float IngredientBonus(float averageStars) { return Mathf.Clamp(averageStars, 0f, 3f) * 20f; } public static int IngredientFloor(float averageStars) { return Mathf.Clamp(Mathf.FloorToInt(averageStars + 0.001f), 0, 3); } public static float CreatureBonus(int creatureLevel) { return Mathf.Clamp((float)(creatureLevel - 1) * 20f, 0f, 60f); } public static float FishBonus(int fishLevel) { return (float)Mathf.Clamp(fishLevel - 1, 0, 4) * 15f; } public static float Effective(float level, float bonus) { return Mathf.Clamp(Sane(level) + Sane(bonus) + Fortune.Levels(), 0f, 150f); } public static float Chance(float effective, int stars) { float num = Mathf.Clamp(effective, 0f, 150f) / 50f; int num2 = Mathf.Min((int)num, Tables.Length - 2); float num3 = num - (float)num2; int num4 = Mathf.Clamp(stars, 0, 3); return Mathf.Lerp(Tables[num2][num4], Tables[num2 + 1][num4], num3); } public static int Roll(float level, float bonus, int floor) { float effective = Effective(level, bonus); float num = Random.value; int num2 = 3; for (int i = 0; i < 3; i++) { num -= Chance(effective, i); if (num < 0f) { num2 = i; break; } } return Mathf.Clamp(Mathf.Max(num2, floor), 0, 3); } public static float Sane(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Mathf.Max(0f, value); } return 0f; } } public static class Stars { public const int Max = 3; private static readonly string[] tierNames = new string[4] { "Plain", "Bronze", "Silver", "Gold" }; public static int Get(ItemData item) { return GrindstoneLink.GetStars(item); } public static bool IsStarItem(ItemData item) { return GrindstoneLink.IsStarItem(item); } public static void Set(ItemData item, int stars) { GrindstoneLink.SetStars(item, Mathf.Clamp(stars, 0, 3)); } public static void SetOnDrop(ItemDrop drop, int stars) { if (!((Object)(object)drop == (Object)null) && IsStarItem(drop.m_itemData)) { Set(drop.m_itemData, stars); drop.SetQuality(drop.m_itemData.m_quality); if ((Object)(object)drop.m_nview != (Object)null && drop.m_nview.IsValid() && drop.m_nview.IsOwner()) { drop.Save(); } } } public static int ToQuality(int stars) { return Mathf.Clamp(stars, 0, 3) + 1; } public static string Text(int stars) { return GrindstoneLink.StarText(stars); } public static string TierName(int stars) { return tierNames[Mathf.Clamp(stars, 0, 3)]; } } public enum StarSource { Forage, Crop, Meat, Dish, Fillet } public static class StarSources { public static string Skill(StarSource source) { return source switch { StarSource.Forage => "Foraging", StarSource.Crop => "Farming", StarSource.Meat => "Husbandry", StarSource.Fillet => "Fishing", _ => "Cooking", }; } } public static class EatBonus { private const float FoodPerStar = 0.1f; private const float DurationPerStar = 0.1f; private const float RestorePerStar = 0.1f; private const float LastingPerStar = 0.15f; public static float Food(int stars) { return 0.1f * (float)Clamp(stars); } public static float Duration(int stars) { return 0.1f * (float)Clamp(stars); } public static float Restore(int stars) { return 0.1f * (float)Clamp(stars); } public static float Lasting(int stars) { return 0.15f * (float)Clamp(stars); } public static int Percent(float share) { return Mathf.RoundToInt(share * 100f); } public static float Whole(float value) { if (!(value <= 0f)) { return Mathf.Ceil(value - 0.5f - 0.001f); } return 0f; } public static float Boosted(float baseValue, float bonus) { return Whole(baseValue * (1f + bonus)); } public static float RoundedShare(float baseValue, float bonus) { if (!(baseValue > 0f)) { return 0f; } return Boosted(baseValue, bonus) / baseValue - 1f; } private static int Clamp(int stars) { return Mathf.Clamp(stars, 0, 3); } } public static class EatingKeys { public const string ActiveFood = "hearthhold_food_"; } public static class FoodCleanup { [HarmonyPatch(typeof(Player), "UpdateFood")] private static class Expired { [HarmonyPrefix] private static void Prefix(Player __instance, out int __state) { __state = __instance.m_foods.Count; } [HarmonyPostfix] private static void Postfix(Player __instance, int __state) { if (__instance.m_foods.Count < __state) { FoodStars.Prune(__instance); } } } [HarmonyPatch(typeof(Player), "ClearFood")] private static class Cleared { [HarmonyPostfix] private static void Postfix(Player __instance) { FoodStars.Prune(__instance); } } [HarmonyPatch(typeof(Player), "RemoveOneFood")] private static class RemovedOne { [HarmonyPostfix] private static void Postfix(Player __instance, bool __result) { if (__result) { FoodStars.Prune(__instance); } } } [HarmonyPatch(typeof(Player), "OnDeath")] private static class Died { [HarmonyPostfix] private static void Postfix(Player __instance) { FoodStars.Prune(__instance); } } [HarmonyPatch(typeof(Player), "Load")] private static class Loaded { [HarmonyPostfix] private static void Postfix(Player __instance) { FoodStars.Prune(__instance); } } } [HarmonyPatch(typeof(Player), "EatFood")] public static class FoodEaten { [HarmonyPostfix] private static void Postfix(Player __instance, ItemData item, bool __result) { if (__result && item?.m_shared != null) { HookGuard.Run("eat starred food", delegate { Record(__instance, item); }); } } private static void Record(Player player, ItemData item) { Food val = Find(player, item); if (val != null) { int stars = Stars.Get(item); FoodStars.Set(player, val.m_name, stars); Extend(val, item, stars); FoodStars.Prune(player); FoodTotals.Refresh(player); } } private static void Extend(Food food, ItemData item, int stars) { float num = EatBonus.Duration(stars); float foodBurnTime = item.m_shared.m_foodBurnTime; if (num > 0f && foodBurnTime > 0f) { food.m_time = Mathf.Max(food.m_time, foodBurnTime * (1f + num)); } } private static Food Find(Player player, ItemData item) { foreach (Food food in player.m_foods) { if (food.m_item == item) { return food; } } foreach (Food food2 in player.m_foods) { if (food2.m_item?.m_shared?.m_name == item.m_shared.m_name) { return food2; } } return null; } } public static class FoodMessage { [HarmonyPatch(typeof(Player), "EatFood")] private static class Scope { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ItemData item, out ItemData __state) { __state = eating; eating = ((Stars.Get(item) > 0) ? item : null); } [HarmonyFinalizer] private static void Finalizer(ItemData __state) { eating = __state; } } [HarmonyPatch(typeof(Player), "Message")] private static class Rewrite { [HarmonyPrefix] private static void Prefix(MessageType type, ref string msg) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (eating?.m_shared != null && (int)type == 2 && msg == Text(eating.m_shared, 0f)) { msg = Text(eating.m_shared, EatBonus.Food(Stars.Get(eating))); } } } private static ItemData eating; private static string Text(SharedData shared, float bonus) { return Part(shared.m_food, bonus, "$item_food_health") + Part(shared.m_foodStamina, bonus, "$item_food_stamina") + Part(shared.m_foodEitr, bonus, "$item_food_eitr"); } private static string Part(float value, float bonus, string key) { if (value <= 0f) { return ""; } string text = ((bonus > 0f) ? EatBonus.Boosted(value, bonus).ToString("0") : value.ToString()); return " +" + text + " " + key + " "; } } public static class FoodStars { private static readonly Dictionary<string, string> keys = new Dictionary<string, string>(); private static readonly List<string> stale = new List<string>(); public static int Get(Player player, Food food) { if (player.m_customData.Count == 0 || string.IsNullOrEmpty(food?.m_name)) { return 0; } if (!player.m_customData.TryGetValue(Key(food.m_name), out var value)) { return 0; } if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0; } return Mathf.Clamp(result, 0, 3); } public static void Set(Player player, string foodName, int stars) { if (!string.IsNullOrEmpty(foodName)) { stars = Mathf.Clamp(stars, 0, 3); if (stars > 0) { player.m_customData[Key(foodName)] = stars.ToString(CultureInfo.InvariantCulture); } else { player.m_customData.Remove(Key(foodName)); } } } public static void Prune(Player player) { if ((Object)(object)player == (Object)null || player.m_customData.Count == 0) { return; } foreach (string key in player.m_customData.Keys) { if (key.StartsWith("hearthhold_food_", StringComparison.Ordinal) && !IsActive(player, key)) { stale.Add(key); } } foreach (string item in stale) { player.m_customData.Remove(item); } stale.Clear(); } private static bool IsActive(Player player, string key) { foreach (Food food in player.m_foods) { if (!string.IsNullOrEmpty(food.m_name) && Key(food.m_name) == key) { return true; } } return false; } private static string Key(string foodName) { if (!keys.TryGetValue(foodName, out var value)) { value = (keys[foodName] = "hearthhold_food_" + foodName); } return value; } } [HarmonyPatch(typeof(Player), "GetTotalFoodValue")] public static class FoodTotals { [HarmonyPostfix] private static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr) { if (__instance.m_customData.Count == 0) { return; } foreach (Food food in __instance.m_foods) { float num = EatBonus.Food(FoodStars.Get(__instance, food)); SharedData val = food.m_item?.m_shared; if (!(num <= 0f) && val != null) { hp += food.m_health * EatBonus.RoundedShare(val.m_food, num); stamina += food.m_stamina * EatBonus.RoundedShare(val.m_foodStamina, num); eitr += food.m_eitr * EatBonus.RoundedShare(val.m_foodEitr, num); } } } public static void Refresh(Player player) { float num = default(float); float num2 = default(float); float num3 = default(float); player.GetTotalFoodValue(ref num, ref num2, ref num3); player.SetMaxHealth(num, true); player.SetMaxStamina(num2, true); player.SetMaxEitr(num3, true); } } public static class MeadBoost { public static void Apply(StatusEffect effect, int stars) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown switch (MeadKinds.Of(effect)) { case MeadKind.Restore: Restore((SE_Stats)effect, EatBonus.Restore(stars)); break; case MeadKind.Lasting: effect.m_ttl *= 1f + EatBonus.Lasting(stars); break; } } private static void Restore(SE_Stats stats, float bonus) { if (!(bonus <= 0f) && !((Object)(object)((StatusEffect)stats).m_character == (Object)null)) { UpFront(stats, bonus); if (stats.m_healthOverTime > 0f) { stats.m_healthOverTimeTickHP *= 1f + EatBonus.RoundedShare(stats.m_healthOverTime, bonus); stats.m_healthOverTime = EatBonus.Boosted(stats.m_healthOverTime, bonus); } if (stats.m_staminaOverTime > 0f) { stats.m_staminaOverTime *= 1f + EatBonus.RoundedShare(stats.m_staminaOverTime, bonus); } if (stats.m_eitrOverTime > 0f) { stats.m_eitrOverTime *= 1f + EatBonus.RoundedShare(stats.m_eitrOverTime, bonus); } } } private static void UpFront(SE_Stats stats, float bonus) { Character character = ((StatusEffect)stats).m_character; if (stats.m_healthUpFront > 0f) { character.Heal(EatBonus.Boosted(stats.m_healthUpFront, bonus) - stats.m_healthUpFront, true); stats.m_healthUpFront = EatBonus.Boosted(stats.m_healthUpFront, bonus); } if (stats.m_staminaUpFront > 0f) { character.AddStamina(EatBonus.Boosted(stats.m_staminaUpFront, bonus) - stats.m_staminaUpFront); stats.m_staminaUpFront = EatBonus.Boosted(stats.m_staminaUpFront, bonus); } if (stats.m_eitrUpFront > 0f) { character.AddEitr(EatBonus.Boosted(stats.m_eitrUpFront, bonus) - stats.m_eitrUpFront); stats.m_eitrUpFront = EatBonus.Boosted(stats.m_eitrUpFront, bonus); } } } public static class MeadDrink { private struct Drink { public StatusEffect Effect; public int Stars; } [HarmonyPatch(typeof(Player), "ConsumeItem")] private static class Consumed { [HarmonyPrefix] private static void Prefix(ItemData item, out Drink __state) { __state = Open(item); } [HarmonyFinalizer] private static void Finalizer(Drink __state) { current = __state; } } [HarmonyPatch(typeof(ItemDrop), "Eat")] private static class EatenFromGround { [HarmonyPrefix] private static void Prefix(ItemDrop __instance, out Drink __state) { __state = Open(__instance.m_itemData); } [HarmonyFinalizer] private static void Finalizer(Drink __state) { current = __state; } } [HarmonyPatch(typeof(SEMan), "AddStatusEffect", new Type[] { typeof(StatusEffect), typeof(bool), typeof(int), typeof(float), typeof(short) })] private static class Added { [HarmonyPostfix] private static void Postfix(SEMan __instance, StatusEffect statusEffect, StatusEffect __result) { if (current.Effect != null && statusEffect == current.Effect && !((Object)(object)__result == (Object)null) && __instance.m_character is Player) { int stars = current.Stars; current.Effect = null; HookGuard.Run("boost starred mead", delegate { MeadBoost.Apply(__result, stars); }); } } } private static Drink current; private static Drink Open(ItemData item) { Drink result = current; StatusEffect val = item?.m_shared?.m_consumeStatusEffect; int num = (((Object)(object)val != (Object)null) ? Stars.Get(item) : 0); current = ((num > 0) ? new Drink { Effect = val, Stars = num } : default(Drink)); return result; } } public enum MeadKind { None, Restore, Lasting } public static class MeadKinds { public static MeadKind Of(StatusEffect effect) { if ((Object)(object)effect == (Object)null) { return MeadKind.None; } SE_Stats val = (SE_Stats)(object)((effect is SE_Stats) ? effect : null); if (val != null && Restores(val)) { return MeadKind.Restore; } if (!(effect.m_ttl > 0f)) { return MeadKind.None; } return MeadKind.Lasting; } private static bool Restores(SE_Stats stats) { if (!(stats.m_healthUpFront > 0f) && !(stats.m_healthOverTime > 0f) && !(stats.m_staminaUpFront > 0f) && !(stats.m_staminaOverTime > 0f) && !(stats.m_eitrUpFront > 0f)) { return stats.m_eitrOverTime > 0f; } return true; } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] public static class StarTooltip { private static readonly StringBuilder text = new StringBuilder(); [HarmonyPostfix] private static void Postfix(ItemData item, bool appending, ref string __result) { if (appending || __result == null || item?.m_shared == null) { return; } int num = Stars.Get(item); if (num > 0) { text.Clear(); FoodLine(item.m_shared, num); MeadLine(item.m_shared.m_consumeStatusEffect, num); if (text.Length > 0) { __result += text.ToString(); } } } private static void FoodLine(SharedData shared, int stars) { if (!(Kitchen.Value(shared) <= 0f)) { float num = EatBonus.Food(stars); float num2 = EatBonus.Duration(stars); Label(stars).Append('+').Append(EatBonus.Percent(num)).Append("% "); StatLine.Values(text, shared.m_food, shared.m_foodStamina, shared.m_foodEitr, 1f + num); text.Append(", lasts ").Append(EatBonus.Percent(num2)).Append("% longer (") .Append(ItemData.GetDurationString(shared.m_foodBurnTime * (1f + num2))) .Append(')'); } } private static void MeadLine(StatusEffect effect, int stars) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown switch (MeadKinds.Of(effect)) { case MeadKind.Restore: Restore((SE_Stats)effect, EatBonus.Restore(stars), stars); break; case MeadKind.Lasting: Lasting(effect.m_ttl, EatBonus.Lasting(stars), stars); break; } } private static void Restore(SE_Stats stats, float bonus, int stars) { Label(stars).Append("restores ").Append(EatBonus.Percent(bonus)).Append("% more "); float stamina = stats.m_staminaUpFront + (stats.m_staminaOverTimeIsFraction ? 0f : stats.m_staminaOverTime); float health = stats.m_healthUpFront + stats.m_healthOverTime; StatLine.Values(text, health, stamina, stats.m_eitrUpFront + stats.m_eitrOverTime, 1f + bonus); } private static void Lasting(float ttl, float bonus, int stars) { Label(stars).Append("lasts ").Append(EatBonus.Percent(bonus)).Append("% longer (") .Append(ItemData.GetDurationString(ttl * (1f + bonus))) .Append(')'); } private static StringBuilder Label(int stars) { return text.Append("\n<color=orange>").Append(Stars.TierName(stars)).Append("</color>: "); } } public static class StatLine { public static void Values(StringBuilder text, float health, float stamina, float eitr, float factor) { int num = ((health > 0f) ? 1 : 0) + ((stamina > 0f) ? 1 : 0) + ((eitr > 0f) ? 1 : 0); if (num != 0) { int written = 0; Name(text, health, "health", num, ref written); Name(text, stamina, "stamina", num, ref written); Name(text, eitr, "eitr", num, ref written); text.Append(" ("); written = 0; Number(text, health * factor, ref written); Number(text, stamina * factor, ref written); Number(text, eitr * factor, ref written); text.Append(')'); } } private static void Name(StringBuilder text, float value, string name, int count, ref int written) { if (!(value <= 0f)) { if (written > 0) { text.Append((written == count - 1) ? " and " : ", "); } text.Append(name); written++; } } private static void Number(StringBuilder text, float value, ref int written) { if (!(value <= 0f)) { if (written++ > 0) { text.Append(" / "); } text.Append(EatBonus.Whole(value).ToString("0", CultureInfo.InvariantCulture)); } } } public static class Fortune { public enum Kind { IllOmened, Poor, Fair, Good, Blessed } private static readonly float[] levels = new float[5] { -10f, -5f, 0f, 5f, 10f }; private static readonly string[] names = new string[5] { "Ill-omened", "Poor", "Fair", "Good", "Blessed" }; private static int cachedDay = -1; private static int cachedSeed; private static Kind cachedKind = Kind.Fair; public static bool On { get { if (Settings.DailyFortune != null) { return Settings.DailyFortune.Value; } return false; } } public static float Levels() { if (!On) { return 0f; } return levels[(int)Today()]; } public static Kind Today() { if ((Object)(object)EnvMan.instance == (Object)null || WorldGenerator.instance == null) { return Kind.Fair; } return Of(EnvMan.instance.GetDay(), WorldGenerator.instance.GetSeed()); } public static Kind Of(int day, int seed) { if (day == cachedDay && seed == cachedSeed) { return cachedKind; } cachedDay = day; cachedSeed = seed; int num = (StringExtensionMethods.GetStableHashCode($"hearthhold_fortune:{seed}:{day}") & 0x7FFFFFFF) % 100; cachedKind = ((num >= 10) ? ((num < 30) ? Kind.Poor : ((num < 70) ? Kind.Fair : ((num < 90) ? Kind.Good : Kind.Blessed))) : Kind.IllOmened); return cachedKind; } public static string Name(Kind kind) { return names[(int)kind]; } public static float LevelsOf(Kind kind) { return levels[(int)kind]; } public static string Line(Kind kind) { float num = LevelsOf(kind); string text = ((num == 0f) ? "star rolls as usual" : string.Format("star rolls {0}{1:0}", (num > 0f) ? "+" : "", num)); return Name(kind) + " fortune today: " + text; } } public static class FortuneNotice { [HarmonyPatch(typeof(Player), "Update")] private static class Tick { [HarmonyPostfix] private static void Postfix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } timer += Time.deltaTime; if (!(timer < 3f)) { timer = 0f; HookGuard.Run("fortune notice", delegate(Player player) { Check(player); }, __instance); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] private static class Command { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__Print; } [HarmonyPostfix] private static void Postfix() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown object obj = <>O.<0>__Print; if (obj == null) { ConsoleEvent val = Print; <>O.<0>__Print = val; obj = (object)val; } new ConsoleCommand("fortune", "Hearthhold: today's fortune and what it does to star rolls.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } private const float Interval = 3f; private static float timer; private static int toldDay = -1; private static Player toldPlayer; private static void Check(Player player) { if (Fortune.On && !((Object)(object)EnvMan.instance == (Object)null) && WorldGenerator.instance != null) { int day = EnvMan.instance.GetDay(); if (day != toldDay || !((Object)(object)player == (Object)(object)toldPlayer)) { toldDay = day; toldPlayer = player; ((Character)player).Message((MessageType)1, Fortune.Line(Fortune.Today()), 0, (Sprite)null, false); } } } private static void Print(ConsoleEventArgs args) { if (!Fortune.On) { args.Context.AddString("Daily Fortune is off on this server."); } else { args.Context.AddString(Fortune.Line(Fortune.Today())); } } } public static class Friendship { public const int PointsPerHeart = 100; public const int MaxHearts = 10; public const int MaxPoints = 1000; public const int PercentPerHeart = 1; private static readonly string[] lines = BuildLines(); public static bool On { get { if (Settings.TraderFriendship != null) { return Settings.TraderFriendship.Value; } return false; } } public static int Points(Player player, TraderInfo trader) { return ReadInt(player, trader.PointsKey, 0); } public static int Hearts(Player player, TraderInfo trader) { return Mathf.Clamp(Points(player, trader) / 100, 0, 10); } public static int DiscountPercent(Player player, TraderInfo trader) { if (!On || !((Object)(object)player != (Object)null)) { return 0; } return Hearts(player, trader); } public static int Discounted(int price, int percent) { if (percent <= 0 || price <= 1) { return price; } return Mathf.Max(1, price * (100 - percent) / 100); } public static int LastGiftDay(Player player, TraderInfo trader) { return ReadInt(player, trader.GiftDayKey, -1); } public static int AddGift(Player player, TraderInfo trader, int points, int day) { int num = Mathf.Clamp(Points(player, trader) + points, 0, 1000); player.m_customData[trader.PointsKey] = num.ToString(); player.m_customData[trader.GiftDayKey] = day.ToString(); return num / 100; } public static string Line(int hearts) { return lines[Mathf.Clamp(hearts, 0, 10)]; } private static string[] BuildLines() { string[] array = new string[11]; for (int i = 0; i <= 10; i++) { string arg = ((i > 0) ? $", {i}% off" : ""); array[i] = $"Friendship {i}/{10}{arg}"; } return array; } private static int ReadInt(Player player, string key, int fallback) { if ((Object)(object)player == (Object)null || !player.m_customData.TryGetValue(key, out var value)) { return fallback; } if (!int.TryParse(value, out var result)) { return fallback; } return result; } } public static class FriendshipKeys { public const string PointsPrefix = "hearthhold_friendship_"; public const string GiftDayPrefix = "hearthhold_giftday_"; } public static class StoreDiscount { [HarmonyPatch(typeof(StoreGui), "FillList")] private static class Fill { [HarmonyPrefix] private static void Prefix(StoreGui __instance) { Enter(__instance); } [HarmonyFinalizer] private static void Finalizer() { Exit(); } } [HarmonyPatch(typeof(StoreGui), "BuySelectedItem")] private static class Buy { [HarmonyPrefix] private static void Prefix(StoreGui __instance) { Enter(__instance); } [HarmonyFinalizer] private static void Finalizer() { Exit(); } } [HarmonyPatch(typeof(StoreGui), "CanAfford")] private static class Afford { [HarmonyPrefix] private static bool Prefix(StoreGui __instance, TradeItem item, ref bool __result) { int num = ((depth <= 0 && item != null) ? Percent(__instance) : 0); if (num <= 0) { return true; } __result = Friendship.Discounted(item.m_price, num) <= __instance.GetPlayerCoins(); return false; } } [HarmonyPatch(typeof(StoreGui), "Show")] private static class Open { [HarmonyPostfix] private static void Postfix(Trader trader) { HookGuard.Run("store discount notice", delegate(Trader t) { Tell(t); }, trader); } } private static readonly List<TradeItem> lowered = new List<TradeItem>(); private static readonly List<int> prices = new List<int>(); private static int depth; private static int Percent(StoreGui gui) { if (!((Object)(object)gui.m_trader == (Object)null)) { return Friendship.DiscountPercent(Player.m_localPlayer, TraderInfo.Of(gui.m_trader)); } return 0; } private static void Enter(StoreGui gui) { if (depth++ <= 0) { HookGuard.Run("store discount", delegate(StoreGui g) { Lower(g); }, gui); } } private static void Lower(StoreGui gui) { int num = Percent(gui); if (num <= 0) { return; } foreach (TradeItem availableItem in gui.m_trader.GetAvailableItems()) { if (availableItem != null && !lowered.Contains(availableItem)) { lowered.Add(availableItem); prices.Add(availableItem.m_price); availableItem.m_price = Friendship.Discounted(availableItem.m_price, num); } } } private static void Exit() { if (depth <= 0 || --depth <= 0) { for (int i = 0; i < lowered.Count; i++) { lowered[i].m_price = prices[i]; } lowered.Clear(); prices.Clear(); } } private static void Tell(Trader trader) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)trader == (Object)null) && !((Object)(object)localPlayer == (Object)null)) { TraderInfo traderInfo = TraderInfo.Of(trader); if (Friendship.DiscountPercent(localPlayer, traderInfo) > 0) { ((Character)localPlayer).Message((MessageType)1, traderInfo.DisplayName + ": " + Friendship.Line(Friendship.Hearts(localPlayer, traderInfo)), 0, (Sprite)null, false); } } } } public static class TraderGift { [HarmonyPatch(typeof(Trader), "UseItem")] private static class Use { [HarmonyPrefix] private static bool Prefix(Trader __instance, Humanoid user, ItemData item, ref bool __result) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown if (!Friendship.On || (Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer || !IsDish(item) || TraderTakes(__instance, item)) { return true; } Player player = (Player)user; bool num = HookGuard.Run("trader gift", () => Give(__instance, player, item), fallback: false); if (num) { __result = true; } return !num; } } public const int BasePoints = 20; public const int PointsPerStar = 20; private static bool IsDish(ItemData item) { if (item?.m_shared != null && (Object)(object)item.m_dropPrefab != (Object)null) { return Kitchen.IsProduct(((Object)item.m_dropPrefab).name); } return false; } private static bool TraderTakes(Trader trader, ItemData item) { foreach (TraderUseItem useItem in trader.m_useItems) { if ((Object)(object)useItem?.m_prefab != (Object)null && useItem.m_prefab.m_itemData.m_shared.m_name == item.m_shared.m_name) { return true; } } return false; } private static bool Give(Trader trader, Player player, ItemData item) { if ((Object)(object)EnvMan.instance == (Object)null) { return false; } TraderInfo traderInfo = TraderInfo.Of(trader); int num = Stars.Get(item); int day = EnvMan.instance.GetDay(); if (num < 1) { ((Character)player).Message((MessageType)2, traderInfo.DisplayName + " only accepts starred food as a gift.", 0, (Sprite)null, false); } else if (Friendship.LastGiftDay(player, traderInfo) == day) { ((Character)player).Message((MessageType)2, "Come back tomorrow.", 0, (Sprite)null, false); } else { Accept(traderInfo, player, item, num, day); } return true; } private static void Accept(TraderInfo info, Player player, ItemData item, int stars, int day) { string text = Stars.TierName(stars) + " " + Localization.instance.Localize(item.m_shared.m_name); if (((Humanoid)player).GetInventory().RemoveItem(item, 1)) { int hearts = Friendship.AddGift(player, info, 20 + 20 * stars, day); string text2 = info.DisplayName + " likes the " + text + ". " + Friendship.Line(hearts); ((Character)player).Message((MessageType)2, text2, 0, (Sprite)null, false); } } } public static class TraderHover { [HarmonyPatch(typeof(Trader), "GetHoverText")] private static class Hover { [HarmonyPostfix] private static void Postfix(Trader __instance, ref string __result) { if (!Friendship.On || (Object)(object)Player.m_localPlayer == (Object)null || string.IsNullOrEmpty(__result)) { return; } try { __result = WithLine(__instance, __result); } catch (Exception exception) { Guard.Report(exception, "trader hover"); } } } private static string WithLine(Trader trader, string text) { TraderInfo traderInfo = TraderInfo.Of(trader); int num = Friendship.Hearts(Player.m_localPlayer, traderInfo); if (num != traderInfo.HoverHearts || text != traderInfo.HoverBase) { traderInfo.HoverHearts = num; traderInfo.HoverBase = text; traderInfo.HoverText = text + "\n" + Friendship.Line(num); } return traderInfo.HoverText; } } public sealed class TraderInfo { private const int PruneAt = 32; private static readonly Dictionary<Trader, TraderInfo> cache = new Dictionary<Trader, TraderInfo>(); private static readonly List<Trader> gone = new List<Trader>(); public readonly string Prefab; public readonly string PointsKey; public readonly string GiftDayKey; public readonly string DisplayName; internal string HoverBase; internal string HoverText; internal int HoverHearts = -1; private TraderInfo(Trader trader) { Prefab = Utils.GetPrefabName(((Component)trader).gameObject); PointsKey = "hearthhold_friendship_" + Prefab; GiftDayKey = "hearthhold_giftday_" + Prefab; DisplayName = ((Localization.instance != null) ? Localization.instance.Localize(trader.m_name) : trader.m_name); } public static TraderInfo Of(Trader trader) { if (cache.TryGetValue(trader, out var value)) { return value; } if (cache.Count >= 32) { Prune(); } value = new TraderInfo(trader); cache[trader] = value; return value; } private static void Prune() { gone.Clear(); foreach (Trader key in cache.Keys) { if ((Object)(object)key == (Object)null) { gone.Add(key); } } foreach (Trader item in gone) { cache.Remove(item); } gone.Clear(); } } [BepInPlugin("com.Hearthhold", "Hearthhold", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Hearthhold : BaseUnityPlugin { public const string PluginGuid = "com.Hearthhold"; public const string PluginName = "Hearthhold"; public const string PluginVersion = "0.1.0"; public static ManualLogSource Log { get; private set; } public static SyncedConfiguration Synced { get; private set; } private void Awake() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Synced = new SyncedConfiguration((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, "Hearthhold", "0.1.0"); Settings.Initialize(Synced); Harmony val = new Harmony("com.Hearthhold"); val.PatchAll(Assembly.GetExecutingAssembly()); Synced.Finish(val); Guard.Install(val, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly()); } } public static class KitchenCraft { [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] private static class Crafting { [HarmonyPrefix] private static void Prefix(InventoryGui __instance, Player player) { HookGuard.Run("kitchen craft", delegate((InventoryGui __instance, Player player) args) { Begin(args.__instance, args.player); }, (__instance, player)); } [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "com.GrindstoneSkills" })] private static void Postfix() { if (current != null) { HookGuard.Run("kitchen craft stars", Finish); } } [HarmonyFinalizer] private static void Finalizer() { HookGuard.Run("kitchen craft", End); } } private static KitchenCraftContext current; private static void Begin(InventoryGui gui, Player player) { End(); CraftingStation station = (((Object)(object)player == (Object)null) ? null : player.GetCurrentCraftingStation()); Recipe craftRecipe = gui.m_craftRecipe; if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && Kitchen.IsKitchen(station) && !((Object)(object)craftRecipe?.m_item == (Object)null) && gui.m_craftUpgradeItem == null) { current = new KitchenCraftContext(gui, player, station); KitchenIngredientOrder.BestFirst(((Humanoid)player).GetInventory()); KitchenIngredientRecord.Start(); } } private static void Finish() { KitchenCraftContext kitchenCraftContext = current; KitchenIngredientRecord.Stop(); if (kitchenCraftContext != null && kitchenCraftContext.Grades) { KitchenCraftSplit.Apply(kitchenCraftContext); } } private static void End() { KitchenIngredientRecord.Clear(); KitchenIngredientOrder.Restore(); current = null; } } public sealed class KitchenCraftContext { public readonly Player Player; public readonly ItemDrop Product; public readonly string Prefab; public readonly SharedData Shared; public readonly int Variant; public readonly bool Cheated; public readonly int PlainBefore; public readonly float CookLevel; public readonly float FishingLevel; public bool Grades => Stars.IsStarItem(Product.m_itemData); public KitchenCraftContext(InventoryGui gui, Player player, CraftingStation station) { Player = player; Product = gui.m_craftRecipe.m_item; Prefab = ((Object)((Component)Product).gameObject).name; Shared = Product.m_itemData.m_shared; Variant = gui.m_craftVariant; Cheated = WillBeCheated(player, station, gui.m_craftRecipe); PlainBefore = PlainCount(((Humanoid)player).GetInventory(), Shared.m_name); CookLevel = GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Dish)); FishingLevel = GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Fillet)); } public static int PlainCount(Inventory inventory, string name) { int num = 0; foreach (ItemData item in inventory.m_inventory) { if (item.m_shared.m_name == name && item.m_quality == Stars.ToQuality(0)) { num += item.m_stack; } } return num; } private static bool WillBeCheated(Player player, CraftingStation station, Recipe recipe) { bool num = ((Humanoid)player).GetInventory().ItemCheated(recipe.m_resources, -1, true) || player.NoCostCheat(); ZNetView val = (((Object)(object)station == (Object)null) ? null : station.m_nview); bool flag = (Object)(object)val != (Object)null && val.IsValid() && val.GetZDO().GetBool(ZDOVars.s_cheated, false); if (num || flag) { return !PlayerProfile.s_bypassCheatChecks; } return false; } } public static class KitchenCraftSplit { public static void Apply(KitchenCraftContext craft) { Inventory inventory = ((Humanoid)craft.Player).GetInventory(); int num = KitchenCraftContext.PlainCount(inventory, craft.Shared.m_name) - craft.PlainBefore; if (num <= 0) { return; } int[] array = Roll(craft, num); int num2 = num - array[0]; if (num2 > 0) { inventory.RemoveItem(craft.Shared.m_name, num2, Stars.ToQuality(0), true); for (int i = 1; i <= 3; i++) { Give(craft, i, array[i]); } } } private static int[] Roll(KitchenCraftContext craft, int units) { int fishLevel = KitchenIngredientRecord.FishLevel; float bonus = StarOdds.IngredientBonus(KitchenIngredientRecord.AverageStars) + ((fishLevel > 0) ? StarOdds.FishBonus(fishLevel) : 0f); int num = Mathf.Max(Professions.Floor(StarSource.Dish, craft.CookLevel), StarOdds.IngredientFloor(KitchenIngredientRecord.AverageStars)); if (fishLevel > 0) { num = Mathf.Max(num, Professions.Floor(StarSource.Fillet, craft.FishingLevel)); } int[] array = new int[4]; for (int i = 0; i < units; i++) { array[StarOdds.Roll(craft.CookLevel, bonus, num)]++; } return array; } private static void Give(KitchenCraftContext craft, int stars, int count) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (count > 0) { int num = Stars.ToQuality(stars); Player player = craft.Player; Inventory inventory = ((Humanoid)player).GetInventory(); int num2 = Mathf.Min(count, Room(inventory, craft.Shared, num)); if (num2 > 0) { inventory.AddItem(craft.Prefab, num2, num, craft.Variant, player.GetPlayerID(), player.GetPlayerName(), new Vector2i(-1, -1), craft.Cheated, false, true); } if (count > num2) { AtFeet(player, NewItem(craft, num), count - num2); } } } private static int Room(Inventory inventory, SharedData shared, int quality) { int num = (byte)Game.m_worldLevel; int num2 = inventory.GetEmptySlots() * Mathf.Max(1, shared.m_maxStackSize); foreach (ItemData item in inventory.m_inventory) { if (item.m_shared.m_name == shared.m_name && item.m_quality == quality && item.m_worldLevel == num) { num2 += Mathf.Max(0, item.m_shared.m_maxStackSize - item.m_stack); } } return num2; } private static ItemData NewItem(KitchenCraftContext craft, int quality) { ItemData obj = craft.Product.m_itemData.Clone(); obj.m_dropPrefab = ((Component)craft.Product).gameObject; obj.m_quality = quality; obj.m_variant = craft.Variant; obj.m_durability = obj.GetMaxDurability(); obj.m_crafterID = craft.Player.GetPlayerID(); obj.m_crafterName = craft.Player.GetPlayerName(); obj.m_worldLevel = (byte)Game.m_worldLevel; obj.m_cheated = craft.Cheated; return obj; } private static void AtFeet(Player player, ItemData item, int amount) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)player).transform; Vector3 val = transform.position + transform.forward + transform.up; int num = Mathf.Max(1, item.m_shared.m_maxStackSize); for (int num2 = amount; num2 > 0; num2 -= num) { ItemDrop.DropItem(item, Mathf.Min(num2, num), val, transform.rotation); } } } public static class KitchenIngredientOrder { private static readonly List<ItemData> original = new List<ItemData>(); private static Inventory sorted; public static void BestFirst(Inventory inventory) { Restore(); List<ItemData> list = inventory?.m_inventory; if (list != null && list.Any((ItemData item) => Stars.Get(item) > 0)) { original.AddRange(list); List<ItemData> collection = list.OrderByDescending(Stars.Get).ToList(); list.Clear(); list.AddRange(collection); sorted = inventory; } } public static void Restore() { Inventory val = sorted; sorted = null; if (val != null) { Reorder(val.m_inventory); } original.Clear(); } private static void Reorder(List<ItemData> items) { HashSet<ItemData> hashSet = new HashSet<ItemData>(items); HashSet<ItemData> before = new HashSet<ItemData>(original); List<ItemData> collection = original.Where(hashSet.Contains).Concat(items.Where((ItemData item) => !before.Contains(item))).ToList(); items.Clear(); items.AddRange(collection); } } public static class KitchenIngredientRecord { [HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[] { typeof(string), typeof(int), typeof(int), typeof(bool) })] private static class NameTaken { [HarmonyPrefix] [HarmonyPriority(200)] private static void Prefix(Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased, bool __runOriginal) { if (Recording && __runOriginal) { HookGuard.Run("kitchen ingredients", delegate((Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased) args) { NoteByName(args.__instance, args.name, args.amount, args.itemQuality, args.worldLevelBased); }, (__instance, name, amount, itemQuality, worldLevelBased)); } } } [HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[] { typeof(ItemData), typeof(int) })] private static class StackTaken { [HarmonyPrefix] [HarmonyPriority(200)] private static void Prefix(Inventory __instance, ItemData item, int amount, bool __runOriginal) { if (Recording && __runOriginal && item != null && __instance.ContainsItem(item)) { HookGuard.Run("kitchen ingredients", delegate((ItemData item, int amount) args) { Note(args.item, Mathf.Min(args.item.m_stack, args.amount)); }, (item, amount)); } } } private static int starUnits; private static int starSum; public static bool Recording { get; private set; } public static int FishLevel { get; private set; } public static float AverageStars { get { if (starUnits != 0) { return (float)starSum / (float)starUnits; } return 0f; } } public static void Start() { Clear(); Recording = true; } public static void Stop() { Recording = false; } public static void Clear() { Recording = false; starUnits = 0; starSum = 0; FishLevel = 0; } private static void Note(ItemData stack, int count) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (stack?.m_shared != null && count > 0) { if ((int)stack.m_shared.m_itemType == 21) { FishLevel = Mathf.Max(FishLevel, stack.m_quality); } if (Stars.IsStarItem(stack)) { starUnits += count; starSum += Stars.Get(stack) * count; } } } private static void NoteByName(Inventory inventory, string name, int amount, int quality, bool worldLevelBased) { foreach (ItemData item in inventory.m_inventory) { if (amount <= 0) { break; } if (!(item.m_shared.m_name != name) && (quality < 0 || item.m_quality == quality) && (!worldLevelBased || item.m_worldLevel >= Game.m_worldLevel)) { int num = Mathf.Min(item.m_stack, amount); Note(item, num); amount -= num; } } } } public static class KitchenBarrel { public struct Pending { public bool Empty; public int Stars; } [HarmonyPatch(typeof(Fermenter), "AddItem")] private static class Adding { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Fermenter __instance, Humanoid user, ItemData item) { HookGuard.Run("barrel mark", delegate((Fermenter __instance, Humanoid user, ItemData item) args) { SendMark(args.__instance, args.user, args.item); }, (__instance, user, item)); } } [HarmonyPatch(typeof(Fermenter), "RPC_AddItem")] private static class Added { [HarmonyPrefix] private static void Prefix(Fermenter __instance, long sender, out Pending __state) { __state = HookGuard.Run("barrel add", () => Before(__instance, sender), default(Pending)); } [HarmonyPostfix] private static void Postfix(Fermenter __instance, int nameHash, Pending __state) { if (__state.Empty) { HookGuard.Run("barrel add", delegate((Fermenter __instance, int nameHash, int Stars) args) { After(args.__instance, args.nameHash, args.Stars); }, (__instance, nameHash, __state.Stars)); } } } private static readonly int StarsKey = StringExtensionMethods.GetStableHashCode("hearthhold_base_stars"); public static int Get(ZDO zdo) { if (zdo != null) { return Mathf.Clamp(zdo.GetInt(StarsKey, 0), 0, 3); } return 0; } public static void Write(ZDO zdo, int stars) { stars = Mathf.Clamp(stars, 0, 3); if (zdo != null && zdo.GetInt(StarsKey, 0) != stars) { zdo.Set(StarsKey, stars, false); } } public static void Clear(ZDO zdo) { Write(zdo, 0); } private static void SendMark(Fermenter fermenter, Humanoid user, ItemData item) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = fermenter.m_nview; if (!((Object)(object)item?.m_dropPrefab == (Object)null) && !((Object)(object)user == (Object)null) && !((Object)(object)user != (Object)(object)Player.m_localPlayer) && !((Object)(object)nview == (Object)null) && nview.IsValid() && (int)fermenter.GetStatus() == 0 && fermenter.IsItemAllowed(item)) { Marks.Send(nview, GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Dish)), Stars.Get(item)); } } private static Pending Before(Fermenter fermenter, long sender) { ZNetView nview = fermenter.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || fermenter.GetContent() != 0) { return default(Pending); } Marks.Mark mark; int stars = (Marks.TryTake(nview, sender, out mark) ? Mathf.RoundToInt(mark.Stars) : 0); return new Pending { Empty = true, Stars = stars }; } private static void After(Fermenter fermenter, int nameHash, int stars) { if (nameHash != 0 && fermenter.m_nview.IsValid() && fermenter.GetContent() == nameHash) { Write(fermenter.m_nview.GetZDO(), stars); } } } public static class KitchenBarrelTap { [HarmonyPatch(typeof(Fermenter), "RPC_Tap")] private static class Tap { [HarmonyPrefix] private static void Prefix(Fermenter __instance, out int __state) { __state = HookGuard.Run("barrel tap", () => __instance.GetContent(), 0); } [HarmonyPostfix] private static void Postfix(Fermenter __instance, int __state) { if (__state != 0) { HookGuard.Run("barrel tap", delegate(Fermenter fermenter) { Tapped(fermenter); }, __instance); } } } [HarmonyPatch(typeof(Fermenter), "DelayedTap")] private static class Spawn { [HarmonyPrefix] private static void Prefix(Fermenter __instance, out bool __state) { __state = HookGuard.Run("barrel spawn", () => Open(TakeTapped(__instance)), fallback: false); } [HarmonyFinalizer] private static void Finalizer(bool __state) { Close(__state); } } [HarmonyPatch(typeof(Fermenter), "DropAllItems")] private static class Drop { [HarmonyPrefix] private static void Prefix(Fermenter __instance, out bool __state) { __state = HookGuard.Run("barrel drops", () => __instance.GetContent() != 0 && Open(KitchenBarrel.Get(__instance.m_nview.GetZDO())), fallback: false); } [HarmonyFinalizer] private static void Finalizer(Fermenter __instance, bool __state) { Close(__state); HookGuard.Run("barrel drops", delegate(Fermenter fermenter) { ClearWhenEmpty(fermenter); }, __instance); } } private static readonly Dictionary<Fermenter, int> tapped = new Dictionary<Fermenter, int>(); private static readonly List<Fermenter> dead = new List<Fermenter>(); private static readonly SpawnStars.Roller roller = Give; private static SpawnStars.Roller previous; private static int giving; private static void Tapped(Fermenter fermenter) { ZNetView nview = fermenter.m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && fermenter.GetContent() == 0) { ZDO zDO = nview.GetZDO(); int num = KitchenBarrel.Get(zDO); KitchenBarrel.Clear(zDO); Prune(); if (num > 0) { tapped[fermenter] = num; } else { tapped.Remove(fermenter); } } } private static int TakeTapped(Fermenter fermenter) { if (!tapped.TryGetValue(fermenter, out var value)) { return 0; } tapped.Remove(fermenter); return value; } private static void ClearWhenEmpty(Fermenter fermenter) { ZNetView nview = fermenter.m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid() && fermenter.GetContent() == 0) { KitchenBarrel.Clear(nview.GetZDO()); } } private static void Prune() { foreach (Fermenter key in tapped.Keys) { if ((Object)(object)key == (Object)null) { dead.Add(key); } } foreach (Fermenter item in dead) { tapped.Remove(item); } dead.Clear(); } private static bool Open(int stars) { if (stars <= 0) { return false; } giving = stars; previous = SpawnStars.Open(roller); return true; } private static void Close(bool opened) { if (opened) { SpawnStars.Close(previous); previous = null; giving = 0; } } private static int Give(ItemData item) { return giving; } } public static class KitchenKeys { public const string SlotLevel = "hearthhold_slot_level"; public const string SlotInput = "hearthhold_slot_input"; public const string SlotStars = "hearthhold_slot_stars"; public const string BaseStars = "hearthhold_base_stars"; } public static class KitchenSlots { [HarmonyPatch(typeof(CookingStation), "SetSlot")] private static class Emptied { [HarmonyPostfix] private static void Postfix(CookingStation __instance, int slot, string itemName) { if (string.IsNullOrEmpty(itemName) && Kitchen.IsKitchen(__instance)) { HookGuard.Run("kitchen slot reset", delegate((CookingStation __instance, int slot) args) { ResetSlot(args.__instance, args.slot); }, (__instance, slot)); } } } private const int Hashed = 16; private const string GameName = "slot"; private const string GameStatus = "slotstatus"; private static readonly int[] NameKeys = Hashes("slot"); private static readonly int[] StatusKeys = Hashes("slotstatus"); private static readonly int[] LevelKeys = Hashes("hearthhold_slot_level"); private static readonly int[] InputKeys = Hashes("hearthhold_slot_input"); private static readonly int[] StarKeys = Hashes("hearthhold_slot_stars"); public static string Name(ZDO zdo, int slot) { return zdo.GetString(Key(NameKeys, "slot", slot), ""); } public static Status Status(ZDO zdo, int slot) { return (Status)zdo.GetInt(Key(StatusKeys, "slotstatus", slot), 0); } public static float Level(ZDO zdo, int slot) { return StarOdds.Sane(zdo.GetFloat(Key(LevelKeys, "hearthhold_slot_level", slot), 0f)); } public static float InputStars(ZDO zdo, int slot) { return Mathf.Clamp(StarOdds.Sane(zdo.GetFloat(Key(InputKeys, "hearthhold_slot_input", slot), 0f)), 0f, 3f); } public static int DishStars(ZDO zdo, int slot) { return Mathf.Clamp(zdo.GetInt(Key(StarKeys, "hearthhold_slot_stars", slot), 0), 0, 3); } public static int RawStars(ZDO zdo, int slot) { return Mathf.Clamp(Mathf.RoundToInt(InputStars(zdo, slot)), 0, 3); } public static void Fill(ZDO zdo, int slot, float level, float inputStars) { Write(zdo, Key(LevelKeys, "hearthhold_slot_level", slot), StarOdds.Sane(level)); Write(zdo, Key(InputKeys, "hearthhold_slot_input", slot), Mathf.Clamp(StarOdds.Sane(inputStars), 0f, 3f)); Write(zdo, Key(StarKeys, "hearthhold_slot_stars", slot), 0); } public static void SetStars(ZDO zdo, int slot, int stars) { Write(zdo, Key(StarKeys, "hearthhold_slot_stars", slot), Mathf.Clamp(stars, 0, 3)); } public static void Reset(ZDO zdo, int slot) { Fill(zdo, slot, 0f, 0f); } private static void Write(ZDO zdo, int key, float value) { if (zdo.GetFloat(key, 0f) != value) { zdo.Set(key, value); } } private static void Write(ZDO zdo, int key, int value) { if (zdo.GetInt(key, 0) != value) { zdo.Set(key, value, false); } } private static int[] Hashes(string key) { int[] array = new int[16]; for (int i = 0; i < 16; i++) { array[i] = StringExtensionMethods.GetStableHashCode(key + i); } return array; } private static int Key(int[] hashes, string key, int slot) { if (slot < 0 || slot >= hashes.Length) { return StringExtensionMethods.GetStableHashCode(key + slot); } return hashes[slot]; } private static void ResetSlot(CookingStation station, int slot) { ZNetView nview = station.m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid()) { Reset(nview.GetZDO(), slot); } } } public static class KitchenStationAdd { public struct Pending { public int Slot; public float Level; public float Stars; } [HarmonyPatch(typeof(CookingStation), "CookItem")] private static class Cook { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CookingStation __instance, Humanoid user, ItemData item) { if (Kitchen.IsKitchen(__instance)) { HookGuard.Run("station mark", delegate((CookingStation __instance, Humanoid user, ItemData item) args) { SendMark(args.__instance, args.user, args.item); }, (__instance, user, item)); } } } [HarmonyPatch(typeof(CookingStation), "RPC_AddItem")] private static class Add { [HarmonyPrefix] private static void Prefix(CookingStation __instance, long sender, out Pending __state) { Pending pending = new Pending { Slot = -1 }; __state = (Kitchen.IsKitchen(__instance) ? HookGuard.Run("station add", () => Before(__instance, sender), pending) : pending); } [HarmonyPostfix] private static void Postfix(CookingStation __instance, string itemName, Pending __state) { if (__state.Slot >= 0) { HookGuard.Run("station add", delegate((CookingStation __instance, string itemName, Pending __state) args) { After(args.__instance, args.itemName, args.__state); }, (__instance, itemName, __state)); } } } private static void SendMark(CookingStation station, Humanoid user, ItemData item) { ZNetView nview = station.m_nview; if (!((Object)(object)item?.m_dropPrefab == (Object)null) && !((Object)(object)user == (Object)null) && !((Object)(object)user != (Object)(object)Player.m_localPlayer) && !((Object)(object)nview == (Object)null) && nview.IsValid()) { if (!nview.HasOwner()) { nview.ClaimOwnership(); } if (!Incompatible(station, item) && station.IsItemAllowed(item) && station.GetFreeSlot() != -1) { Marks.Send(nview, GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Dish)), Stars.Get(item)); } } } private static bool Incompatible(CookingStation station, ItemData item) { foreach (ItemMessage incompatibleItem in station.m_incompatibleItems) { if ((Object)(object)incompatibleItem.m_item != (Object)null && incompatibleItem.m_item.m_itemData.m_shared.m_name == item.m_shared.m_name) { return true; } } return false; } private static Pending Before(CookingStation station, long sender) { ZNetView nview = station.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return new Pending { Slot = -1 }; } if (!Marks.TryTake(nview, sender, out var mark)) { mark = default(Marks.Mark); } return new Pending { Slot = station.GetFreeSlot(), Level = mark.Level, Stars = mark.Stars }; } private static void After(CookingStation station, string itemName, Pending pending) { ZNetView nview = station.m_nview; if (nview.IsValid() && !string.IsNullOrEmpty(itemName)) { ZDO zDO = nview.GetZDO(); if (KitchenSlots.Name(zDO, pending.Slot) == itemName) { KitchenSlots.Fill(zDO, pending.Slot, pending.Level, pending.Stars); } } } } [HarmonyPatch(typeof(CookingStation), "UpdateCooking")] public static class KitchenStationRoll { private const int MaskSlots = 32; private static int cooking; [HarmonyPrefix] private static void Prefix(CookingStation __instance, out int __state) { cooking = 0; if (Kitchen.IsKitchen(__instance)) { HookGuard.Run("station cooking", delegate(CookingStation station) { cooking = Cooking(station); }, __instance); } __state = cooking; } [HarmonyPostfix] private static void Postfix(CookingStation __instance, int __state) { if (__state != 0) { HookGuard.Run("station roll", delegate((CookingStation __instance, int __state) args) { RollDone(args.__instance, args.__state); }, (__instance, __state)); } } private static int Cooking(CookingStation station) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = station.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { return 0; } ZDO zDO = nview.GetZDO(); int num = 0; int num2 = Math.Min(station.m_slots.Length, 32); for (int i = 0; i < num2; i++) { if ((int)KitchenSlots.Status(zDO, i) == 0 && KitchenSlots.Name(zDO, i).Length > 0) { num |= 1 << i; } } return num; } private static void RollDone(CookingStation station, int cookingMask) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 ZNetView nview = station.m_nview; if (!nview.IsValid() || !nview.IsOwner()) { return; } ZDO zDO = nview.GetZDO(); int num = Math.Min(station.m_slots.Length, 32); for (int i = 0; i < num; i++) { if ((cookingMask & (1 << i)) != 0 && (int)KitchenSlots.Status(zDO, i) == 1 && Kitchen.IsProduct(KitchenSlots.Name(zDO, i))) { KitchenSlots.SetStars(zDO, i, Roll(zDO, i)); } } } private static int Roll(ZDO zdo, int slot) { float level = KitchenSlots.Level(zdo, slot); float averageStars =