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 WellStocked v1.0.2
plugins/WellStocked.dll
Decompiled 12 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] namespace WellStocked; internal static class Details { private static Dictionary<string, List<string>> s_uses; private static int s_usesVersion = -1; internal static void Forget() { s_uses = null; } internal static string Title(TradeItem t) { if (t != null) { return Shop.NameOf(t); } return ""; } internal static Sprite Icon(TradeItem t) { if (t == null) { return null; } if ((Object)(object)t.m_prefab != (Object)null) { return t.m_prefab.m_itemData.GetIcon(); } return t.m_icon; } internal static string Text(TradeItem t, Trader trader) { if (t == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(); Row row = Shop.RowOf(t); Player localPlayer = Player.m_localPlayer; try { if (row != null && row.Mode == ShopMode.Services) { stringBuilder.AppendLine(Localize(t.m_tooltip)); string text = (((Object)(object)localPlayer != (Object)null) ? row.Service.Unavailable(localPlayer) : null); if (text != null) { stringBuilder.AppendLine().Append(Grey(text)).AppendLine(); } string text2 = null; try { text2 = row.Service.PriceNote?.Invoke(); } catch { } stringBuilder.AppendLine().Append(Grey(text2 ?? "Prices go up a little with every boss you defeat.")); return stringBuilder.ToString(); } if ((Object)(object)t.m_prefab == (Object)null) { stringBuilder.Append(Localize(t.m_tooltip)); return stringBuilder.ToString(); } SharedData shared = t.m_prefab.m_itemData.m_shared; string prefab = ((Object)((Component)t.m_prefab).gameObject).name; StockItem stockItem = row?.Stock ?? Stock.Items?.FirstOrDefault((StockItem s) => s.Prefab == prefab); string text3 = Localize(shared.m_description); if (!string.IsNullOrEmpty(text3)) { stringBuilder.AppendLine(Grey(text3)).AppendLine(); } switch (row?.Mode ?? ShopMode.Buy) { case ShopMode.Buy: BuyLines(stringBuilder, t, row, stockItem, trader); break; case ShopMode.Sell: SellLines(stringBuilder, t, row, trader); break; case ShopMode.Orders: OrderLines(stringBuilder, t, row, trader, localPlayer); break; } if (stockItem != null && !string.IsNullOrEmpty(stockItem.Source)) { stringBuilder.AppendLine(stockItem.Source); } List<string> list = UsesOf(prefab); if (list.Count > 0) { stringBuilder.Append(Label("Used for")); stringBuilder.Append(string.Join(", ", list.Take(6))); if (list.Count > 6) { stringBuilder.Append(" and " + (list.Count - 6) + " more"); } stringBuilder.AppendLine(); } if ((Object)(object)localPlayer != (Object)null) { int num = Shop.Carried(localPlayer, prefab); stringBuilder.AppendLine(Label("You carry") + num); } } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Item details failed: " + ex.Message)); } return stringBuilder.ToString().TrimEnd(); } private static void BuyLines(StringBuilder sb, TradeItem t, Row row, StockItem stock, Trader trader) { int num = Math.Max(1, t.m_stack); float each = (float)t.m_price / (float)num; sb.AppendLine(Label("Price") + t.m_price + ((num > 1) ? (" for " + num + " (" + Each(each) + " each)") : "")); if (row != null && row.Percent > 0) { int num2 = Shop.Left(row); sb.AppendLine(Gold("Today's deal: " + row.Percent + "% off (normally " + row.FullPrice + "). ") + ((num2 > 0) ? (num2 + " left today.") : "Sold out today.")); } else if (row != null && row.Limit >= 0) { int num3 = Shop.Left(row); int num4 = Math.Max(1, WellStockedPlugin.RestockDays.Value); sb.AppendLine(Label("In stock") + num3 + " of " + row.Limit + ", restocks " + Shop.StockBack + ((num4 > 1) ? (" (every " + num4 + " days)") : "")); } Player localPlayer = Player.m_localPlayer; UnlockMode value = WellStockedPlugin.Unlock.Value; if (row != null && row.Percent > 0 && stock != null && (Object)(object)localPlayer != (Object)null && (value == UnlockMode.HeldOrKnown || value == UnlockMode.Both) && !Shop.Knows(localPlayer, stock)) { sb.AppendLine(Grey("A peek: you haven't found this yet. After today it's on sale once you have.")); } sb.AppendLine(); } private static void SellLines(StringBuilder sb, TradeItem t, Row row, Trader trader) { if (row == null) { return; } if (row.DemandKey != null && row.Step != int.MaxValue && Market.DemandFactor(Shop.SoldSoFar(row), row.Step) <= 0f) { sb.AppendLine(Gold(Shop.NoMoreText(trader, Shop.NameOf(t)) + ".")); } else { sb.AppendLine(Label("Price now") + t.m_price + ((row.Amount > 1) ? (" for " + row.Amount) : "")); if (row.Single == null && row.Amount > 1) { sb.AppendLine(Label("Sell 1") + Shop.OnePrice(row)); } } sb.AppendLine(); } private static void OrderLines(StringBuilder sb, TradeItem t, Row row, Trader trader, Player player) { if (row == null) { return; } string text = (((Object)(object)trader != (Object)null) ? Localize(trader.m_name) : "The trader"); string text2 = Shop.NameOf(t); if (Shop.Filled(row)) { sb.AppendLine(Gold("Filled. " + text + " has new orders tomorrow.")); } else { int num = Shop.Brought(row); int num2 = Market.OrderLeft(row.Amount, num); sb.AppendLine(text + " wants " + row.Amount + " " + text2 + " today and pays " + t.m_price + " coins to whoever brings the last of them."); if (num > 0) { sb.AppendLine(Label("Brought so far") + num + " of " + row.Amount + ", " + num2 + " to go"); } int num3 = Market.SellPrice(row.Unit, row.Amount, Shop.SellShare, 1f, 1); if (num3 > 0 && t.m_price > num3) { sb.AppendLine(Grey("Selling them all would pay " + num3 + ".")); } int num4 = (((Object)(object)player != (Object)null) ? Shop.Carried(player, row.Prefab) : 0); if (num4 >= num2) { sb.AppendLine(Gold("You carry " + num4 + ": delivering fills the order and the " + t.m_price + " coins are yours.")); } else if (num4 > 0) { sb.AppendLine(Grey("You carry " + num4 + ": delivering adds them, " + (num2 - num4) + " more fill it.")); } else { sb.AppendLine(Grey("You carry none.")); } } sb.AppendLine(Grey("Orders are the same for everyone in the world. Anyone can add to an order; the player who brings the last ones gets the whole reward.")); sb.AppendLine(); } private static List<string> UsesOf(string prefab) { if (s_uses == null || s_usesVersion != Stock.Version) { BuildUses(); } if (!s_uses.TryGetValue(prefab, out var value)) { return new List<string>(); } return value; } private static void BuildUses() { s_usesVersion = Stock.Version; Dictionary<string, HashSet<string>> uses = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal); Action<Requirement[], string> action = delegate(Requirement[] reqs, string output) { if (reqs != null && !string.IsNullOrEmpty(output)) { foreach (Requirement val3 in reqs) { if (val3 != null && !((Object)(object)val3.m_resItem == (Object)null)) { string name = ((Object)((Component)val3.m_resItem).gameObject).name; if (!uses.TryGetValue(name, out var value)) { value = (uses[name] = new HashSet<string>(StringComparer.Ordinal)); } value.Add(output); } } } }; try { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance != (Object)null) { foreach (Recipe recipe in instance.m_recipes) { if ((Object)(object)recipe != (Object)null && recipe.m_enabled && (Object)(object)recipe.m_item != (Object)null) { action(recipe.m_resources, Localize(recipe.m_item.m_itemData.m_shared.m_name)); } } foreach (GameObject item in instance.m_items) { PieceTable val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val == (Object)null || val.m_pieces == null) { continue; } foreach (GameObject piece in val.m_pieces) { Piece val2 = (((Object)(object)piece != (Object)null) ? piece.GetComponent<Piece>() : null); if ((Object)(object)val2 != (Object)null && val2.m_enabled) { action(val2.m_resources, Localize(val2.m_name)); } } } } } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Finding what items are used for failed: " + ex.Message)); } s_uses = uses.ToDictionary<KeyValuePair<string, HashSet<string>>, string, List<string>>((KeyValuePair<string, HashSet<string>> p) => p.Key, (KeyValuePair<string, HashSet<string>> p) => p.Value.Where((string n) => !string.IsNullOrEmpty(n)).OrderBy<string, string>((string n) => n, StringComparer.CurrentCultureIgnoreCase).ToList(), StringComparer.Ordinal); } private static string Localize(string text) { if (string.IsNullOrEmpty(text)) { return ""; } if (Localization.instance == null) { return text; } return Localization.instance.Localize(text); } private static string Each(float each) { if (!(each >= 10f)) { return each.ToString("0.#"); } return each.ToString("0"); } private static string Label(string text) { return "<color=#c9b37e>" + text + ":</color> "; } private static string Grey(string text) { return "<color=#a8a8a8>" + text + "</color>"; } private static string Gold(string text) { return "<color=#ffcf4a>" + text + "</color>"; } } internal static class GatherTimes { internal struct Time { public readonly int Tier; public readonly float Minutes; public readonly string How; public Time(int tier, float minutes, string how) { Tier = tier; Minutes = minutes; How = how; } } internal static readonly Dictionary<string, Time> Table = new Dictionary<string, Time> { { "Acorn", new Time(1, 1.08f, "Oak1 (chop) · Meadows") }, { "BeechSeeds", new Time(1, 0.2f, "Beech1 (chop) · Meadows") }, { "BirchSeeds", new Time(1, 0.25f, "Birch1 (chop) · Meadows") }, { "BoneFragments", new Time(1, 1.53f, "Forest Crypt Remains01 (pick) · Meadows") }, { "Dandelion", new Time(1, 0.3f, "Dandelion (pick) · Meadows") }, { "DeerHide", new Time(1, 0.58f, "Deer (kill) · Meadows") }, { "DeerMeat", new Time(1, 0.58f, "Deer (kill) · Meadows") }, { "Feathers", new Time(1, 0.21f, "Seagal (mine or break) · Meadows · farm 0.13 min") }, { "FineWood", new Time(1, 0.1f, "Birch1 (chop) · Meadows") }, { "FirCone", new Time(1, 0.5f, "Fir Tree (chop) · Meadows") }, { "Fish1", new Time(1, 1f, "fishing (estimated)") }, { "Fish2", new Time(1, 1f, "fishing (estimated)") }, { "Flint", new Time(1, 0.34f, "Flint (pick) · Meadows") }, { "Honey", new Time(1, 0.42f, "Beehive (mine or break) · Meadows · farm 0.07 min") }, { "LeatherScraps", new Time(1, 0.6f, "Boar (kill) · Meadows · farm 0.31 min") }, { "Mushroom", new Time(1, 0.45f, "Mushroom (pick) · Meadows") }, { "QueenBee", new Time(1, 4.55f, "Beehive (mine or break) · Meadows") }, { "Raspberry", new Time(1, 0.13f, "Raspberry Bush (pick) · Meadows") }, { "RawMeat", new Time(1, 0.6f, "Boar (kill) · Meadows · farm 0.31 min") }, { "Resin", new Time(1, 0.42f, "Beech small2 (mine or break) · Meadows") }, { "Stone", new Time(1, 0.05f, "Rock 3 (mine or break) · Meadows") }, { "StoneRock", new Time(1, 9.87f, "Stone Rock (pick) · Meadows") }, { "TrophyBoar", new Time(1, 4f, "Boar (kill) · Meadows · farm 2.08 min") }, { "TrophyDeer", new Time(1, 2.31f, "Deer (kill) · Meadows") }, { "TrophyNeck", new Time(1, 3.3f, "Neck (kill) · Meadows") }, { "Wood", new Time(1, 0.06f, "Beech1 (chop) · Meadows") }, { "apple", new Time(1, 0.83f, "apple tree wild (pick) · Meadows") }, { "rae_HorseHide", new Time(1, 9.87f, "rae Odin Horse (kill) · Meadows") }, { "rae_HorseMeat", new Time(1, 8.83f, "rae Odin Horse (kill) · Meadows") }, { "rae_OdinHorse_Trophy", new Time(1, 94.11f, "rae Odin Horse (kill) · Meadows") }, { "rk_egg", new Time(1, 1.02f, "Seagal (mine or break) · Meadows") }, { "rk_pork", new Time(1, 0.6f, "Boar (kill) · Meadows · farm 0.31 min") }, { "BjornHide", new Time(2, 2.92f, "Bjorn (kill) · Black Forest") }, { "BjornMeat", new Time(2, 5.44f, "Bjorn (kill) · Black Forest") }, { "BjornPaw", new Time(2, 13.05f, "Bjorn (kill) · Black Forest") }, { "Blueberries", new Time(2, 0.15f, "Blueberry Bush (pick) · Black Forest") }, { "Carrot", new Time(2, 0.25f, "farm 0.25 min") }, { "CarrotSeeds", new Time(2, 0.25f, "Seed Carrot (pick) · Black Forest · farm 0.10 min") }, { "Coal", new Time(2, 0.08f, "charcoal kiln, from wood") }, { "CopperOre", new Time(2, 0.28f, "rock4 copper (mine or break) · Black Forest") }, { "Fish5", new Time(2, 1.2f, "fishing (estimated)") }, { "GreydwarfEye", new Time(2, 1.28f, "Greydwarf (kill) · Black Forest") }, { "MushroomYellow", new Time(2, 0.48f, "Mushroom yellow (pick) · Black Forest") }, { "PineCone", new Time(2, 0.26f, "Pinetree 01 (chop) · Black Forest") }, { "Pukeberries", new Time(2, 2.17f, "Greydwarf Shaman (kill) · Black Forest") }, { "RoundLog", new Time(2, 0.08f, "Pinetree 01 (chop) · Black Forest") }, { "SurtlingCore", new Time(2, 0.77f, "Surtling Core Stand (pick) · Black Forest") }, { "Thistle", new Time(2, 0.59f, "Thistle (pick) · Black Forest") }, { "TinOre", new Time(2, 0.36f, "Mine Rock Tin (mine or break) · Black Forest") }, { "TrollBone_TW", new Time(2, 15.88f, "Troll (kill) · Black Forest") }, { "TrollHide", new Time(2, 4.19f, "Troll (kill) · Black Forest") }, { "TrophyBjorn", new Time(2, 137.05f, "Bjorn (kill) · Black Forest") }, { "TrophyForestTroll", new Time(2, 30f, "no source found in the game data (estimated)") }, { "TrophyFrostTroll", new Time(2, 39.13f, "Troll (kill) · Black Forest") }, { "TrophyGhost", new Time(2, 30f, "no source found in the game data (estimated)") }, { "TrophyGreydwarf", new Time(2, 11.43f, "Greydwarf (kill) · Black Forest") }, { "TrophyGreydwarfBrute", new Time(2, 59.69f, "Greydwarf Elite (kill) · Black Forest") }, { "TrophyGreydwarfShaman", new Time(2, 31.05f, "Greydwarf Shaman (kill) · Black Forest") }, { "TrophySkeletonPoison", new Time(2, 30f, "no source found in the game data (estimated)") }, { "garlic", new Time(2, 4.53f, "Cucumber wild wild (pick) · Black Forest") }, { "pepper", new Time(2, 0.18f, "Pepper wild (pick) · Black Forest") }, { "Bloodbag", new Time(3, 0.75f, "Leech (kill) · Swamp") }, { "Chain", new Time(3, 1.08f, "chest sunkencrypt (loot) · Swamp") }, { "Chitin", new Time(3, 0.35f, "Leviathan (mine or break) · Ocean") }, { "ElderBark", new Time(3, 0.13f, "chest sunkencrypt (loot) · Swamp") }, { "Entrails", new Time(3, 0.93f, "Draugr (kill) · Swamp") }, { "Fish3", new Time(3, 1.5f, "fishing (estimated)") }, { "Fish8", new Time(3, 1.5f, "fishing (estimated)") }, { "Guck", new Time(3, 0.08f, "Guck Sack (mine or break) · Swamp") }, { "IronOre", new Time(3, 2f, "no source found in the game data (estimated)") }, { "IronScrap", new Time(3, 0.66f, "chest sunkencrypt (loot) · Swamp") }, { "Ooze", new Time(3, 1.8f, "Blob (kill) · Swamp") }, { "Root", new Time(3, 1.44f, "Abomination (kill) · Swamp") }, { "RottenPelt_TW", new Time(3, 8f, "Draugr (kill) · Swamp") }, { "SerpentMeat", new Time(3, 11.21f, "Serpent (kill) · Ocean") }, { "SerpentScale", new Time(3, 8.58f, "Serpent (kill) · Ocean") }, { "Thunderstone", new Time(3, 2f, "no source found in the game data (estimated)") }, { "TrophyAbomination", new Time(3, 15.13f, "Abomination (kill) · Swamp") }, { "TrophyBlob", new Time(3, 27f, "Blob (kill) · Swamp") }, { "TrophyDraugr", new Time(3, 9.8f, "Draugr (kill) · Swamp") }, { "TrophyDraugrElite", new Time(3, 49.17f, "Draugr Elite (kill) · Swamp") }, { "TrophyDraugrFem", new Time(3, 30f, "no source found in the game data (estimated)") }, { "TrophyKvastur", new Time(3, 30f, "no source found in the game data (estimated)") }, { "TrophyLeech", new Time(3, 7.28f, "Leech (kill) · Swamp") }, { "TrophySerpent", new Time(3, 234.28f, "Serpent (kill) · Ocean") }, { "TrophySkeleton", new Time(3, 12.56f, "Skeleton Swamps (kill) · Swamp") }, { "TrophySurtling", new Time(3, 294.21f, "Surtling (kill) · Swamp") }, { "TrophyWraith", new Time(3, 30.08f, "Wraith (kill) · Swamp") }, { "TrophyWrithan", new Time(3, 109.84f, "Writhan (kill) · Swamp") }, { "Turnip", new Time(3, 0.25f, "farm 0.25 min") }, { "TurnipSeeds", new Time(3, 0.26f, "Seed Turnip (pick) · Swamp · farm 0.10 min") }, { "WrithanRoots", new Time(3, 7.44f, "Writhan (kill) · Swamp") }, { "potato", new Time(3, 0.49f, "Broccoli wild (pick) · Swamp") }, { "tomato", new Time(3, 0.37f, "Tomato wild (pick) · Swamp") }, { "Crystal", new Time(4, 0.34f, "Stone Golem (kill) · Mountain") }, { "Fish4_cave", new Time(4, 2f, "fishing (estimated)") }, { "FreezeGland", new Time(4, 1.33f, "Hatchling (kill) · Mountain") }, { "JuteRed", new Time(4, 2f, "no source found in the game data (estimated)") }, { "MushroomBlue", new Time(4, 2f, "no source found in the game data (estimated)") }, { "Obsidian", new Time(4, 0.08f, "Mine Rock Obsidian (mine or break) · Mountain") }, { "Onion", new Time(4, 0.25f, "farm 0.25 min") }, { "OnionSeeds", new Time(4, 0.28f, "chest mountains (loot) · Mountain · farm 0.10 min") }, { "SilverOre", new Time(4, 0.32f, "silvervein (mine or break) · Mountain") }, { "TrophyCultist", new Time(4, 30f, "no source found in the game data (estimated)") }, { "TrophyFenring", new Time(4, 90.99f, "Fenring (kill) · Mountain") }, { "TrophyHatchling", new Time(4, 20.39f, "Hatchling (kill) · Mountain") }, { "TrophySGolem", new Time(4, 76.57f, "Stone Golem (kill) · Mountain") }, { "TrophyUlv", new Time(4, 519.29f, "Ulv (kill) · Mountain") }, { "TrophyWolf", new Time(4, 6.14f, "Wolf (kill) · Mountain · farm 3.42 min") }, { "WolfClaw", new Time(4, 3.51f, "Mountain Cave Random (pick) · Mountain") }, { "WolfFang", new Time(4, 1.49f, "Wolf (kill) · Mountain · farm 0.86 min") }, { "WolfMeat", new Time(4, 0.6f, "Wolf (kill) · Mountain · farm 0.34 min") }, { "WolfPelt", new Time(4, 0.4f, "Wolf (kill) · Mountain · farm 0.23 min") }, { "rk_dragonegg", new Time(4, 1.94f, "Hatchling (kill) · Mountain") }, { "Barley", new Time(5, 0.38f, "Barley Wild (pick) · Plains · farm 0.17 min") }, { "BlackMetalScrap", new Time(5, 1.05f, "Goblin (kill) · Plains") }, { "ChickenEgg", new Time(5, 0.25f, "farm 0.25 min") }, { "ChickenMeat", new Time(5, 0.26f, "farm 0.26 min") }, { "Cloudberry", new Time(5, 0.11f, "Cloudberry Bush (pick) · Plains") }, { "Fish6", new Time(5, 1.8f, "fishing (estimated)") }, { "Fish7", new Time(5, 1.8f, "fishing (estimated)") }, { "Flax", new Time(5, 0.39f, "Flax Wild (pick) · Plains · farm 0.17 min") }, { "LoxBone_TW", new Time(5, 2.66f, "Lox (kill) · Plains · farm 0.80 min") }, { "LoxMeat", new Time(5, 0.6f, "Lox (kill) · Plains · farm 0.18 min") }, { "LoxPelt", new Time(5, 1.19f, "Lox (kill) · Plains · farm 0.36 min") }, { "Needle", new Time(5, 1.89f, "chest heath (loot) · Plains") }, { "RottenMeat", new Time(5, 46.49f, "Unbjorn (kill) · Plains") }, { "Tar", new Time(5, 0.31f, "Tar Big (pick) · Plains") }, { "TrophyBjornUndead", new Time(5, 650.17f, "Unbjorn (kill) · Plains") }, { "TrophyDeathsquito", new Time(5, 92.91f, "Deathsquito (kill) · Plains") }, { "TrophyGoblin", new Time(5, 10.65f, "Goblin (kill) · Plains") }, { "TrophyGoblinBrute", new Time(5, 145.53f, "Goblin Brute (kill) · Plains") }, { "TrophyGoblinShaman", new Time(5, 205.14f, "Goblin Shaman (kill) · Plains") }, { "TrophyGrowth", new Time(5, 133.68f, "Blob Tar (kill) · Plains") }, { "TrophyLox", new Time(5, 17.41f, "Lox (kill) · Plains · farm 4.99 min") }, { "UndeadBjornRibcage", new Time(5, 29.23f, "Unbjorn (kill) · Plains") }, { "corn", new Time(5, 0.56f, "Corn wild (pick) · Plains") }, { "pumpkin", new Time(5, 1.77f, "Pumpkin wild (pick) · Plains") }, { "rice", new Time(5, 2.18f, "Rice wild wild (pick) · Plains") }, { "Bilebag", new Time(6, 28.89f, "Gjall (kill) · Mistlands") }, { "BlackCore", new Time(6, 3.45f, "Black Core Stand (pick) · Mistlands") }, { "BlackMarble", new Time(6, 5.23f, "Dverger (kill) · Mistlands") }, { "BugMeat", new Time(6, 1.68f, "Seeker (kill) · Mistlands") }, { "Carapace", new Time(6, 1.68f, "Seeker (kill) · Mistlands") }, { "CopperScrap", new Time(6, 0.85f, "giant helmet1 (mine or break) · Mistlands") }, { "DarkCrystal_TW", new Time(6, 15.87f, "Seeker (kill) · Mistlands") }, { "Fish9", new Time(6, 2.5f, "fishing (estimated)") }, { "GiantBloodSack", new Time(6, 12.55f, "Tick (kill) · Mistlands") }, { "HareMeat", new Time(6, 1.12f, "Hare (kill) · Mistlands") }, { "Hook", new Time(6, 16.51f, "chest dvergrtown (loot) · Mistlands") }, { "JuteBlue", new Time(6, 2f, "no source found in the game data (estimated)") }, { "Mandible", new Time(6, 14.81f, "Seeker Brute (kill) · Mistlands") }, { "MushroomJotunPuffs", new Time(6, 0.21f, "Mushroom Jotun Puffs (pick) · Mistlands · farm 0.10 min") }, { "MushroomMagecap", new Time(6, 0.14f, "Mushroom Magecap (pick) · Mistlands · farm 0.10 min") }, { "RoyalJelly", new Time(6, 0.65f, "Royal Jelly (pick) · Mistlands") }, { "Sap", new Time(6, 2f, "no source found in the game data (estimated)") }, { "ScaleHide", new Time(6, 0.56f, "Hare (kill) · Mistlands") }, { "Softtissue", new Time(6, 10.98f, "Dverger (kill) · Mistlands") }, { "TrophyDvergr", new Time(6, 71.48f, "Dverger (kill) · Mistlands") }, { "TrophyGjall", new Time(6, 94.35f, "Gjall (kill) · Mistlands") }, { "TrophyHare", new Time(6, 25.57f, "Hare (kill) · Mistlands") }, { "TrophySeeker", new Time(6, 11.74f, "Seeker (kill) · Mistlands") }, { "TrophySeekerBrute", new Time(6, 131.99f, "Seeker Brute (kill) · Mistlands") }, { "TrophyTick", new Time(6, 259.72f, "Tick (kill) · Mistlands") }, { "VenomousFang_TW", new Time(6, 16.47f, "Seeker (kill) · Mistlands") }, { "Wisp", new Time(6, 2f, "no source found in the game data (estimated)") }, { "YggdrasilWood", new Time(6, 0.13f, "Ygga Shoot1 (chop) · Mistlands") }, { "AskBladder", new Time(7, 2.2f, "Asksvin (kill) · Ashlands · farm 0.56 min") }, { "AskHide", new Time(7, 0.89f, "Asksvin (kill) · Ashlands · farm 0.22 min") }, { "AsksvinMeat", new Time(7, 0.9f, "Asksvin (kill) · Ashlands · farm 0.22 min") }, { "Blackwood", new Time(7, 0.15f, "Ashlands Tree1 (chop) · Ashlands") }, { "BoneMawSerpentMeat", new Time(7, 3.19f, "Bonemaw Serpent (kill) · Ashlands") }, { "BonemawSerpentTooth", new Time(7, 2.54f, "Bonemaw Serpent (kill) · Ashlands") }, { "CelestialFeather", new Time(7, 30.26f, "Fallen Valkyrie (kill) · Ashlands") }, { "CharcoalResin", new Time(7, 4.78f, "Ashlands Tree1 (chop) · Ashlands") }, { "CharredBone", new Time(7, 2.15f, "Charred Twitcher (kill) · Ashlands") }, { "Charredskull", new Time(7, 0.75f, "Charredskull (pick) · Ashlands") }, { "Fiddleheadfern", new Time(7, 0.44f, "Fiddlehead (pick) · Ashlands") }, { "Fish12", new Time(7, 3f, "fishing (estimated)") }, { "FlametalOre", new Time(7, 2f, "no source found in the game data (estimated)") }, { "FlametalOreNew", new Time(7, 0.4f, "Leviathan Lava (mine or break) · Ashlands") }, { "GemstoneBlue", new Time(7, 161.19f, "chest charredfortress (loot) · Ashlands") }, { "GemstoneGreen", new Time(7, 163f, "chest charredfortress (loot) · Ashlands") }, { "GemstoneRed", new Time(7, 139.27f, "chest charredfortress (loot) · Ashlands") }, { "Grausten", new Time(7, 0.03f, "cliff ashlands5 (mine or break) · Ashlands") }, { "MoltenCore", new Time(7, 4.98f, "Molten Core Stand (pick) · Ashlands") }, { "MorgenHeart", new Time(7, 372.53f, "Morgen Non Sleeping (kill) · Ashlands") }, { "MorgenSinew", new Time(7, 196.61f, "Morgen Non Sleeping (kill) · Ashlands") }, { "MushroomSmokePuff", new Time(7, 0.97f, "Smoke Puff (pick) · Ashlands") }, { "Pot_Shard_Green", new Time(7, 2.45f, "Pot Shard (pick) · Ashlands") }, { "ProustitePowder", new Time(7, 15.34f, "Blob Lava (kill) · Ashlands") }, { "SulfurStone", new Time(7, 3.26f, "Sulfur Rock (pick) · Ashlands") }, { "TrophyAsksvin", new Time(7, 22.78f, "Asksvin (kill) · Ashlands · farm 5.61 min") }, { "TrophyBlob_Lava", new Time(7, 217.17f, "Blob Lava (kill) · Ashlands") }, { "TrophyBonemawSerpent", new Time(7, 71.46f, "Bonemaw Serpent (kill) · Ashlands") }, { "TrophyCharredArcher", new Time(7, 272.69f, "Charred Archer (kill) · Ashlands") }, { "TrophyCharredMage", new Time(7, 30f, "no source found in the game data (estimated)") }, { "TrophyCharredMelee", new Time(7, 218.3f, "Charred Melee (kill) · Ashlands") }, { "TrophyFallenValkyrie", new Time(7, 2722.03f, "Fallen Valkyrie (kill) · Ashlands") }, { "TrophyMorgen", new Time(7, 5565.72f, "Morgen Non Sleeping (kill) · Ashlands") }, { "TrophyVolture", new Time(7, 55.27f, "Volture (kill) · Ashlands") }, { "Vineberry", new Time(7, 0.15f, "Vine Ash (pick) · Ashlands · farm 0.10 min") }, { "VineberrySeeds", new Time(7, 0.28f, "Vine Ash (pick) · Ashlands · farm 0.10 min") }, { "VoltureEgg", new Time(7, 2.56f, "Volture Egg (pick) · Ashlands") }, { "VoltureMeat", new Time(7, 5.78f, "Volture (kill) · Ashlands") }, { "BarkaBranch", new Time(8, 143.99f, "Barka (kill) · Deep North (rough)") }, { "ElakingHairBundle", new Time(8, 11.14f, "Elaking (kill) · Deep North (rough)") }, { "FirConeFrost", new Time(8, 0.12f, "Snow Fir Tree (chop) · Deep North (rough)") }, { "Fish10", new Time(8, 3f, "fishing (estimated)") }, { "Fish11", new Time(8, 3f, "fishing (estimated)") }, { "FrostCore", new Time(8, 2f, "no source found in the game data (estimated)") }, { "Frostwood", new Time(8, 0.15f, "Pinetree Snow dead (chop) · Deep North (rough)") }, { "FrozenFuel", new Time(8, 2f, "no source found in the game data (estimated)") }, { "GlowWorm", new Time(8, 2f, "no source found in the game data (estimated)") }, { "GoldOre", new Time(8, 2f, "no source found in the game data (estimated)") }, { "Ice", new Time(8, 0.08f, "Ice Shore 1 (mine or break) · Deep North (rough)") }, { "Kale", new Time(8, 0.1f, "farm 0.10 min (rough)") }, { "KaleSeeds", new Time(8, 0.29f, "Seed Kale (pick) · Deep North · farm 0.10 min (rough)") }, { "Leatherstraps", new Time(8, 1365.4f, "Jotun Warrior (kill) · Deep North (rough)") }, { "Lingonberry", new Time(8, 0.21f, "Lingonberry Bush (pick) · Deep North (rough)") }, { "MemorialCoal", new Time(8, 14409.7f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldArmorGoldChest", new Time(8, 76045.5f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldArmorGoldHelmet", new Time(8, 111873f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldArmorGoldLegs", new Time(8, 106440f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldArmorMageChest", new Time(8, 79041.6f, "Jotun Witch (kill) · Deep North (rough)") }, { "MoldArmorMageHelmet", new Time(8, 75143.6f, "Jotun Witch (kill) · Deep North (rough)") }, { "MoldArmorMageLegs", new Time(8, 98729.2f, "Jotun Witch (kill) · Deep North (rough)") }, { "MoldArmorMediumHelmet", new Time(8, 72088.4f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldArmorMediumLegs", new Time(8, 40500.2f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldArmormediumChest", new Time(8, 95045.9f, "Jotun Warrior (kill) · Deep North (rough)") }, { "MoldKeys", new Time(8, 79.65f, "Elaking (kill) · Deep North (rough)") }, { "MoleClaws", new Time(8, 2f, "no source found in the game data (estimated)") }, { "MooseHide", new Time(8, 83.45f, "Moose (kill) · Deep North (rough)") }, { "MooseMeat", new Time(8, 44.82f, "Moose (kill) · Deep North (rough)") }, { "MooseSinew", new Time(8, 85.2f, "Moose (kill) · Deep North (rough)") }, { "NornThread", new Time(8, 1963.74f, "Jotun Witch (kill) · Deep North (rough)") }, { "Oat", new Time(8, 0.1f, "farm 0.10 min (rough)") }, { "OatSeeds", new Time(8, 0.1f, "farm 0.10 min (rough)") }, { "OozeMork", new Time(8, 2f, "no source found in the game data (estimated)") }, { "OrbFrostFire", new Time(8, 2f, "no source found in the game data (estimated)") }, { "OrbThunderBlood", new Time(8, 2f, "no source found in the game data (estimated)") }, { "Poteitr", new Time(8, 0.1f, "farm 0.10 min (rough)") }, { "PoteitrSeeds", new Time(8, 0.1f, "farm 0.10 min (rough)") }, { "SealBlubber", new Time(8, 1.78f, "Seal (kill) · Deep North (rough)") }, { "SealHide", new Time(8, 1.77f, "Seal (kill) · Deep North (rough)") }, { "TrophyBarka", new Time(8, 1734.01f, "Barka (kill) · Deep North (rough)") }, { "TrophyBlob_Frost", new Time(8, 30f, "no source found in the game data (estimated)") }, { "TrophyBlob_Morkhalla", new Time(8, 30f, "no source found in the game data (estimated)") }, { "TrophyElaking", new Time(8, 148.12f, "Elaking (kill) · Deep North (rough)") }, { "TrophyJotunWarrior", new Time(8, 25011.6f, "Jotun Warrior (kill) · Deep North (rough)") }, { "TrophyJotunWitch", new Time(8, 35980.3f, "Jotun Witch (kill) · Deep North (rough)") }, { "TrophyMole", new Time(8, 30f, "no source found in the game data (estimated)") }, { "TrophyMoose", new Time(8, 2233.64f, "Moose (kill) · Deep North (rough)") }, { "TrophySeal", new Time(8, 42.06f, "Seal (kill) · Deep North (rough)") } }; } internal static class Ledger { private const string RpcAdd = "anwo_ws_ledger_add"; private const string RpcAll = "anwo_ws_ledger_all"; private const string RpcAsk = "anwo_ws_ledger_ask"; private static readonly Dictionary<string, int> s_counts = new Dictionary<string, int>(StringComparer.Ordinal); private static readonly Dictionary<string, KeyValuePair<int, float>> s_pending = new Dictionary<string, KeyValuePair<int, float>>(StringComparer.Ordinal); private static readonly List<KeyValuePair<string, int>> s_unsent = new List<KeyValuePair<string, int>>(); private static ZRoutedRpc s_registeredOn; private static bool s_hostHasMod; private static long s_loadedWorld = long.MinValue; private static int s_cleanedDay = int.MinValue; internal static int Day { get { if (!((Object)(object)EnvMan.instance != (Object)null)) { return 0; } return EnvMan.instance.GetDay(); } } internal static long World { get { try { if (WorldGenerator.instance != null) { return WorldGenerator.instance.GetSeed(); } } catch { } try { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.GetWorldUID(); } } catch { } return 0L; } } private static bool IsHost { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } internal static int Get(string key) { if (string.IsNullOrEmpty(key) || !s_counts.TryGetValue(key, out var value)) { return 0; } return value; } internal static void Add(string key, int amount) { if (string.IsNullOrEmpty(key) || amount == 0) { return; } try { Register(); Load(); s_counts[key] = Get(key) + amount; if (IsHost) { Tidy(); Save(); Broadcast(0L); return; } s_pending.TryGetValue(key, out var _); s_pending[key] = new KeyValuePair<int, float>(s_counts[key], Time.realtimeSinceStartup); if (s_hostHasMod) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("anwo_ws_ledger_add", new object[2] { key, amount }); } } else { if (s_unsent.Count < 500) { s_unsent.Add(new KeyValuePair<string, int>(key, amount)); } Save(); } } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Counting a purchase failed: " + ex.Message)); } } internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && s_registeredOn != instance) { s_registeredOn = instance; s_hostHasMod = false; s_counts.Clear(); s_pending.Clear(); s_unsent.Clear(); s_loadedWorld = long.MinValue; instance.Register<string, int>("anwo_ws_ledger_add", (Action<long, string, int>)OnAdd); instance.Register<string>("anwo_ws_ledger_all", (Action<long, string>)OnAll); instance.Register("anwo_ws_ledger_ask", (Action<long>)OnAsk); } } internal static void Join() { try { Register(); Load(); if (!IsHost) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("anwo_ws_ledger_ask", Array.Empty<object>()); } } } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Asking for shop counts failed: " + ex.Message)); } } private static void OnAdd(long sender, string key, int amount) { if (IsHost && !string.IsNullOrEmpty(key) && amount > 0 && amount <= 1000 && Market.ParseKey(key, out var _, out var _)) { Load(); s_counts[key] = Get(key) + amount; Tidy(); Save(); Broadcast(0L); Shop.Forget(); ShopPatches.RefreshList(); } } private static void OnAsk(long sender) { if (IsHost) { Load(); Tidy(); Broadcast(sender); } } private static void Broadcast(long target) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(target, "anwo_ws_ledger_all", new object[1] { Pack() }); } } private static void OnAll(long sender, string packed) { if (IsHost) { return; } bool flag = !s_hostHasMod; s_hostHasMod = true; s_counts.Clear(); foreach (KeyValuePair<string, int> item in Unpack(packed)) { s_counts[item.Key] = item.Value; } float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (string item2 in s_pending.Keys.ToList()) { KeyValuePair<int, float> keyValuePair = s_pending[item2]; if (Get(item2) >= keyValuePair.Key || realtimeSinceStartup - keyValuePair.Value > 10f) { s_pending.Remove(item2); } else { s_counts[item2] = keyValuePair.Key; } } if (flag && s_unsent.Count > 0) { foreach (KeyValuePair<string, int> item3 in s_unsent) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("anwo_ws_ledger_add", new object[2] { item3.Key, item3.Value }); } } s_unsent.Clear(); } Shop.Forget(); ShopPatches.RefreshList(); } internal static string Pack() { StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, int> item in s_counts.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.Ordinal)) { stringBuilder.Append(item.Key).Append('=').Append(item.Value.ToString(CultureInfo.InvariantCulture)) .Append(';'); } return stringBuilder.ToString(); } internal static IEnumerable<KeyValuePair<string, int>> Unpack(string packed) { string[] array = (packed ?? "").Split(new char[3] { ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num).Trim(); if (int.TryParse(text.Substring(num + 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && Market.ParseKey(key, out var _, out var _)) { yield return new KeyValuePair<string, int>(key, result); } } } } private static string FilePath(long world) { return Path.Combine(Paths.ConfigPath, "anwo.WellStocked.ledger." + world.ToString(CultureInfo.InvariantCulture) + (IsHost ? "" : ".mine") + ".txt"); } private static void Load() { long world = World; if (world == s_loadedWorld) { return; } s_loadedWorld = world; if (!IsHost && s_hostHasMod) { return; } try { string path = FilePath(world); if (!File.Exists(path)) { return; } foreach (KeyValuePair<string, int> item in Unpack(File.ReadAllText(path))) { if (!s_counts.ContainsKey(item.Key)) { s_counts[item.Key] = item.Value; } } Tidy(); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Reading shop counts failed: " + ex.Message)); } } private static void Save() { if (!IsHost && s_hostHasMod) { return; } try { File.WriteAllText(FilePath(World), Pack().Replace(";", "\n")); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Saving shop counts failed: " + ex.Message)); } } internal static void Tidy() { int day = Day; if (day == s_cleanedDay) { return; } s_cleanedDay = day; int period = Market.Period(day, WellStockedPlugin.RestockDays.Value); foreach (string item in s_counts.Keys.ToList()) { if (Market.ParseKey(item, out var kind, out var when) && Market.Stale(kind, when, day, period)) { s_counts.Remove(item); } } } internal static int BossesDown() { ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return 0; } int num = 0; string[] array = new string[8] { "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader", "defeated_frozenking_p3" }; foreach (string text in array) { if (instance.GetGlobalKey(text)) { num++; } } return num; } } internal static class MapSketch { internal const int Radius = 500; private const string Key = "anwo_ws_sketches"; private static readonly MethodInfo ExploreMethod = AccessTools.Method(typeof(Minimap), "Explore", new Type[2] { typeof(Vector3), typeof(float) }, (Type[])null); private static readonly MethodInfo ScreenToWorld = AccessTools.Method(typeof(Minimap), "ScreenToWorldPoint", new Type[1] { typeof(Vector3) }, (Type[])null); private static readonly MethodInfo ClosestPin = AccessTools.Method(typeof(Minimap), "GetClosestPin", new Type[3] { typeof(Vector3), typeof(float), typeof(bool) }, (Type[])null); private static readonly PropertyInfo PinRadius = AccessTools.Property(typeof(Minimap), "PinInteractRadius"); private static float s_openAt = -1f; internal static bool Works { get { if (ExploreMethod != null) { return ScreenToWorld != null; } return false; } } internal static int Count(Player player) { if ((Object)(object)player == (Object)null || player.m_customData == null || !player.m_customData.TryGetValue("anwo_ws_sketches", out var value)) { return 0; } if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0; } return Mathf.Max(0, result); } internal static void Add(Player player, int n) { if (!((Object)(object)player == (Object)null) && player.m_customData != null) { int num = Count(player) + n; if (num <= 0) { player.m_customData.Remove("anwo_ws_sketches"); } else { player.m_customData["anwo_ws_sketches"] = num.ToString(CultureInfo.InvariantCulture); } } } internal static string Hint(Player player) { int num = Count(player); return "Click a spot on your map: Haldor sketches the land " + 500 + " m around it" + ((num > 1) ? (" (" + num + " sketches)") : ""); } internal static void OpenSoon() { s_openAt = Time.time + 0.15f; } internal static void Tick() { if (s_openAt < 0f || Time.time < s_openAt) { return; } s_openAt = -1f; try { if ((Object)(object)StoreGui.instance != (Object)null && StoreGui.IsVisible()) { StoreGui.instance.Hide(); } if ((Object)(object)Minimap.instance != (Object)null) { Minimap.instance.SetMapMode((MapMode)2); } } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Opening the map failed: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(Minimap), "OnMapLeftClick")] private static bool LeftClick(Minimap __instance) { //IL_0034: 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) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || Count(localPlayer) <= 0 || !Works) { return true; } Vector3 val = (Vector3)ScreenToWorld.Invoke(__instance, new object[1] { ZInput.pointerPosition }); if (ClosestPin != null && PinRadius != null && ClosestPin.Invoke(__instance, new object[3] { val, (float)PinRadius.GetValue(__instance), true }) != null) { return true; } ExploreMethod.Invoke(__instance, new object[2] { val, 500f }); Add(localPlayer, -1); int num = Count(localPlayer); ((Character)localPlayer).Message((MessageType)2, "Haldor's sketch is on your map" + ((num > 0) ? (" (" + num + " more to place)") : ""), 0, (Sprite)null, false); return false; } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Placing a map sketch failed: " + ex.Message)); return true; } } [HarmonyPostfix] [HarmonyPatch(typeof(Minimap), "SetMapMode")] private static void MapOpened(MapMode mode) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 try { Player localPlayer = Player.m_localPlayer; if ((int)mode == 2 && (Object)(object)localPlayer != (Object)null && Count(localPlayer) > 0) { ((Character)localPlayer).Message((MessageType)2, Hint(localPlayer), 0, (Sprite)null, false); } } catch { } } } internal static class Market { internal sealed class Dice { private ulong _state; public Dice(ulong seed) { _state = ((seed == 0L) ? 11400714819323198485uL : seed); Next(); } public ulong Next() { _state ^= _state >> 12; _state ^= _state << 25; _state ^= _state >> 27; return _state * 2685821657736338717L; } public int Below(int max) { if (max > 1) { return (int)(Next() % (ulong)max); } return 0; } } internal struct Deal { public int Index; public int Percent; public int Quantity; } internal const int DemandSteps = 4; internal const int NoLimit = int.MaxValue; internal const string KeyPrefix = "anwo_ws_"; internal static ulong Hash(string text) { ulong num = 14695981039346656037uL; string text2 = text ?? ""; foreach (char c in text2) { num ^= c; num *= 1099511628211L; } return num; } internal static ulong Seed(long world, int day, string trader, string what) { return Hash(world + "|" + day + "|" + (trader ?? "").ToLowerInvariant() + "|" + what); } internal static List<Deal> PickDeals(int poolCount, int count, int maxPercent, ulong seed) { List<Deal> list = new List<Deal>(); if (poolCount <= 0 || count <= 0 || maxPercent <= 0) { return list; } Dice dice = new Dice(seed); HashSet<int> hashSet = new HashSet<int>(); int num = Math.Max(5, Math.Min(90, maxPercent)); int num2 = Math.Max(5, num / 2); int max = (num - num2) / 5 + 1; int num3 = 0; while (list.Count < count && list.Count < poolCount && num3 < count * 20) { int num4 = dice.Below(poolCount); if (hashSet.Add(num4)) { list.Add(new Deal { Index = num4, Percent = num2 + 5 * dice.Below(max), Quantity = 1 + dice.Below(3) }); } num3++; } return list; } internal static int DealPrice(int price, int percent) { return Math.Max(1, (int)Math.Round((double)(price * (100 - percent)) / 100.0)); } internal static int StockLimit(int stackPrice, float budget, int mostStacks) { if (budget <= 0f) { return -1; } double val = Math.Floor(budget / (float)Math.Max(1, stackPrice)); return (int)Math.Max(1.0, Math.Min(Math.Max(1, mostStacks), val)); } internal static int Period(int day, int restockDays) { return day / Math.Max(1, restockDays); } internal static int DaysToRestock(int day, int restockDays) { int num = Math.Max(1, restockDays); return (Period(Math.Max(0, day), num) + 1) * num - Math.Max(0, day); } internal static int GearLimit(int price, float budget, int most) { int num = Math.Max(1, most); if (budget <= 0f) { return num; } int val = (int)Math.Floor((double)budget / (2.0 * (double)Math.Max(1, price))); return Math.Max(1, Math.Min(num, val)); } internal static int SellPrice(float unitBuy, int amount, float ratio, float condition, int quality) { if (unitBuy <= 0f || amount <= 0 || ratio <= 0f) { return 0; } float num = 0.5f + 0.5f * Math.Max(0f, Math.Min(1f, condition)); float num2 = 1f + 0.5f * (float)Math.Max(0, quality - 1); return Math.Max(1, (int)Math.Floor(unitBuy * ratio * (float)amount * num * num2)); } internal static float SellUnit(float unitBuy, float ratio, float condition, int quality) { if (unitBuy <= 0f || ratio <= 0f) { return 0f; } float num = 0.5f + 0.5f * Math.Max(0f, Math.Min(1f, condition)); float num2 = 1f + 0.5f * (float)Math.Max(0, quality - 1); return unitBuy * ratio * num * num2; } internal static int DemandStep(float unitPay, float demand) { if (demand <= 0f || unitPay <= 0f) { return int.MaxValue; } return (int)Math.Max(1.0, Math.Min(429496729.0, Math.Floor((double)(demand / unitPay) + 0.0001))); } internal static float DemandFactor(int sold, int step) { if (step == int.MaxValue) { return 1f; } int num = Math.Max(0, sold) / Math.Max(1, step); if (num < 4) { return 1f / (float)(1 << num); } return 0f; } internal static int DemandLeft(int sold, int step) { if (step != int.MaxValue) { return Math.Max(0, step * 4 - Math.Max(0, sold)); } return int.MaxValue; } internal static int DemandLeftInStep(int sold, int step) { if (step == int.MaxValue) { return int.MaxValue; } if (DemandFactor(sold, step) <= 0f) { return 0; } return step - Math.Max(0, sold) % Math.Max(1, step); } internal static int DemandPay(float unitPay, int count, int sold, int step, out int taken) { taken = 0; if (unitPay <= 0f || count <= 0) { return 0; } double num = 0.0; for (int i = 0; i < count; i++) { float num2 = DemandFactor(sold + i, step); if (num2 <= 0f) { break; } num += (double)unitPay * (double)num2; taken++; } if (taken <= 0) { return 0; } return Math.Max(1, (int)Math.Floor(num + 0.01)); } internal static string ShareWords(float f) { if (f >= 0.99f) { return "full price"; } if (f >= 0.49f) { return "half price"; } if (f >= 0.24f) { return "a quarter of the price"; } if (f > 0f) { return "an eighth of the price"; } return "nothing"; } internal static int OrderAmount(float unitBuy, int maxStack) { double a = 200.0 / Math.Pow(Math.Max(0.5, unitBuy), 0.75); int num = Math.Max(2, Math.Min(40, (maxStack > 1) ? (maxStack * 2) : 3)); return (int)Math.Max(2.0, Math.Min(num, Math.Round(a))); } internal static int OrderAmountFor(float unitBuy, int maxStack, float daily) { int num = OrderAmount(unitBuy, maxStack); if (daily > 0f) { num = Math.Min(num, Math.Max(1, (int)Math.Round((double)daily * 0.5))); } else if (daily < 0f) { num = Math.Min(num, 10); } return Math.Max(1, num); } internal static int OrderLeft(int amount, int brought) { return Math.Max(0, amount - Math.Max(0, brought)); } internal static int OrderTake(int amount, int brought, int carried) { return Math.Max(0, Math.Min(Math.Max(0, carried), OrderLeft(amount, brought))); } internal static int OrderReward(float unitBuy, int amount, float pay) { return Math.Max(1, (int)Math.Round(unitBuy * (float)amount * Math.Max(0f, pay))); } internal static List<int> PickOrders(int poolCount, int count, ulong seed) { List<int> list = new List<int>(); if (poolCount <= 0 || count <= 0) { return list; } Dice dice = new Dice(seed); HashSet<int> hashSet = new HashSet<int>(); int num = 0; while (list.Count < count && list.Count < poolCount && num < count * 20) { int item = dice.Below(poolCount); if (hashSet.Add(item)) { list.Add(item); } num++; } return list; } internal static float BlessingTime(float left, float seconds, int turns) { return Math.Min(Math.Max(0f, left) + Math.Max(0f, seconds), Math.Max(0f, seconds) * (float)Math.Max(1, turns)); } internal static int ServicePrice(int basePrice, int bossesDown, float multiplier) { return Math.Max(1, (int)Math.Round((float)basePrice * (1f + 0.5f * (float)Math.Max(0, bossesDown)) * Math.Max(0.05f, multiplier))); } internal static int FortunePrice(int basePrice, int bossIndex, float growth, float multiplier) { double num = (double)Math.Max(1, basePrice) * Math.Pow(Math.Max(1f, growth), Math.Max(0, bossIndex)) * (double)Math.Max(0.05f, multiplier); if (num < 100.0) { return Math.Max(1, (int)Math.Round(num)); } if (num < 1000.0) { return (int)(Math.Round(num / 5.0) * 5.0); } return (int)Math.Min(1073741823.0, Math.Round(num / 10.0) * 10.0); } private static string Clean(string s) { return (s ?? "").ToLowerInvariant().Replace(' ', '_'); } internal static string StockKey(int period, string trader, string prefab) { return "anwo_ws_s" + period + "_" + Clean(trader) + "_" + Clean(prefab); } internal static string DealKey(int day, string trader, string prefab) { return "anwo_ws_d" + day + "_" + Clean(trader) + "_" + Clean(prefab); } internal static string OrderKey(int day, string trader, string prefab) { return "anwo_ws_o" + day + "_" + Clean(trader) + "_" + Clean(prefab); } internal static string SoldKey(int day, string trader, string prefab) { return "anwo_ws_b" + day + "_" + Clean(trader) + "_" + Clean(prefab); } internal static float SafeShare(float share, int maxDealPercent) { float num = (float)(100 - Math.Max(0, Math.Min(90, maxDealPercent))) / 100f; return Math.Max(0.01f, Math.Min(share, num * 0.95f)); } internal static bool ParseKey(string key, out char kind, out int when) { kind = ' '; when = 0; if (string.IsNullOrEmpty(key)) { return false; } string text = key.ToLowerInvariant(); int num = text.IndexOf(' '); if (num >= 0) { text = text.Substring(0, num); } if (!text.StartsWith("anwo_ws_", StringComparison.Ordinal) || text.Length < "anwo_ws_".Length + 2) { return false; } kind = text["anwo_ws_".Length]; if (kind != 's' && kind != 'd' && kind != 'o' && kind != 'b') { return false; } int num2 = "anwo_ws_".Length + 1; int num3 = text.IndexOf('_', num2); if (num3 <= num2) { return false; } return int.TryParse(text.Substring(num2, num3 - num2), out when); } internal static bool Stale(char kind, int when, int day, int period) { if (kind == 's') { return when < period - 1; } return when < day - 1; } internal static bool Matches(string name, string search) { if (string.IsNullOrEmpty(search)) { return true; } if (string.IsNullOrEmpty(name)) { return false; } string[] array = search.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string value in array) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) < 0) { return false; } } return true; } } public enum UnlockMode { HeldOrKnown, Bosses, Both, Off } [BepInPlugin("anwo.WellStocked", "WellStocked", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class WellStockedPlugin : BaseUnityPlugin { public const string Guid = "anwo.WellStocked"; public const string ModName = "WellStocked"; public const string Version = "1.0.2"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<string> Traders; internal static ConfigEntry<UnlockMode> Unlock; internal static ConfigEntry<float> PriceMultiplier; internal static ConfigEntry<float> PriceGrowth; internal static ConfigEntry<bool> TimePrices; internal static ConfigEntry<float> MinutesPerUnit; internal static ConfigEntry<float> LongestTime; internal static ConfigEntry<string> SoldCategories; internal static ConfigEntry<string> ExcludedItems; internal static ConfigEntry<string> ExtraItems; internal static ConfigEntry<bool> ShowTabs; internal static ConfigEntry<bool> KeepSelection; internal static ConfigEntry<SortOrder> Sort; internal static ConfigEntry<bool> SearchBox; internal static ConfigEntry<bool> KeepSearch; internal static ConfigEntry<bool> DetailsPanel; internal static ConfigEntry<string> ExtraTraders; internal static ConfigEntry<bool> SellTab; internal static ConfigEntry<float> SellRatio; internal static ConfigEntry<float> Demand; internal static ConfigEntry<int> OrdersPerDay; internal static ConfigEntry<float> OrderPay; internal static ConfigEntry<int> DealsPerDay; internal static ConfigEntry<int> DealDiscount; internal static ConfigEntry<bool> LimitedStock; internal static ConfigEntry<float> StockBudget; internal static ConfigEntry<int> GearStock; internal static ConfigEntry<int> MostStacks; internal static ConfigEntry<int> RestockDays; internal static ConfigEntry<bool> ServicesOn; internal static ConfigEntry<float> ServicePriceMultiplier; internal static ConfigEntry<int> FortunePrice; private Harmony _harmony; private float _nextTick; private ConfigEntry<T> Synced<T>(string section, string key, T value, string description, AcceptableValueBase range = null) { //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_000d: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { val })); } private void Awake() { //IL_051f: Unknown result type (might be due to invalid IL or missing references) //IL_0529: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = Synced("1 - Shop", "Sell more items", value: true, "The traders in Traders sell most items in the game, sorted into categories, each once you've found it (see Unlock). Prices come from what an item is made of and which biome its materials come from, so endgame goods are dear."); Traders = Synced("1 - Shop", "Traders", "Haldor", "Which traders sell the extra items (prefab names, separated by commas: Haldor, Hildir, BogWitch)."); Unlock = Synced("1 - Shop", "Unlock", UnlockMode.HeldOrKnown, "When an extra item goes on sale. HeldOrKnown: once you've held it or know its recipe (each player sees their own). Bosses: a biome's goods once the boss before it is dead (Black Forest goods after Eikthyr, Swamp after the Elder, Mountain after Bonemass, Plains after Moder, Mistlands after Yagluth, Ashlands after the Queen, Deep North after Fader). Both: both at once. Off: everything from the start. Haldor's own goods keep their own rules. Today's deals can show an item before you've found it, but never before its biome's boss is down."); PriceMultiplier = Synced("1 - Shop", "Price multiplier", 1f, "All prices of the extra items times this.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 20f)); PriceGrowth = Synced("1 - Shop", "Price growth per biome", 1.9f, "How many times dearer each biome's raw materials are than the biome before (a Meadows biome unit is 3 coins). Higher makes endgame much dearer while early goods stay cheap: 1.9 makes an Ashlands unit about 140 coins, 2.2 about 340. With Gathering time prices on, a raw material costs half a unit of its biome plus one unit for every few minutes it takes to gather (see Minutes per unit).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1.1f, 3f)); TimePrices = Synced("1 - Shop", "Gathering time prices", value: true, "Prices raw materials by how long one takes to gather, measured by simulating gathering trips with the game's own drop tables, rock and tree health, tool tiers and spawn rates in a real world: travel out and back, searching, harvesting or fighting with the usual gear of the biome. A raw material costs half a biome unit (see Price growth per biome) for reaching its biome, plus one unit for every Minutes per unit it takes to get one: wood or stone hardly more than the half, iron scrap (about 40 seconds) a bit more, a trophy that takes ten kills a lot more. Things you can farm (crops, honey, eggs, tame boar, wolf, lox and asksvin drops) count halfway between hunting and farming them. Made things cost their ingredients, so they follow. The price list shows each material's time and fastest source. Off: the older rule (biome price times a rarity guess)."); MinutesPerUnit = Synced("1 - Shop", "Minutes per unit", 3f, "With Gathering time prices: how many minutes of gathering cost one biome unit (3 coins in the Meadows, about 140 in Ashlands). Lower makes slow, rare things (trophies, gemstones, rare drops) dearer while quick bulk goods hardly change.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f)); LongestTime = Synced("1 - Shop", "Longest time counted", 30f, "With Gathering time prices: anything that takes longer than this many minutes to get one of (rare trophies, moulds, gemstones) is priced as this many, so the rarest things don't cost a fortune.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 240f)); SoldCategories = Synced("1 - Shop", "Categories for sale", string.Join(", ", Stock.Categories), "Which kinds of items are for sale: " + string.Join(", ", Stock.Categories) + "."); ExcludedItems = Synced("1 - Shop", "Not for sale", "", "Item prefab names Haldor doesn't sell, separated by commas (for example SurtlingCore, BlackCore)."); ExtraItems = Synced("1 - Shop", "Own prices", "", "Your own price or extra items: Item:amount:price, separated by commas (for example Wood:50:40, Bronze:5:250). An optional fourth part is the boss key that unlocks it (Iron:10:300:defeated_gdking)."); ShowTabs = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Window", "Category tabs", true, "Show category tabs beside the trader's list (with a Deals tab when there are deals)."); KeepSelection = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Window", "Keep selection after buying", true, "After you buy something it stays selected, so you can buy again right away."); Sort = ((BaseUnityPlugin)this).Config.Bind<SortOrder>("2 - Window", "Sort", SortOrder.Name, "How the trader's list is sorted: Name (A to Z), Cheapest first or Dearest first. The Sort button beside the window switches it too."); SearchBox = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Window", "Search box", true, "Show a search box beside the trader's window. While you type in it, the window's keys (E, Esc and so on) are left alone; Esc or Enter leaves the box."); KeepSearch = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Window", "Keep search", false, "Keep what you typed in the search box the next time you open a trader. Off: the box starts empty."); DetailsPanel = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Window", "Details panel", true, "Show a panel beside the trader's window with the selected line's details: what it is, where it comes from, what it's used for, how many you carry, and today's deal or stock. What selling pays is shown in the Sell tab only."); ExtraTraders = Synced("3 - Trading", "Trader extras", "Haldor, Hildir, BogWitch", "Which traders have the Sell, Orders and Services tabs (prefab names, separated by commas). Each trader offers its own services."); SellTab = Synced("3 - Trading", "Sell tab", value: true, "A Sell tab where the trader buys what you carry. Pick a line: the Sell stack button sells all of that item you carry at once (as many as the trader still wants today), and the Sell 1 button just below it sells just one; a single piece of gear has a plain Sell button. What you wear or hold is never offered. Each trader buys only so much of one item per day (see Trader demand); the list shows what he pays now, and greys out what he wants no more of. Valuables (amber, rubies and the like) are listed first, in gold, and pay their full value, as with the game's own Sell button."); SellRatio = Synced("3 - Trading", "Sell price", 0.3f, "What the trader pays for an item, as a share of its shop price (0.3 = 30%). Upgraded gear pays more (half again per quality level); wear doesn't matter, since repairing is free. Keep it below the deal prices (Deal discount) or buying on a deal and selling back makes money.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.9f)); Demand = Synced("3 - Trading", "Trader demand", 500f, "How much of one item a trader buys at full price per day, counted in the coins he pays: with 500, Haldor pays full price for about 42 lox meat, then half price for the next 42, a quarter for the next 42 and an eighth for the next 42, and after that he takes no more lox meat until tomorrow. One item at most pays a bit less than twice this per trader per day (500 + 250 + 125 + 62). It counts by day, not by restock. Each trader counts on his own, and the count is shared by everyone in the world. It keeps farms (bred high-star animals, hen pens) from paying for everything in the shop, while selling a mix of loot pays as usual. Gear counts too: an expensive sword can be one step on its own, so the second of the same sword pays half. Valuables (amber, rubies and the like) are always bought at full worth, as many as you bring. Orders aren't affected. 0 = no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100000f)); OrdersPerDay = Synced("3 - Trading", "Orders per day", 2, "How many orders each trader posts per day: a pile of one material, food or trophy to deliver for a reward. Orders are the same for everyone in the world. Anyone can add what they carry to an order, a few at a time; the player who brings the last ones fills it and gets the whole reward. New orders every morning. 0 = no Orders tab.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 6)); OrderPay = Synced("3 - Trading", "Order pay", 0.6f, "What an order pays, as a share of the shop price of what you deliver (0.6 = 60%, twice what selling pays by default). Keep it at or below 1 minus the top Deal discount (0.6 with 40% deals), or buying an order's goods on a deal and delivering them makes money.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f)); DealsPerDay = Synced("4 - Deals and stock", "Deals per day", 4, "How many goods each trader in Traders has on sale each day, picked from what's on offer in the biomes you've opened. The same deals for everyone in the world. 0 = no deals.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 12)); DealDiscount = Synced("4 - Deals and stock", "Deal discount", 40, "The biggest discount a deal can have, in percent. Each deal is between half this and this (40 = 20% to 40% off). Each deal has 1 to 3 to go round, for everyone together (a deal on a weapon, armour or tool is a single piece).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 80)); LimitedStock = Synced("4 - Deals and stock", "Limited stock", value: true, "Every good runs out: the trader has only so many of each until the next restock, shared by everyone in the world, the trader's own goods included. Cheap goods come by the pile (up to Most stacks per restock), dear ones a few at a time, and weapons, armour, shields and tools a couple each (see Gear in stock). The details panel shows how many are left and when the next restock is. Off: everything is always in stock."); StockBudget = Synced("4 - Deals and stock", "Stock per restock", 3000f, "How much of each good the trader has per restock, in coins: a stack costing 1000 has 3 in stock, one costing 3000 or more has 1, and cheap goods as many stacks as this buys, up to Most stacks per restock. Gear has its own, smaller stock: see Gear in stock.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(200f, 100000f)); MostStacks = Synced("4 - Deals and stock", "Most stacks per restock", 10, "The most stacks of one cheap good a trader has per restock (wood, stone, berries, arrows...). A stack is what one purchase gives (20 wood, 10 iron...), so 10 means 200 wood or 100 iron at most until the next restock. Dearer goods have fewer (see Stock per restock). Only while Limited stock is on.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100)); GearStock = Synced("4 - Deals and stock", "Gear in stock", 2, "How many of each weapon, piece of armour, shield, tool or other one-of-a-kind item the trader has per restock, shared by everyone in the world. Gear costing more than half of Stock per restock (1500 coins by default) has only 1. A trader keeps a couple of each sword or helmet, never a pile. Only while Limited stock is on.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10)); RestockDays = Synced("4 - Deals and stock", "Restock every", 7, "Days between restocks: the limited stock and gear come back every this many in-game days, all on the same morning. 7 = once a week (an in-game day is about 30 minutes of play, so a week is about three and a half hours); 1 = every morning. Deals, orders and what traders buy (Trader demand) still change every day. The details panel says when the next restock is.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30)); ServicesOn = Synced("5 - Services", "Services", value: true, "A Services tab: Haldor packs your load (carry more), mends your gear and sketches a map of any spot you pick on your map; Hildir makes your step light (less stamina for running and jumping), lines your clothes against frost and mends gear; the Bog Witch speeds up your skills, reads your fortune (marks the next boss's altar) and wakes your Forsaken power. Services aren't items: they work at once. Timed blessings last through logging out, one at a time: a different one replaces the one you have, and buying the same one again adds its time, up to three times as long."); ServicePriceMultiplier = Synced("5 - Services", "Service prices", 1f, "All service prices times this. Prices also go up by half the base price for each boss defeated in the world (all of them, the Deep North's included), so a service costs twice its base price after two bosses. Reading your fortune is priced by the boss it finds instead: see Fortune price (this setting multiplies it too).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f)); FortunePrice = Synced("5 - Services", "Fortune price", 150, "What the Bog Witch asks for reading your fortune when the boss she finds is Eikthyr. Each later boss costs 'Price growth per biome' times more, like the goods of each new biome. With the default settings: Eikthyr 150, The Elder 285, Bonemass 540, Moder 1030, Yagluth 1950, The Queen 3710, Fader 7060, the Deep North's boss 13410 coins - about one piece of the gear you'd fight that boss in. Finding a boss's altar yourself (vegvisirs) is a big part of the game, so the shortcut isn't cheap. She won't take your coins for a boss whose altar your map already shows.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 10000)); ((BaseUnityPlugin)this).Config.Save(); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; SynchronizationManager.OnConfigurationSynchronized += delegate { Refresh(); }; _harmony = new Harmony("anwo.WellStocked"); _harmony.PatchAll(typeof(ShopPatches)); _harmony.PatchAll(typeof(MapSketch)); Log.LogInfo((object)"WellStocked 1.0.2 loaded."); } private void Update() { MapSketch.Tick(); if (Time.time < _nextTick) { return; } _nextTick = Time.time + 10f; try { Services.Tick(Player.m_localPlayer); } catch (Exception ex) { Log.LogDebug((object)("Blessing check failed: " + ex.Message)); } } private static void OnSettingChanged(object sender, SettingChangedEventArgs e) { if (e.ChangedSetting.Definition.Section == "1 - Shop") { Refresh(); return; } Shop.Forget(); if ((Object)(object)StoreGui.instance != (Object)null && StoreGui.IsVisible()) { Window.Layout(StoreGui.instance); } ShopPatches.RefreshList(); } internal static void Refresh() { Stock.Invalidate(); Shop.Reset(); Details.Forget(); ShopPatches.RefreshList(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal static HashSet<string> NameSet(string value) { return new HashSet<string>(from s in (value ?? "").Split(',') select s.Trim() into s where s.Length > 0 select s, StringComparer.OrdinalIgnoreCase); } } internal static class ShopPatches { private static int s_coinsBefore = -1; private static Row s_buying; private static readonly MethodInfo FillList = AccessTools.Method(typeof(StoreGui), "FillList", (Type[])null, (Type[])null); [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyPatch(typeof(Trader), "GetAvailableItems")] private static void Available(Trader __instance, ref List<TradeItem> __result) { try { Shop.Available(__instance, ref __result); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Trader list failed: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(StoreGui), "GetSelectedItemIndex")] private static bool SelectedIndex(StoreGui __instance, ref int __result) { try { if (!WellStockedPlugin.KeepSelection.Value) { return true; } Trader val = Shop.TraderOf(__instance); TradeItem selected = Shop.SelectedField.Invoke(__instance); if ((Object)(object)val == (Object)null || selected == null) { return true; } List<TradeItem> availableItems = val.GetAvailableItems(); int num = availableItems.IndexOf(selected); if (num < 0 && (Object)(object)selected.m_prefab != (Object)null) { num = availableItems.FindIndex((TradeItem t) => t != null && (Object)(object)t.m_prefab == (Object)(object)selected.m_prefab && t.m_stack == selected.m_stack); } if (num < 0 && (Object)(object)selected.m_prefab != (Object)null) { num = availableItems.FindIndex((TradeItem t) => t != null && (Object)(object)t.m_prefab == (Object)(object)selected.m_prefab); } __result = Mathf.Max(0, num); return false; } catch { return true; } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "OnSpawned")] private static void Spawned(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { if (WellStockedPlugin.Enabled.Value) { _ = Stock.Items; } Ledger.Join(); Services.Restore(__instance); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Getting the shop ready at spawn failed: " + ex.Message)); } } [HarmonyPostfix] [HarmonyPatch(typeof(Game), "Start")] private static void GameStart() { try { Ledger.Register(); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Registering shop messages failed: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(StoreGui), "Show")] private static void ShowPrefix() { Shop.Mode = ShopMode.Buy; Tabs.Selected = "All"; if (!WellStockedPlugin.KeepSearch.Value) { Shop.Search = ""; } Shop.Forget(); } [HarmonyPostfix] [HarmonyPatch(typeof(StoreGui), "Show")] private static void Shown(StoreGui __instance) { Window.Shown(__instance); Window.ShowDetails(__instance); if ((Object)(object)__instance.m_listScroll != (Object)null) { __instance.m_listScroll.value = 1f; } } [HarmonyPostfix] [HarmonyPatch(typeof(StoreGui), "Hide")] private static void Hidden() { Window.Hidden(); } [HarmonyPrefix] [HarmonyPatch(typeof(StoreGui), "Update")] private static bool UpdatePrefix(StoreGui __instance) { try { return Window.Update(__instance); } catch { return true; } } [HarmonyPrefix] [HarmonyPatch(typeof(StoreGui), "FillList")] private static void Filling() { Shop.Drawing(); } [HarmonyPostfix] [HarmonyPatch(typeof(StoreGui), "FillList")] private static void Filled(StoreGui __instance, List<GameObject> ___m_itemList) { try { Window.Decorate(__instance, ___m_itemList); Tabs.UpdateCounts(__instance); Window.ShowDetails(__instance); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("After drawing the list: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(StoreGui), "SelectItem")] private static bool SelectPrefix(StoreGui __instance, int index) { try { Trader val = Shop.TraderOf(__instance); if ((Object)(object)val == (Object)null) { return true; } int count = val.GetAvailableItems().Count; if (index >= 0 && index < count) { return true; } Shop.SelectedField.Invoke(__instance) = null; return false; } catch { return true; } } [HarmonyPostfix] [HarmonyPatch(typeof(StoreGui), "SelectItem")] private static void Selected(StoreGui __instance) { Window.ShowDetails(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(StoreGui), "UpdateBuyButton")] private static void BuyButton(StoreGui __instance) { Window.BuyButton(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(StoreGui), "BuySelectedItem")] private static bool BuyPrefix(StoreGui __instance) { s_buying = null; try { Trader trader = Shop.TraderOf(__instance); Row row = Shop.RowOf(Shop.SelectedField.Invoke(__instance)); Player localPlayer = Player.m_localPlayer; string message = null; if (row != null && row.Mode != Shop.Mode) { return false; } if (row == null && Shop.Mode != ShopMode.Buy) { return false; } bool flag; switch (Shop.Mode) { case ShopMode.Sell: flag = Shop.SellRow(row, trader, out message); break; case ShopMode.Orders: flag = Shop.Deliver(row, trader, out message); break; case ShopMode.Services: flag = Shop.BuyService(row, trader, out message); break; default: if (row != null && Shop.Left(row) == 0) { if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, Shop.SoldOutText(row), 0, (Sprite)null, false); } return false; } s_buying = row; s_coinsBefore = Shop.Coins(localPlayer); return true; } if (!string.IsNullOrEmpty(message) && localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null, false); } if (flag) { Shop.Forget(); RefreshList(); } return false; } catch (Exception ex) { WellStockedPlugin.Log.LogWarning((object)("Shop action failed: " + ex)); return Shop.Mode == ShopMode.Buy; } } [HarmonyPostfix] [HarmonyPatch(typeof(StoreGui), "BuySelectedItem")] private static void BuyPostfix(StoreGui __instance) { try { Row row = s_buying; s_buying = null; if (row == null || Shop.Mode != ShopMode.Buy || Shop.Coins(Player.m_localPlayer) >= s_coinsBefore) { return; } if (row.Percent > 0) { Ledger.Add(row.DealKey, 1); if (Shop.Left(row) == 0) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "That was the last one on offer today", 0, (Sprite)null, false); } } } else { if (row.Limit < 0) { return; } Ledger.Add(row.StockKey, 1); if (Shop.Left(row) == 0) { Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, "That was the last one; more " + Shop.StockBack, 0, (Sprite)null, false); } } } Shop.Forget(); RefreshList(); } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Counting a purchase failed: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "OnDeath")] private static void Died(Player __instance) { try { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { Services.Clear(__instance); } } catch { } } [HarmonyPostfix] [HarmonyPatch(typeof(TextInput), "IsVisible")] private static void TextInputVisible(ref bool __result) { if (!__result && Window.Typing) { __result = true; } } internal static void RefreshList(bool toTop = false) { try { StoreGui instance = StoreGui.instance; if (Shop.Open(instance)) { FillList.Invoke(instance, null); if (toTop && (Object)(object)instance.m_listScroll != (Object)null) { instance.m_listScroll.value = 1f; } } } catch (Exception ex) { WellStockedPlugin.Log.LogDebug((object)("Refreshing the trader's list failed: " + ex.Message)); } } } internal enum WayKind { Craft, Smelt, Process, Cook, Ferment } internal sealed class Way { public string Output; public int OutputAmount = 1; public WayKind Kind; public readonly List<KeyValuePair<string, int>> Inputs = new List<KeyValuePair<string, int>>(); public Way(string output, int amount, WayKind kind) { Output = output; OutputAmount = Math.Max(1, amount); Kind = kind; } public Way In(string item, int amount) { if (!string.IsNullOrEmpty(item) && amount > 0 && item != Output) { Inputs.Add(new KeyValuePair<string, int>(item, amount)); } return this; } } internal sealed class PriceSettings { public float BaseRaw = 3f; public float Growth = 1.9f; public float CraftMarkup = 1.3f; public float SmeltMarkup = 1.5f; public float ProcessMarkup = 1.2f; public float CookMarkup = 1.4f; public float FermentMarkup = 1.5f; public float TimeBase = 0.5f; public float TimeUnit = 3f; public float TimeCap = 30f; } internal sealed class PriceBook { public readonly Dictionary<string, int> Tier = new Dictionary<string, int>(StringComparer.Ordinal); public readonly Dictionary<string, float> Price = new Dictionary<string, float>(StringComparer.Ordinal); public readonly Dictionary<string, string> TierSource = new Dictionary<string, string>(StringComparer.Ordinal); public readonly HashSet<string> Raw = new HashSet<string>(StringComparer.Ordinal); public readonly Dictionary<string, Way> Made = new Dictionary<string, Way>(StringComparer.Ordinal); public readonly Dictionary<string, float> Rarity = new Dictionary<string, float>(StringComparer.Ordinal); public readonly Dictionary<string, float> Kind = new Dictionary<string, float>(StringComparer.Ordinal); public readonly Dictionary<string, GatherTimes.Time> Timed = new Dictionary<string, GatherTimes.Time>(StringComparer.Ordinal); public static PriceBook Compute(IDictionary<string, int> tableTiers, IDictionary<string, float> tablePrices, IDictionary<string, int> biomeTiers, IList<Way> ways, PriceSettings s, IDictionary<string, float> rarity = null, IDictionary<string, float> kind = null, IDictionary<string, GatherTimes.Time> times = null) { PriceBook priceBook = new PriceBook(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (Way way in ways) { if (way.Inputs.Count > 0) { hashSet.Add(way.Output); } } foreach (KeyValuePair<string, int> tableTier in tableTiers) { if (tableTier.Value >= 1) { priceBook.Tier[tableTier.Key] = tableTier.Value; priceBook.TierSource[tableTier.Key] = "table"; priceBook.Raw.Add(tableTier.Key); } } HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); if (tablePrices != null) { foreach (KeyValuePair<string, float> tablePrice in tablePrices) { if (tablePrice.Value > 0f) { hashSet2.Add(tablePrice.Key); priceBook.Raw.Add(tablePrice.Key); } } } foreach (KeyValuePair<string, int> biomeTier in biomeTiers) { if (biomeTier.Value >= 1 && !priceBook.Tier.ContainsKey(biomeTier.Key) && !hashSet.Contains(biomeTier.Key)) { priceBook.Tier[biomeTier.Key] = biomeTier.Value; priceBook.TierSource[biomeTier.Key] = "biome"; priceBook.Raw.Add(biomeTier.Key); } } for (int i = 0; i < 12; i++) { bool flag = false; for (int j = 0; j < 40; j++) { bool flag2 = false; foreach (Way way2 in ways) { if (way2.Inputs.Count == 0 || (priceBook.Raw.Contains(way2.Output) && !hashSet2.Contains(way2.Output)) || (priceBook.TierSource.TryGetValue(way2.Output, out var value) && value != "made")) { continue; } int num = 0; bool flag3 = true; foreach (KeyValuePair<string, int> input in way2.Inputs) { if (!priceBook.Tier.TryGetValue(input.Key, out var value2)) { flag3 = false; break; } num = Math.Max(num, value2); } if (flag3 && num >= 1 && (!priceBook.Tier.TryGetValue(way2.Output, out var value3) || num < value3)) { priceBook.Tier[way2.Output] = num; priceBook.TierSource[way2.Output] = "made"; flag2 = true; } } if (!flag2) { break; } flag = true; } bool flag4 = false; foreach (KeyValuePair<string, int> biomeTier2 in biomeTiers) { if (biomeTier2.Value >= 1 && !priceBook.Tier.ContainsKey(biomeTier2.Key)) { priceBook.Tier[biomeTier2.Key] = biomeTier2.Value; priceBook.TierSource[biomeTier2.Key] = "biome"; priceBook.Raw.Add(biomeTier2.Key); flag4 = true; } } if (flag4) { continue; } Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); foreach (Way way3 in ways) { int num2 = 0; foreach (KeyValuePair<string, int> input2 in way3.Inputs) { if (priceBook.Tier.TryGetValue(input2.Key, out var value4)) { num2 = Math.Max(num2, value4); } } if (priceBook.Tier.TryGetValue(way3.Output, out var value5) && priceBook.TierSource[way3.Output] != "made") { num2 = Math.Max(num2, value5); } if (num2 < 1) { continue; } foreach (KeyValuePair<string, int> input3 in way3.Inputs) { if (!priceBook.Tier.ContainsKey(input3.Key) && !hashSet.Contains(input3.Key) && (!dictionary.TryGetValue(input3.Key, out var value6) || num2 < value6)) { dictionary[input3.Key] = num2; } } } foreach (KeyValuePair<string, int> item in dictionary) { priceBook.Tier[item.Key] = item.Value; priceBook.TierSource[item.Key] = "use"; priceBook.Raw.Add(item.Key); flag = true; } if (!flag) { break; } } foreach (string item2 in hashSet2) { if (!priceBook.Tier.ContainsKey(item2)) { priceBook.Tier[item2] = 1; priceBook.TierSource[item2] = "trader"; } } foreach (string item3 in priceBook.Raw) { float value8; if (times != null && times.TryGetValue(item3, out var value7) && value7.Tier >= 1) { priceBook.Price[item3] = TimePrice(value7, s); priceBook.Timed[item3] = value7; } else if (tablePrices != null && tablePrices.TryGetValue(item3, out value8) && value8 > 0f) { priceBook.Price[item3] = value8; } else if (priceBook.Tier.ContainsKey(item3)) { float num3 = 1f; if (rarity != null && rarity.TryGetValue(item3, out var value9)) { num3 = Math.Max(1f, value9); } if (num3 > 1f) { priceBook.Rarity[item3] = num3; } float num4 = 1f; if (kind != null && kind.TryGetValue(item3, out var value10) && value10 > 0f) { num4 = value10; } if (num4 != 1f) { priceBook.Kind[item3] = num4; } priceBook.Price[item3] = RawPrice(priceBook.Tier[item3], s) * num3 * num4; } } for (int k = 0; k < 60; k++) { bool flag5 = false; foreach (Way way4 in ways) { if (way4.Inputs.Count == 0 || priceBook.Raw.Contains(way4.Output)) { continue; } float num5 = 0f; bool flag6 = true; foreach (KeyValuePair<string, int> input4 in way4.Inputs) { if (!priceBook.Price.TryGetValue(input4.Key, out var value11)) { flag6 = false; break; } num5 += value11 * (float)input4.Value; } if (flag6) { float num6 = num5 / (float)way4.OutputAmount * Markup(way4.Kind, s); if (!priceBook.Price.TryGetValue(way4.Output, out var value12) || num6 < value12 - 0.001f) { priceBook.Price[way4.Output] = num6; priceBook.Made[way4.Output] = way4; flag5 = true; } } } if (!flag5) { break; } } return priceBook; } public static float TimePrice(GatherTimes.Time time, PriceSettings s) { float num = Math.Max(0f, Math.Min(time.Minutes, Math.Max(1f, s.TimeCap))); return RawPrice(time.Tier, s) * (Math.Max(0f, s.TimeBase) + num / Math.Max(0.1f, s.TimeUnit)); } public static float RawPrice(int tier, PriceSettings s) { return s.BaseRaw * (float)Math.Pow(Math.Max(1f, s.Growth), Math.Max(0, tier - 1)); } private static float Markup(WayKind kind, PriceSettings s) { return kind switch { WayKind.Smelt => s.SmeltMarkup, WayKind.Process => s.ProcessMarkup, WayKind.Cook => s.CookMarkup, WayKind.Ferment => s.FermentMarkup, _ => s.CraftMarkup, }; } public static int Round(float price) { if (price <= 1f) { return 1; } if (price < 100f) { return (int)Math.Round(price); } if (price < 1000f) { return (int)(Math.Round(price / 5f) * 5.0); } return (int)(Math.Round(price / 10f) * 10.0); } public static int StackFor(float unitPrice, int maxStack) { int val = ((unitPrice >= 100f) ? 1 : ((unitPrice >= 25f) ? 5 : ((unitPrice >= 5f) ? 10 : 20))); return Math.Max(1, Math.Min(val, Math.Max(1, maxStack))); } } internal static class RawTable { internal static readonly Dictionary<string, int> Tiers = new Dictionary<string, int> { { "Coins", 1 }, { "Wood", 1 }, { "Stone", 1 }, { "Resin", 1 }, { "Flint", 1 }, { "LeatherScraps", 1 }, { "DeerHide", 1 }, { "RawMeat", 1 }, { "DeerMeat", 1 }, { "NeckTail", 1 }, { "Feathers", 1 }, { "Raspberry", 1 }, { "Mushroom", 1 }, { "Honey", 1 }, { "Dandelion", 1 }, { "BoneFragments", 1 }, { "QueenBee", 1 }, { "Acorn", 1 }, { "BeechSeeds", 1 }, { "BirchSeeds", 1 }, { "Fish1", 1 }, { "Fish2", 1 }, { "FineWood", 2 }, { "RoundLog", 2 }, { "CopperOre", 2 }, { "TinOre", 2 }, { "Coal", 2 }, { "SurtlingCore", 2 }, { "GreydwarfEye", 2 }, { "TrollHide", 2 }, { "Blueberries", 2 }, { "Thistle", 2 }, { "Carrot", 2 }, { "CarrotSeeds", 2 }, { "MushroomYellow", 2 }, { "FirCone", 2 }, { "PineCone", 2 }, { "BjornHide", 2 }, { "BjornPaw", 2 }, { "BjornMeat", 2 }, { "CopperScrap", 2 }, { "Fish5", 2 }, { "IronScrap", 3 }, { "IronOre", 3 }, { "ElderBark", 3 }, { "Guck", 3 }, { "Ooze", 3 }, { "Entrails", 3 }, { "Bloodbag", 3 }, { "Root", 3 }, { "Turnip", 3 }, { "TurnipSeeds", 3 }, { "Chain", 3 }, { "SerpentScale", 3 }, { "SerpentMeat", 3 }, { "Chitin", 3 }, { "Thunderstone", 3 }, { "Fish3", 3 }, { "Fish8", 3 }, { "SilverOre", 4 }, { "Obsidian", 4 }, { "WolfPelt", 4 }, { "WolfFang", 4 }, { "WolfClaw", 4 }, { "WolfMeat", 4 }, { "FreezeGland", 4 }, { "Crystal", 4 }, { "Onion", 4 }, { "OnionSeeds", 4 }, { "JuteRed", 4 }, { "Fish4_cave", 4 }, { "MushroomBlue", 4 }, { "BlackMetalScrap", 5 }, { "Flax", 5 }, { "Barley", 5 }, { "Tar", 5 }, { "Needle", 5 }, { "LoxPelt", 5 }, { "LoxMeat", 5 }, { "Cloudberry", 5 }, { "ChickenEgg", 5 }, { "ChickenMeat", 5 }, { "Fish6", 5 }, { "Softtissue", 6 }, { "BlackMarble", 6 }, { "YggdrasilWood", 6 }, { "Carapace", 6 }, { "Mandible", 6 }, { "Sap", 6 }, { "RoyalJelly", 6 }, { "MushroomJotunPuffs", 6 }, { "MushroomMagecap", 6 }, { "GiantBloodSack", 6 }, { "Bilebag", 6 }, { "ScaleHide", 6 }, { "BlackCore", 6 }, { "Wisp", 6 }, { "HareMeat", 6 }, { "BugMeat", 6 }, { "JuteBlue", 6 }, { "Fish9", 6 }, { "FlametalOreNew", 7 }, { "FlametalOre", 7 }, { "Blackwood", 7 }, { "AskHide", 7 }, { "AskBladder", 7 }, { "CharredBone", 7 }, { "CelestialFeather", 7 }, { "MorgenSinew", 7 }, { "MorgenHeart", 7 }, { "ProustitePowder", 7 }, { "SulfurStone", 7 }, { "VoltureEgg", 7 }, { "VoltureMeat", 7 }, { "AsksvinMeat", 7 }, { "BonemawSerpentTooth", 7 }, { "BoneMawSerpentMeat", 7 }, { "Fiddleheadfern", 7 }, { "MushroomSmokePuff", 7 }, { "Pukeberries", 7 }, { "Vineberry", 7 }, { "VineberrySeeds", 7 }, { "MoltenCore", 7 }, { "GemstoneRed", 7 }, { "GemstoneBlue", 7 }, { "GemstoneGreen", 7 }, { "Grausten", 7 }, { "CharcoalResin", 7 }, { "Fish12", 7 }, { "MooseHide", 8 }, { "MooseMeat", 8 }, { "MooseSinew", 8 }, { "SealHide", 8 }, { "SealBlubber", 8 }, { "ElakingHairBundle", 8 }, { "NornThread", 8 }, { "Leatherstraps", 8 }, { "WrithanRoots", 8 }, { "OozeMork", 8 }, { "MoleClaws", 8 }, { "FrostCore", 8 }, { "FrozenFuel", 8 }, { "Ice", 8 }, { "Frostwood", 8 }, { "BarkaBranch", 8 }, { "FirConeFrost", 8 }, { "Kale", 8 }, { "KaleSeeds", 8 }, { "Oat", 8 }, { "OatSeeds", 8 }, { "Poteitr", 8 }, { "PoteitrSeeds", 8 }, { "Lingonberry", 8 }, { "GoldOre", 8 }, { "Hook", 8 }, { "OrbFrostFire", 8 }, { "OrbThunderBlood", 8 }, { "GlowWorm", 8 }, { "Fish10", 8 }, { "Fish11", 8 }, { "TrophyDeer", 1 }, { "TrophyBoar", 1 }, { "TrophyNeck", 1 }, { "TrophyGreydwarf", 2 }, { "TrophyGreydwarfBrute", 2 }, { "TrophyGreydwarfShaman", 2 }, { "TrophySkeleton", 2 }, { "TrophySkeletonPoison", 2 }, { "TrophyForestTroll", 2 }, { "TrophyGhost", 2 }, { "TrophyBjorn", 2 }, { "TrophyDraugr", 3 }, { "TrophyDraugrElite", 3 }, { "TrophyDraugrFem", 3 }, { "TrophyBlob", 3 }, { "TrophyLeech", 3 }, { "TrophySurtling", 3 }, { "TrophyWraith", 3 }, { "TrophyAbomination", 3 }, { "TrophyKvastur", 3 }, { "TrophySerpent", 3 }, { "TrophyWolf", 4 }, { "TrophyFenring", 4 }, { "TrophyCultist", 4 }, { "TrophySGolem", 4 }, { "TrophyUlv", 4 }, { "TrophyHatchling", 4 }, { "TrophyGoblin", 5 }, { "TrophyGoblinBrute", 5 }, { "TrophyGoblinShaman", 5 }, { "TrophyDeathsquito", 5 }, { "TrophyLox", 5 }, { "TrophyGrowth", 5 }, { "TrophyBjornUndead", 5 }, { "TrophySeeker", 6 }, { "TrophySeekerBrute", 6 }, { "TrophyGjall", 6 }, { "TrophyTick", 6 }, { "TrophyHare", 6 }, { "TrophyDvergr", 6 }, { "TrophyCharredMelee", 7 }, { "TrophyCharredArcher", 7 }, { "TrophyCharredMage", 7 }, { "TrophyAsksvin", 7 }, { "TrophyVolture", 7 }, { "TrophyMorgen", 7 }, { "TrophyFallenValkyrie", 7 }, { "TrophyBonemawSerpent", 7 }, { "TrophyBlob_Lava", 7 }, { "TrophyBarka", 8 }, { "TrophyElaking", 8 }, { "TrophyMole", 8 }, { "TrophyMoose", 8 }, { "TrophySeal", 8 }, { "TrophyWrithan", 8 }, { "TrophyJotunWarrior", 8 }, { "TrophyJotunWitch", 8 }, { "TrophyBlob_Frost", 8 }, { "TrophyBlob_Morkhalla", 8 }, { "TrophyFrostTroll", 8 } }; internal static readonly Dictionary<string, float> Prices = new Dictionary<string, float> { { "Coins", 1f }, { "Coal", 4f }, { "SurtlingCore", 40f }, { "GreydwarfEye", 3f }, { "QueenBee", 25f }, { "Thunderstone", 60f }, { "Crystal", 30f }, { "FreezeGland", 30f }, { "BlackCore", 400f }, { "Wisp", 60f }, { "RoyalJelly", 80f }, { "MoltenCore", 600f }, { "GemstoneRed", 1200f }, { "GemstoneBlue", 1200f }, { "GemstoneGreen", 1200f }, { "MorgenHeart", 600f }, { "CelestialFeather", 250f }, { "DyrnwynBladeFragment", 10000f }, { "DyrnwynHiltFragment", 10000f }, { "DyrnwynTipFragment", 10000f } }; internal const float Bulk = 0.5f; internal const float Plant = 0.4f; internal const float Farm = 0.33f; internal static readonly Dictionary<string, float> Kinds = new Dictionary<string, float> { { "Wood", 0.5f }, { "FineWood", 0.5f }, { "RoundLog", 0.5f }, { "ElderBark", 0.5f }, { "YggdrasilWood", 0.5f }, { "Blackwood", 0.5f }, { "Frostwood", 0.5f }, { "Stone", 0.5f }, { "Grausten", 0.5f }, { "BlackMarble", 0.5f }, { "Ice", 0.5f }, { "Resin", 0.5f }, { "Raspberry", 0.4f }, { "Blueberries", 0.4f }, { "Cloudberry", 0.4f }, { "Lingonberry", 0.4f }, { "Vineberry", 0.4f }, { "Pukeberries", 0.4f }, { "Mushroom", 0.4f }, { "MushroomYellow", 0.4f }, { "MushroomBlue", 0.4f }, { "MushroomJotunPuffs", 0.4f }, { "MushroomMagecap", 0.4f }, { "MushroomSmokePuff", 0.4f }, { "Fiddleheadfern", 0.4f }, { "Dandelion", 0.4f }, { "Thistle", 0.4f }, { "Carrot", 0.4f }, { "Turnip", 0.4f }, { "Onion", 0.4f }, { "Barley", 0.4f }, { "Flax", 0.4f }, { "Kale", 0.4f }, { "Oat", 0.4f }, { "Poteitr", 0.4f }, { "CarrotSeeds", 0.4f }, { "TurnipSeeds", 0.4f }, { "OnionSeeds", 0.4f }, { "VineberrySeeds", 0.4f }, { "KaleSeeds", 0.4f }, { "OatSeeds", 0.4f }, { "PoteitrSeeds", 0.4f }, { "ChickenEgg", 0.33f }, { "Acorn", 0.4f }, { "BeechSeeds", 0.4f }, { "BirchSeeds", 0.4f }, { "FirCone", 0.4f }, { "PineCone", 0.4f }, { "FirConeFrost", 0.4f } }; internal static readonly HashSet<string> NotForSale = new HashSet<string> { "AncientSeed", "WitheredBone", "DragonEgg", "GoblinTotem", "DvergrKeyFragment", "BellFragment", "CryptKey", "Coins", "Tankard", "TankardAnniversary", "Wishbone", "YmirRemains", "DyrnwynBladeFragment", "DyrnwynHiltFragment", "DyrnwynTipFragment" }; internal static bool IsLoot(string prefab) { return prefab.StartsWith("Upgrader", StringComparison.Ordinal); } } internal sealed class Service { public delegate bool ApplyFn(Player player, out string message); public string Id; public string Name; public string Description; public int BasePrice; public string[] Icons; public Func<Player, string> Unavailable = (Player p) => (string)null; public ApplyFn Apply; public Func<float, int> Price; public Func<string> Extra; public Func<string> PriceNote; private Sprite _icon; public Sprite Icon() { if ((Object)(object)_icon != (Object)null) { return _icon; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return null; } string[] array = Icons ?? new string[0]; foreach (string text in array) { GameObject itemPrefab = instance.GetItemPrefab(text); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val != (Object)null && val.m_itemData.m_shared.m_icons != null && val.m_itemData.m_shared.m_icons.Length != 0) { return _icon = val.m_itemData.m_shared.m_icons[0]; } } GameObject itemPrefab2 = instance.GetItemPrefab("Coins"); object obj; if (!((Object)(object)itemPrefab2 != (Object)null)) { obj = null; } else { ItemDrop component = itemPrefab2.GetComponent<ItemDrop>(); obj = ((component != null) ? component.m_itemData.GetIcon() : null); } Sprite result = (Sprite)obj; _icon = (Sprite)obj; return result; } } internal static class Services { private sealed class BlessingDef { public string Name; public string Description; public float Seconds; public Action<SE_Stats> Setup; public Service Service; } private const string BlessingKey = "anwo_ws_blessing"; internal const int Turns = 3; private static readonly Dictionary<string, SE_Stats> s_effects = new Dictionary<string, SE_Stats>(); private static List<Service> s_haldor; private static List<Service> s_hildir; private static List<Service> s_witch; private static readonly Dictionary<string, BlessingDef> s_blessingSetup = new Dictionary<string, BlessingDef>(); private static Player s_restoredFor; private static readonly string[][] Bosses = new string[8][] { new string[4] { "defeated_eikthyr", "$enemy_eikthyr", "Eikthyrnir", "$enemy_eikthyr" }, new string[4] { "defeated_gdking", "$enemy_gdking", "GDKing", "$enemy_gdking" }, new string[4] { "defeated_bonemass", "$enemy_bonemass", "Bonemass", "$enemy_bonemass" }, new string[4] { "defeated_dragon", "$enemy_dragon", "Dragonqueen", "$enemy_dragon" }, new string[4] { "defeated_goblinking", "$enemy_goblinking", "GoblinKing", "$enemy_goblinking" }, new string[4] { "defeated_queen", "$enemy_seekerqueen", "Mistlands_DvergrBossEntrance1", "$enemy_seekerqueen" }, new string[4] { "defeated_fader", "$enemy_fader", "FaderLocation", "$enemy_fader_codename" }, new string[4] { "defeated_frozenking_p3", "$enemy_frozenking", "DN_Bossroom", "$hud_pin_dnboss" } }; private static readonly FieldInfo PinsField = AccessTools.Field(typeof(Minimap), "m_pins"); internal static List<Service> For(string trader) { Build(); return (trader ?? "").ToLowerInvariant() switch { "haldor" => s_haldor, "hildir" => s_hildir, "bogwitch" => s_witch, _ => new List<Service>(), }; } private static void Build() { if (s_haldor == null) { List<Service> list = new List<Service>(); list.Add(Blessing("mule", "Pack mule's luck", "Carry 150 more for an hour. Haldor knows a trick or two about packing.", 80, new string[1] { "BeltStrength" }, 3600f, delegate(SE_Stats se) { se.m_addMaxCarryWeight = 150f; })); list.Add(new Service { Id = "mend", Name = "Mend your gear", BasePrice = 25, Icons = new string[1] { "Hammer" }, Description = "Haldor repairs everything you carry and wear, anywhere, without a workbench.", Unavailable = (Player p) => (Worn(p).Count != 0) ? null : "Nothing needs mending", Apply = Mend }); list.Add(new Service { Id = "map", Name = "A traveller's map", BasePrice = 50, Icons = new string[1] { "Feathers" }, Description = "Haldor sketches any part of the world you point at: after paying, your map opens; click a spot and the land " + 500 + " m around it is uncovered, wherever it is. A sketch you don't use right away waits for your next click on the map.", Apply = Map }); s_haldor = list; list = new List<Service>(); list.Add(Blessing("lightstep", "Light step", "Running and jumping cost a third less stamina for 40 minutes. Hildir's boots are made for walking.", 60, new string[2] { "MeadStaminaMedium", "MeadStaminaMinor" }, 2400f, delegate(SE_Stats se) { se.m_runStaminaDrainModifier = -0.33f; se.m_jumpStaminaUseModifier = -0.33f; })); list.Add(Blessing("warm", "Warm lining", "Resistant to frost for 40 minutes: you don't freeze in the mountains or the north, and frost hurts less.", 80, new string[2] { "CapeWolf", "WolfPelt" }, 2400f, delegate(SE_Stats se) { //IL_0009: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) se.m_mods = new List<DamageModPair> {