From a5542f68f81555209f730c91c4bd4902342a5644 Mon Sep 17 00:00:00 2001 From: Khloe Leclair Date: Wed, 16 Mar 2022 00:57:41 -0400 Subject: [PATCH 01/39] Squashed a bunch of the commits transferred from the main branch --- Almanac/Managers/NoticesManager.cs | 11 + BCBuildings/BPRecipe.cs | 156 ++++++++++++++ BCBuildings/IBCBuildingsApi.cs | 71 +++++++ BCBuildings/ModApi.cs | 64 ++++++ BCBuildings/Properties/launchSettings.json | 8 + .../DynamicGameAssets/DGAIntegration.cs | 38 ++++ .../IDynamicGameAssetsApi.cs | 60 ++++++ .../RaisedGardenBeds/GardenPotRecipe.cs | 4 +- .../Integrations/SpaceCore/DGAIngredient.cs | 197 ++++++++++++++++++ BetterCrafting/Patches/FarmHousePatches.cs | 80 +++++++ .../Properties/launchSettings.json | 8 + 11 files changed, 694 insertions(+), 3 deletions(-) create mode 100644 BCBuildings/BPRecipe.cs create mode 100644 BCBuildings/IBCBuildingsApi.cs create mode 100644 BCBuildings/ModApi.cs create mode 100644 BCBuildings/Properties/launchSettings.json create mode 100644 BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs create mode 100644 BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs create mode 100644 BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs create mode 100644 BetterCrafting/Patches/FarmHousePatches.cs create mode 100644 GiantCropTweaks/Properties/launchSettings.json diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index 68e0c42..a44eb5f 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -102,6 +102,17 @@ private Dictionary LoadEvents() { } } + return events; + } + + foreach (var entry in data) { + entry.Value.Translation = cp.Translation; + entry.Value.ModContent = cp.ModContent; + if (!events.ContainsKey(entry.Key)) + events[entry.Key] = entry.Value; + } + } + return events; } diff --git a/BCBuildings/BPRecipe.cs b/BCBuildings/BPRecipe.cs new file mode 100644 index 0000000..fde7501 --- /dev/null +++ b/BCBuildings/BPRecipe.cs @@ -0,0 +1,156 @@ +#nullable enable + +using System.Collections.Generic; + +using Leclair.Stardew.Common; +using Leclair.Stardew.BetterCrafting; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +using StardewValley; +using StardewValley.Locations; + +namespace Leclair.Stardew.BCBuildings; + +public class BPRecipe : IRecipe { + + public readonly ModEntry Mod; + public readonly BluePrint Blueprint; + + public readonly string? RawQuery; + public readonly GameStateQuery.ParsedQuery? Query; + + public BPRecipe(BluePrint blueprint, ModEntry mod) { + Mod = mod; + Name = $"blueprint:{blueprint.name}"; + Blueprint = blueprint; + + if (mod.ApiBlueprints.TryGetValue(blueprint.name, out var data)) + RawQuery = data.Item3; + + if (!string.IsNullOrEmpty(RawQuery)) + Query = GameStateQuery.ParseConditions(RawQuery); + + List ingredients = new(); + int amount; + + if (blueprint.itemsRequired != null) + foreach (var entry in blueprint.itemsRequired) { + amount = (int) (entry.Value * (mod.Config.CostMaterial / 100.0)); + if ( amount > 0 ) + ingredients.Add(mod.BCAPI!.CreateBaseIngredient(entry.Key, amount)); + } + + amount = (int) (blueprint.moneyRequired * (mod.Config.CostCurrency / 100.0)); + if (amount > 0) + ingredients.Add(mod.BCAPI!.CreateCurrencyIngredient(CurrencyType.Money, amount)); + + IEnumerable? additional = Mod.GetAdditionalCost(); + if (additional is not null) + ingredients.AddRange(additional); + + Ingredients = ingredients.ToArray(); + + if (mod.BuildingSources.TryGetValue(blueprint.name, out Rectangle? source)) { + SourceRectangle = (source.HasValue && !source.Value.IsEmpty) ? source.Value : Blueprint.texture.Bounds; + } else + SourceRectangle = Blueprint.sourceRectForMenuView; + } + + // Identity + + public int SortValue { get; } + public string Name { get; } + public string DisplayName => Blueprint.displayName; + public string Description => Blueprint.description; + + public virtual bool HasRecipe(Farmer who) { + return true; + } + + public virtual int GetTimesCrafted(Farmer who) { + return 0; + } + + public CraftingRecipe? CraftingRecipe => null; + + // Display + + public Texture2D Texture => Blueprint.texture; + public Rectangle SourceRectangle { get; } + + public int GridHeight { + get { + Rectangle rect = SourceRectangle; + if (rect.Height > rect.Width) + return 2; + return 1; + } + } + + public int GridWidth { + get { + Rectangle rect = SourceRectangle; + if (rect.Width > rect.Height) + return 2; + return 1; + } + } + + // Cost + + public int QuantityPerCraft => 1; + public IIngredient[] Ingredients { get; } + + // Creation + + public bool Stackable => false; + + public Item? CreateItem() { + return null; + } + + public bool CanCraft(Farmer who) { + if (who.currentLocation is not BuildableGameLocation bgl) + return false; + + if (Blueprint.isUpgrade()) { + return bgl.isBuildingConstructed(Blueprint.nameOfBuildingToUpgrade); + } + + if (Query.HasValue && !Query.Value.Evaluate()) + return false; + + return true; + } + + public string? GetTooltipExtra(Farmer who) { + if (who.currentLocation is not BuildableGameLocation bgl) + return I18n.Error_NotBuildable(); + + if (Blueprint.isUpgrade() && !bgl.isBuildingConstructed(Blueprint.nameOfBuildingToUpgrade)) { + string other = new BluePrint(Blueprint.nameOfBuildingToUpgrade).displayName; + return I18n.Error_CantUpgrade(other); + } + + if (Query.HasValue && !Query.Value.Evaluate()) + return I18n.Error_UnmetCondition(); + + return null; + } + + public void PerformCraft(IPerformCraftEvent evt) { + + var menu = new BuildMenu(Blueprint, Blueprint.isUpgrade() ? ActionType.Upgrade : ActionType.Build, evt, Mod); + var old_menu = Game1.activeClickableMenu; + + Game1.activeClickableMenu = menu; + + menu.exitFunction = () => { + Game1.activeClickableMenu = old_menu; + evt.Cancel(); + }; + } + +} diff --git a/BCBuildings/IBCBuildingsApi.cs b/BCBuildings/IBCBuildingsApi.cs new file mode 100644 index 0000000..df8ee53 --- /dev/null +++ b/BCBuildings/IBCBuildingsApi.cs @@ -0,0 +1,71 @@ +using Microsoft.Xna.Framework; + +using System; +using System.Collections.Generic; + +using StardewValley; + +namespace Leclair.Stardew.BCBuildings; + +public interface IBlueprintPopulationEventArgs { + Dictionary Blueprints { get; } +} + +public interface IBCBuildingsApi { + + /// + /// Add a blueprint to the BC: Buildings menu, by name. + /// + /// The name of the blueprint to add + /// An optional game state query to control + /// visibility of the blueprint in the menu. + /// + /// An optional game state query to control + /// whether or not players can construct a blueprint. If the condition is + /// set and doesn't pass, the building will be grayed out and unavailable + /// to construct, with a message displayed to the user that a condition + /// has not been fulfilled. + void AddBlueprint(string name, string? displayCondition, string? buildCondition); + + /// + /// Add a blueprint to the BC: Buildings menu. + /// + /// The blueprint to add + /// An optional game state query to control + /// visibility of the blueprint in the menu. + /// + /// An optional game state query to control + /// whether or not players can construct a blueprint. If the condition is + /// set and doesn't pass, the building will be grayed out and unavailable + /// to construct, with a message displayed to the user that a condition + /// has not been fulfilled. + void AddBlueprint(BluePrint blueprint, string? displayCondition, string? buildCondition); + + /// + /// Remove a blueprint from the BC: Buildings menu, by name. + /// + /// The name of the blueprint to remove + void RemoveBlueprint(string name); + + /// + /// Remove a blueprint from the BC: Buildings menu. + /// + /// The blueprint to remove + void RemoveBlueprint(BluePrint blueprint); + + /// + /// Override the source rectangle used for drawing the building in the + /// BC: Buildings menu. + /// + /// The name of the blueprint to change + /// The rectangle of the texture to use + void SetTextureSourceRect(string name, Rectangle? rect); + + /// + /// This event is emitted when discovering blueprints for display in the + /// menu. Additional logic to control the visibility of a blueprint can + /// be performed here, assuming game state queries aren't enough. + /// + event EventHandler? OnBlueprintPopulation; + +} diff --git a/BCBuildings/ModApi.cs b/BCBuildings/ModApi.cs new file mode 100644 index 0000000..680596b --- /dev/null +++ b/BCBuildings/ModApi.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Xna.Framework; + +using StardewValley; + +namespace Leclair.Stardew.BCBuildings; + +public class BlueprintPopulationEventArgs: EventArgs, IBlueprintPopulationEventArgs { + + public Dictionary Blueprints { get; } + + internal BlueprintPopulationEventArgs(Dictionary blueprints) { + Blueprints = blueprints; + } +} + +public class ModApi : IBCBuildingsApi { + + private readonly ModEntry Mod; + + internal ModApi(ModEntry mod) { + Mod = mod; + } + + public void AddBlueprint(string name, string? displayCondition, string? buildCondition) { + AddBlueprint(new BluePrint(name), displayCondition, buildCondition); + } + + public void AddBlueprint(BluePrint blueprint, string? displayCondition, string? buildCondition) { + Mod.ApiBlueprints[blueprint.name] = (blueprint, displayCondition, buildCondition); + } + + public void RemoveBlueprint(string name) { + if (Mod.ApiBlueprints.ContainsKey(name)) + Mod.ApiBlueprints.Remove(name); + } + + public void RemoveBlueprint(BluePrint blueprint) { + RemoveBlueprint(blueprint.name); + } + + public void SetTextureSourceRect(string name, Rectangle? rect) { + if (string.IsNullOrEmpty(name)) + return; + + if (rect.HasValue) + Mod.BuildingSources[name] = rect; + else if (Mod.BuildingSources.ContainsKey(name)) + Mod.BuildingSources.Remove(name); + } + + public event EventHandler? OnBlueprintPopulation; + + internal void EmitBlueprintPopulation(Dictionary blueprints) { + if (OnBlueprintPopulation is null) + return; + + var evt = new BlueprintPopulationEventArgs(blueprints); + OnBlueprintPopulation.Invoke(this, evt); + } + +} diff --git a/BCBuildings/Properties/launchSettings.json b/BCBuildings/Properties/launchSettings.json new file mode 100644 index 0000000..1321e0f --- /dev/null +++ b/BCBuildings/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "BCBuildings": { + "commandName": "Project", + "commandLineArgs": "--mods-path \"$(ModSet)\"" + } + } +} \ No newline at end of file diff --git a/BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs b/BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs new file mode 100644 index 0000000..82f7555 --- /dev/null +++ b/BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Leclair.Stardew.Common.Integrations; + +using DynamicGameAssets; +using StardewValley; + +namespace Leclair.Stardew.BetterCrafting.Integrations.DynamicGameAssets; + +public class DGAIntegration : BaseAPIIntegration { + + public DGAIntegration(ModEntry mod) + : base(mod, "spacechase0.DynamicGameAssets", "1.4.4") { + + } + + public IEnumerable GetAllItems() { + if (!IsLoaded) + yield break; + + foreach(string id in API.GetAllItems()) { + Item? item = null; + try { + item = API.SpawnDGAItem(id) as Item; + } catch { + /* no-op */ + } + + if (item is not null) + yield return item; + } + } + +} diff --git a/BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs b/BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs new file mode 100644 index 0000000..b02e041 --- /dev/null +++ b/BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs @@ -0,0 +1,60 @@ +using Microsoft.Xna.Framework; + +using StardewModdingAPI; + +namespace DynamicGameAssets { + public interface IDynamicGameAssetsApi { + + /// + /// Get the DGA item ID of this item, if it has one. + /// + /// The item to get the DGA item ID of. + /// The DGA item ID if it has one, otherwise null. + string GetDGAItemId(object item); + + /// + /// Spawn a DGA item, referenced with its full ID ("mod.id/ItemId"). + /// Some items, such as crafting recipes or crops, don't have an item representation. + /// + /// The full ID of the item to spawn. + /// The color of the item. + /// + object SpawnDGAItem(string fullId, Color? color); + + /// + /// Spawn a DGA item, referenced with its full ID ("mod.id/ItemId"). + /// Some items, such as crafting recipes or crops, don't have an item representation. + /// + /// The full ID of the item to spawn. + /// + object SpawnDGAItem(string fullId); + + /// + /// Gets the names of all installed packs. + /// + /// Array of all pack names. + string[] ListContentPacks(); + + /// + /// Gets all items provided by a pack. + /// + /// The name of the pack. + /// Namespaced item names. + string[]? GetItemsByPack(string packname); + + /// + /// Gets all the items (namespaced names) + /// + /// A list of all items. + string[] GetAllItems(); + + /// + /// Register a DGA pack embedded in another mod. + /// Needs the standard DGA fields in the manifest. (See documentation.) + /// Probably shouldn't use config-schema.json for these, because if you do it will overwrite your mod's config.json. + /// + /// The mod manifest. + /// The absolute path to the directory of the pack. + void AddEmbeddedPack(IManifest manifest, string dir); + } +} diff --git a/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs b/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs index 6a1552f..37a781e 100644 --- a/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs +++ b/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs @@ -39,7 +39,7 @@ public GardenPotRecipe(RGBIntegration rgb, CraftingRecipe recipe) { // Identity - public string SortValue => Recipe.itemToProduce[0]; + public int SortValue => Recipe.itemToProduce[0]; // Display @@ -47,8 +47,6 @@ public GardenPotRecipe(RGBIntegration rgb, CraftingRecipe recipe) { public string DisplayName { get; } public string Description { get; } - public bool AllowRecycling => true; - public virtual bool HasRecipe(Farmer who) { return who.craftingRecipes.ContainsKey(Name); } diff --git a/BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs b/BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs new file mode 100644 index 0000000..134e8d8 --- /dev/null +++ b/BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +using HarmonyLib; + +using Leclair.Stardew.Common; +using Leclair.Stardew.Common.Crafting; +using Leclair.Stardew.Common.Inventory; +using Leclair.Stardew.Common.Types; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +using StardewModdingAPI; + +using StardewValley; + +namespace Leclair.Stardew.BetterCrafting.Integrations.SpaceCore; + +public class DGAIngredient : IOptimizedIngredient, IRecyclable { + + private readonly ModEntry Mod; + public readonly object Ingredient; + public readonly IItemAbstraction? ItemAbstraction; + + public readonly string? IngredientType; + + public readonly Func ItemMatcher; + + private readonly IReflectedProperty DisplayNameProp; + private readonly IReflectedProperty TextureProp; + private readonly IReflectedProperty SourceProp; + private readonly IReflectedProperty QuantityProp; + private readonly IReflectedMethod MatchesMethod; + + public DGAIngredient(object ingredient, ModEntry mod, IItemAbstraction? itemAbstraction) { + Mod = mod; + Ingredient = ingredient; + ItemAbstraction = itemAbstraction; + + DisplayNameProp = Mod.Helper.Reflection.GetProperty(Ingredient, "DispayName"); + TextureProp = Mod.Helper.Reflection.GetProperty(Ingredient, "IconTexture"); + SourceProp = Mod.Helper.Reflection.GetProperty(Ingredient, "IconSubrect"); + QuantityProp = Mod.Helper.Reflection.GetProperty(Ingredient, "Quantity"); + + MatchesMethod = Mod.Helper.Reflection.GetMethod(Ingredient, "ItemMatches"); + + ItemMatcher = item => MatchesMethod.Invoke(item); + + // Ensure we can do stuff. + DisplayNameProp.GetValue(); + TextureProp.GetValue(); + SourceProp.GetValue(); + QuantityProp.GetValue(); + + RecycledSprite = new(item => SpriteHelper.GetSprite(item), () => RecycledItem?.Item2); + + // Try to get the matcher type. + try { + object? abstraction = mod.Helper.Reflection.GetField(Ingredient, "ingred", true).GetValue(); + if (abstraction is not null) { + var prop = AccessTools.Property(abstraction.GetType(), "Type"); + IngredientType = prop.GetValue(abstraction)?.ToString(); + } else + IngredientType = null; + } catch { + IngredientType = null; + } + } + + #region IRecyclable + + private Tuple? RecycledItem; + private readonly Cache RecycledSprite; + + [MemberNotNull(nameof(RecycledItem))] + private void LoadRecycledItem() { + if (RecycledItem is not null) + return; + + // TODO: Make this better. + switch(IngredientType) { + case "DGAItem": + case "VanillaObject": + case "VanillaObjectColored": + case "VanillaBigCraftable": + case "VanillaWeapon": + case "VanillaHat": + case "VanillaClothing": + case "VanillaBoots": + case "VanillaFurniture": + // TODO: Check for vanilla categories? + RecycledItem = new(false, ItemAbstraction?.Create()); + return; + + case "DGARecipe": + // Recipes are not supported. + RecycledItem = new(false, null); + return; + } + + // If we got here, we're something odd. But probably just a context + // tag match. Either way, we're fuzzy. + + Item? result = null; + int price = 0; + int count = 0; + + foreach (Item item in ModEntry.Instance.GetMatchingItems(ItemMatcher)) { + int ip = item.salePrice(); + count++; + if (result is null || ip < price) { + result = item; + price = ip; + } + } + + ModEntry.Instance.Log($"Item matches for \"{DisplayName}\": {count} -- Using: {result?.Name}", LogLevel.Trace); + + RecycledItem = new(true, result); + } + + public Texture2D GetRecycleTexture(Farmer who, Item? recycledItem, bool fuzzyItems) { + LoadRecycledItem(); + if (!fuzzyItems && RecycledItem.Item1) + return Texture; + return RecycledSprite.Value?.Texture ?? Texture; + } + + public Rectangle GetRecycleSourceRect(Farmer who, Item? recycledItem, bool fuzzyItems) { + LoadRecycledItem(); + if (!fuzzyItems && RecycledItem.Item1) + return SourceRectangle; + return RecycledSprite.Value?.BaseSource ?? SourceRectangle; + } + + public string GetRecycleDisplayName(Farmer who, Item? recycledItem, bool fuzzyItems) { + LoadRecycledItem(); + if (!fuzzyItems && RecycledItem.Item1) + return DisplayName; + return RecycledItem.Item2?.DisplayName ?? DisplayName; + } + + public int GetRecycleQuantity(Farmer who, Item? recycledItem, bool fuzzyItems) { + return Quantity; + } + + public bool CanRecycle(Farmer who, Item? recycledItem, bool fuzzyItems) { + LoadRecycledItem(); + if (!fuzzyItems && RecycledItem.Item1) + return false; + return RecycledItem.Item2 is not null; + } + + public IEnumerable? Recycle(Farmer who, Item? recycledItem, bool fuzzyItems) { + LoadRecycledItem(); + if (!fuzzyItems && RecycledItem.Item1) + return null; + + if (RecycledItem.Item2 is not null) + return IRecyclable.GetManyOf(RecycledItem.Item2, Quantity); + + return null; + } + + #endregion + + #region IIngredient + + public bool SupportsQuality => true; + + public string DisplayName => DisplayNameProp.GetValue(); + + public Texture2D Texture => TextureProp.GetValue(); + + public Rectangle SourceRectangle => SourceProp.GetValue() ?? Texture.Bounds; + + public int Quantity => QuantityProp.GetValue(); + + public int GetAvailableQuantity(Farmer who, IList? items, IList? inventories, int maxQuality) { + return InventoryHelper.CountItem(ItemMatcher, who, items, out bool _, max_quality: maxQuality); + } + + public bool HasAvailableQuantity(int quantity, Farmer who, IList? items, IList? inventories, int maxQuality) { + return InventoryHelper.CountItem(ItemMatcher, who, items, out bool _, max_quality: maxQuality, limit: quantity) >= quantity; + } + + public void Consume(Farmer who, IList? inventories, int maxQuality, bool lowQualityFirst) { + InventoryHelper.ConsumeItems(new (Func, int)[] { + (ItemMatcher, Quantity) + }, who, inventories, maxQuality, lowQualityFirst); + } + + #endregion + +} diff --git a/BetterCrafting/Patches/FarmHousePatches.cs b/BetterCrafting/Patches/FarmHousePatches.cs new file mode 100644 index 0000000..641538e --- /dev/null +++ b/BetterCrafting/Patches/FarmHousePatches.cs @@ -0,0 +1,80 @@ +#nullable enable + +using System; +using System.Collections.Generic; + +using HarmonyLib; + +using Microsoft.Xna.Framework; + +using StardewModdingAPI; + +using StardewValley; +using StardewValley.Locations; +using StardewValley.Objects; + +using xTile.Dimensions; + +namespace Leclair.Stardew.BetterCrafting.Patches; + +public static class GameLocation_Patches { + + private static IMonitor? Monitor; + + public static void Patch(ModEntry mod) { + Monitor = mod.Monitor; + + try { + mod.Harmony!.Patch( + original: AccessTools.Method(typeof(FarmHouse), nameof(FarmHouse.performAction)), + prefix: new HarmonyMethod(typeof(GameLocation_Patches), nameof(performAction_Prefix)) + ); + + mod.Harmony!.Patch( + original: AccessTools.Method(typeof(FarmHouse), nameof(IslandFarmHouse.performAction)), + prefix: new HarmonyMethod(typeof(GameLocation_Patches), nameof(performAction_Prefix)) + ); + + mod.Harmony!.Patch( + original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.performAction)), + prefix: new HarmonyMethod(typeof(GameLocation_Patches), nameof(performAction_Prefix)) + ); + + } catch (Exception ex) { + mod.Log("An error occurred while registering a harmony patch for the FarmHouse.", LogLevel.Warn, ex); + } + } + + public static bool performAction_Prefix(GameLocation __instance, string action, Farmer who, Location tileLocation, ref bool __result) { + try { + if (action is not null && who.IsLocalPlayer && action.Split(' ')[0] == "kitchen") { + // It's the kitchen! Handle it ourselves. + ModEntry mod = ModEntry.Instance; + if (mod.Config.ReplaceCrafting && !(mod.Config.SuppressBC?.IsDown() ?? false)) { + + // TODO: Build containers list! + + Game1.activeClickableMenu = Menus.BetterCraftingPage.Open( + mod, + location: __instance, + position: new Vector2(tileLocation.X, tileLocation.Y), + standalone_menu: true, + cooking: true, + material_containers: (IList?) null + ); + + __result = true; + return false; + } + } + + } catch(Exception ex) { + Monitor?.Log($"An error occurred while attempting to perform a location action.", LogLevel.Warn); + Monitor?.Log($"Details:\n{ex}", LogLevel.Warn); + } + + return true; + } + + +} diff --git a/GiantCropTweaks/Properties/launchSettings.json b/GiantCropTweaks/Properties/launchSettings.json new file mode 100644 index 0000000..91134e3 --- /dev/null +++ b/GiantCropTweaks/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "GiantCropTweaks": { + "commandName": "Project", + "commandLineArgs": "--mods-path \"$(ModSet)\"" + } + } +} \ No newline at end of file From 7bbe90f8648786c68ab8b66794d3aa63bba7dfbc Mon Sep 17 00:00:00 2001 From: Khloe Leclair Date: Sat, 2 Apr 2022 16:47:20 -0400 Subject: [PATCH 02/39] 1.6 dev stuff --- Almanac/Almanac.csproj | 7 +- Almanac/AssetManager.cs | 87 +++++- Almanac/Crops/VanillaProvider.cs | 123 +++----- Almanac/Fish/FishManager.cs | 36 +-- Almanac/Fish/FishingPage.cs | 28 +- Almanac/Fish/VanillaProvider.cs | 30 +- Almanac/FishHelper.cs | 167 +++++----- Almanac/Managers/NoticesManager.cs | 12 +- Almanac/Managers/WeatherManager.cs | 292 ++++++++++-------- Almanac/Menus/BookMenu.cs | 74 +++++ Almanac/ModEntry.cs | 52 ++-- Almanac/Models/WeatherRule.cs | 46 +-- Almanac/Pages/DebugItemsPage.cs | 188 +++++++++++ Almanac/Pages/WeatherPage.cs | 43 ++- Almanac/Patches/Game1_Patches.cs | 42 +++ Almanac/Patches/GameStateQuery_Patches.cs | 44 +++ Almanac/WeatherHelper.cs | 231 ++++++++------ Almanac/assets/weather_rules.json | 5 +- .../Serialization/Converters/ItemConverter.cs | 53 ++++ SeeMeRollin/ModConfig.cs | 2 +- SeeMeRollin/Patches/BuffPatches.cs | 42 +++ SeeMeRollin/RollinBuff.cs | 20 ++ 22 files changed, 1106 insertions(+), 518 deletions(-) create mode 100644 Almanac/Menus/BookMenu.cs create mode 100644 Almanac/Pages/DebugItemsPage.cs create mode 100644 Almanac/Patches/Game1_Patches.cs create mode 100644 Almanac/Patches/GameStateQuery_Patches.cs create mode 100644 Common/Serialization/Converters/ItemConverter.cs create mode 100644 SeeMeRollin/Patches/BuffPatches.cs create mode 100644 SeeMeRollin/RollinBuff.cs diff --git a/Almanac/Almanac.csproj b/Almanac/Almanac.csproj index 46c5492..3f53211 100644 --- a/Almanac/Almanac.csproj +++ b/Almanac/Almanac.csproj @@ -4,11 +4,16 @@ Almanac 0.18.1 Leclair.Stardew.Almanac - true true + net6.0 + + + + + diff --git a/Almanac/AssetManager.cs b/Almanac/AssetManager.cs index b8e7ea5..e440e23 100644 --- a/Almanac/AssetManager.cs +++ b/Almanac/AssetManager.cs @@ -34,13 +34,12 @@ public class AssetManager { public AssetManager(ModEntry mod) { Mod = mod; - Mod.Helper.Events.Content.AssetRequested += OnAssetRequested; } public void Invalidate() { Loaded = false; - Mod.Helper.GameContent.InvalidateCache(asset => asset.Name.StartsWith(EventPath)); + Mod.Helper.Content.InvalidateCache(asset => asset.AssetName.StartsWith(EventPath)); } private void OnAssetRequested(object? sender, AssetRequestedEventArgs e) { @@ -49,40 +48,103 @@ private void OnAssetRequested(object? sender, AssetRequestedEventArgs e) { Load(e.Name.LocaleCode); if (ModEvents == null) return; - string[] bits = PathUtilities.GetSegments(e.Name.BaseName); string end = bits[^1]; - if (!ModEvents.TryGetValue(end, out var events) || events == null) return; - e.Edit(data => { var editor = data.AsDictionary(); foreach (var entry in events) editor.Data[entry.Key] = entry.Localize(Mod.Helper.Translation); }); - return; } - if (e.Name.IsEquivalentTo(CropOverridesPath)) e.LoadFrom( () => new Dictionary(), priority: AssetLoadPriority.Low ); - if (e.Name.IsEquivalentTo(FishOverridesPath)) e.LoadFrom( () => new Dictionary(), priority: AssetLoadPriority.Low ); } + #region IAssetLoader + public bool CanLoad(IAssetInfo asset) + { + return + asset.Name.IsEquivalentTo(CropOverridesPath) || + asset.Name.IsEquivalentTo(FishOverridesPath) || + asset.Name.IsEquivalentTo(LocalNoticesPath) || + asset.Name.IsEquivalentTo(NPCOverridesPath); + } + public T Load(IAssetInfo asset) + { + if (asset.Name.IsEquivalentTo(CropOverridesPath)) + { + var data = new Dictionary(); + return (T)(object)data; + } + if (asset.Name.IsEquivalentTo(FishOverridesPath)) + { + var data = new Dictionary(); + return (T)(object)data; + } + if (asset.Name.IsEquivalentTo(LocalNoticesPath)) + { + var data = new Dictionary(); + return (T)(object)data; + } + if (asset.Name.IsEquivalentTo(NPCOverridesPath)) + { + var data = new Dictionary(); + return (T)(object)data; + } + return (T)(object)null; + } + + #endregion + + #region IAssetEditor + + public bool CanEdit(IAssetInfo asset) + { + if (asset.Name.StartsWith(EventPath)) + { + Load(asset.Locale); + if (ModEvents == null) + return false; - private void Load(string? locale) { - if (Loaded && Locale == locale) + string[] bits = PathUtilities.GetSegments(asset.Name.BaseName); + string end = bits[^1]; + if (ModEvents.ContainsKey(end)) + return true; + } + return false; + } + + public void Edit(IAssetData asset) { + if (!asset.Name.StartsWith(EventPath)) return; - - try { + Load(asset.Locale); + if (ModEvents == null) + return; + string[] bits = PathUtilities.GetSegments(asset.Name.BaseName); + string end = bits[^1]; + if (!ModEvents.TryGetValue(end, out var events) || events == null) + return; + var editor = asset.AsDictionary(); + foreach (var entry in events) + editor.Data[entry.Key] = entry.Localize(Mod.Helper.Translation); + } + + #endregion +private void Load(string? locale) +{ + if (Loaded && Locale == locale) + return; + try { ModEvents = Mod.Helper.ModContent.LoadLocalized>>("assets/events.json"); } catch (Exception ex) { Mod.Log("Unable to load custom mod events.", ex: ex); @@ -91,7 +153,6 @@ private void Load(string? locale) { Loaded = true; Locale = locale; - } } public struct EventData { diff --git a/Almanac/Crops/VanillaProvider.cs b/Almanac/Crops/VanillaProvider.cs index d17d7f0..936b7da 100644 --- a/Almanac/Crops/VanillaProvider.cs +++ b/Almanac/Crops/VanillaProvider.cs @@ -10,6 +10,8 @@ using Microsoft.Xna.Framework.Graphics; using StardewValley; +using StardewValley.GameData; + using StardewModdingAPI; namespace Leclair.Stardew.Almanac.Crops; @@ -26,9 +28,9 @@ public VanillaProvider(ModEntry mod) { public int Priority => 0; - public IEnumerable GetCrops() { - Dictionary crops = Game1.content.Load>(@"Data\Crops"); - List result = new(); + public IEnumerable GetCrops() { + Dictionary crops = Game1.content.Load>(@"Data\Crops"); + List result = new(); foreach (var entry in crops) { try { @@ -43,7 +45,7 @@ public IEnumerable GetCrops() { return result; } - private CropInfo? GetCropInfo(int id, string data) { + private CropInfo? GetCropInfo(string id, string data) { // TODO: Create Crop instances so we can // lean more on vanilla logic. @@ -52,14 +54,14 @@ public IEnumerable GetCrops() { if (bits.Length < 5) return null; - int sprite = Convert.ToInt32(bits[2]); - int harvest = Convert.ToInt32(bits[3]); - int regrow = Convert.ToInt32(bits[4]); + int sprite = Convert.ToInt32(bits[2]); + string harvestID = bits[3]; + int regrow = Convert.ToInt32(bits[4]); bool isTrellisCrop = bits.Length > 7 && bits[7].Equals("true"); - if (!Game1.objectInformation.ContainsKey(harvest)) - return null; + if (!Game1.objectInformation.ContainsKey(harvestID)) + return null; string[] seasons = bits[1].Split(' ', StringSplitOptions.RemoveEmptyEntries); @@ -81,7 +83,7 @@ public IEnumerable GetCrops() { test = end.Season; } catch (Exception) { - ModEntry.Instance.Log($"Invalid season for crop {id} (harvest:{harvest}): {season}", LogLevel.Warn); + ModEntry.Instance.Log($"Invalid season for crop {id} (harvest:{harvestID}): {season}", LogLevel.Warn); return null; } @@ -101,15 +103,20 @@ public IEnumerable GetCrops() { if (startDate.SeasonIndex == 0 && startDate.DayOfMonth == 1 && endDate.SeasonIndex == (WorldDate.MonthsPerYear - 1) && endDate.DayOfMonth == ModEntry.DaysPerMonth) return null; - // If the sprite is 23, it's a seasonal multi-seed - // so we want to show that rather than the seed. - Item item = new SObject(sprite == 23 ? id : harvest, 1).getOne(); + // If the sprite is 23, it's a seasonal multi-seed + // so we want to show that rather than the seed. + Item item; + if (sprite == 23) + item = Utility.CreateItemByID(id, 1); + else + item = Utility.CreateItemByID(harvestID, 1); // Phases int[] phases = bits[0].Split(' ').Select(val => Convert.ToInt32(val)).ToArray(); - // Stupid hard coded bullshit. - bool paddyCrop = harvest == 271 || harvest == 830; + // Stupid hard coded bullshit. + // TODO: Wait for the new context tag / data bit and check that. + bool paddyCrop = harvestID == "271" || harvestID == "830"; // Phase Sprites // We need an extra sprite for the finished crop, @@ -151,67 +158,39 @@ public IEnumerable GetCrops() { bool isGiantCrop = false; SpriteInfo? giantSprite = null; - // Vanilla Giant Crops - if (harvest == 190 || harvest == 254 || harvest == 276) { - isGiantCrop = true; - - int which; - if (harvest == 190) - which = 0; - else if (harvest == 254) - which = 1; - else - which = 2; - - giantSprite = new( - Game1.cropSpriteSheet, - new Rectangle(112 + which*48, 512, 48, 64) - ); - } - - // Giant Crop Tweaks Crops - if (!isGiantCrop && Mod.intGCT != null && Mod.intGCT.IsGiantCrop(harvest)) { - Texture2D? tex = Mod.intGCT.GetGiantCropTexture(harvest); - if (tex != null) { + // Giant Crops + var giantCrops = Game1.content.Load>(@"Data\GiantCrops"); + if (giantCrops != null && giantCrops.TryGetValue(harvestID, out var gcdata) && gcdata != null) { isGiantCrop = true; - giantSprite = new(tex, Mod.intGCT.GetGiantCropSource(harvest) ?? tex.Bounds); - } - } - // JsonAssets Giant Crops - if (!isGiantCrop && Mod.intJA != null && Mod.intJA.IsLoaded) { - Texture2D? tex = Mod.intJA.GetGiantCropTexture(harvest); - if (tex != null) { - isGiantCrop = true; - giantSprite = new(tex, tex.Bounds); + giantSprite = new( + Game1.content.Load(gcdata.Texture), + new Rectangle( + gcdata.Corner.X, gcdata.Corner.Y, + gcdata.TileSize.X * 16, + (gcdata.TileSize.Y + 1) * 16 + ) + ); } - } - // MoreGiantCrops Giant Crops - if (!isGiantCrop && Mod.intMGC != null && Mod.intMGC.IsGiantCrop(harvest)) { - isGiantCrop = true; - Texture2D? tex = Mod.intMGC.GetGiantCropTexture(harvest); - if (tex != null) - giantSprite = new(tex, tex.Bounds); + return new( + Convert.ToString(id), + item, + item.DisplayName, + SpriteHelper.GetSprite(item), + isGiantCrop, + giantSprite, + new Item[] { + new SObject(id, 1) + }, + isTrellisCrop, + phases, + regrow, + paddyCrop, + sprites, + startDate, + endDate + ); } - - return new( - Id: Convert.ToString(id), - Item: item, - Name: item.DisplayName, - Sprite: SpriteHelper.GetSprite(item), - IsTrellisCrop: isTrellisCrop, - IsGiantCrop: isGiantCrop, - GiantSprite: giantSprite, - Seeds: new Item[] { - new SObject(id, 1) - }, - Phases: phases, - PhaseSprites: sprites, - Regrow: regrow, - IsPaddyCrop: paddyCrop, - StartDate: startDate, - EndDate: endDate - ); } } diff --git a/Almanac/Fish/FishManager.cs b/Almanac/Fish/FishManager.cs index d56558a..56e200e 100644 --- a/Almanac/Fish/FishManager.cs +++ b/Almanac/Fish/FishManager.cs @@ -188,11 +188,11 @@ public void LoadOverrides() { #region Locations - private static void AddFish(SubLocation loc, int[] seasons, int fish, Dictionary>> result ) { - if ( ! result.TryGetValue(fish, out var entry)) { - result[fish] = new() { - [loc] = seasons.ToList(), - }; + private static void AddFish(SubLocation loc, int[] seasons, string fish, Dictionary>> result ) { + if ( ! result.TryGetValue(fish, out var entry)) { + result[fish] = new() { + [loc] = seasons.ToList(), + }; return; } @@ -208,9 +208,9 @@ private static void AddFish(SubLocation loc, int[] seasons, int fish, Dictionary } } - private static void RemoveFish(SubLocation loc, int[] seasons, int fish, Dictionary>> result) { - if (!result.TryGetValue(fish, out var entry)) - return; + private static void RemoveFish(SubLocation loc, int[] seasons, string fish, Dictionary>> result) { + if (!result.TryGetValue(fish, out var entry)) + return; if (!entry.TryGetValue(loc, out var slist)) return; @@ -231,9 +231,9 @@ private static void RemoveFish(SubLocation loc, int[] seasons, int fish, Diction result.Remove(fish); } - public Dictionary>> GetFishLocations() { - if (!OverridesLoaded) - LoadOverrides(); + public Dictionary>> GetFishLocations() { + if (!OverridesLoaded) + LoadOverrides(); var result = FishHelper.GetFishLocations(); @@ -250,17 +250,15 @@ public Dictionary>> GetFishLocations() { SubLocation sl = new(ovr.Map, ovr.Zone); - if (ovr.AddFish != null) - foreach(string sFish in ovr.AddFish) { - if (int.TryParse(sFish, out int fish)) + if (ovr.AddFish != null) + foreach(string fish in ovr.AddFish) { AddFish(sl, seasons, fish, result); - } + } - if (ovr.RemoveFish != null) - foreach(string sFish in ovr.RemoveFish) { - if (int.TryParse(sFish, out int fish)) + if (ovr.RemoveFish != null) + foreach(string fish in ovr.RemoveFish) { RemoveFish(sl, seasons, fish, result); - } + } } }); diff --git a/Almanac/Fish/FishingPage.cs b/Almanac/Fish/FishingPage.cs index 87fbb3e..f3b601b 100644 --- a/Almanac/Fish/FishingPage.cs +++ b/Almanac/Fish/FishingPage.cs @@ -299,8 +299,8 @@ public override void LoadState(FishingState state) { if (Tank == null) return null; - var urchin = new SObject(397, 1); - FillTank(urchin, 4); + var urchin = Utility.CreateItemByID("(O)397", 1); + FillTank(urchin, 4); builder.Text("\n\n\n\n"); @@ -812,12 +812,12 @@ public void FillTank(Item item, int count = -1) { Tank.ResetFish(); Tank.generationSeed.Value++; - // Decor - if (Mod.Config.DecorateFishTank) { - Tank.heldItems.Add(new SObject(152, 1)); - Tank.heldItems.Add(new SObject(390, 1)); - Tank.heldItems.Add(new SObject(393, 1)); - } + // Decor + if (Mod.Config.DecorateFishTank) { + Tank.heldItems.Add(Utility.CreateItemByID("(O)152", 1)); + Tank.heldItems.Add(Utility.CreateItemByID("(O)390", 1)); + Tank.heldItems.Add(Utility.CreateItemByID("(O)393", 1)); + } // Add the Fish if (count < 1) { @@ -838,15 +838,15 @@ public void FillTank(Item item, int count = -1) { // Do we want hats? - if (Tank.tankFish.Count > 0 && Tank.tankFish[0].fishIndex == 86) { - Dictionary dictionary = Game1.content.Load>(@"Data\hats"); + if (Tank.tankFish.Count > 0 && Tank.tankFish[0].fishIndex == 86) { + Dictionary dictionary = Game1.content.Load>(@"Data\hats"); - for (int i = 0; i < 4; i++) { - int hat = Game1.random.Next(0, dictionary.Keys.Count); - Tank.heldItems.Add(new Hat(hat)); + for (int i = 0; i < 4; i++) { + string hat = dictionary.Keys.ElementAt(Game1.random.Next(0, dictionary.Count)); + Tank.heldItems.Add(Utility.CreateItemByID($"(H){hat}", 1)); + } } } - } #endregion diff --git a/Almanac/Fish/VanillaProvider.cs b/Almanac/Fish/VanillaProvider.cs index b6edbd0..6f9bfc5 100644 --- a/Almanac/Fish/VanillaProvider.cs +++ b/Almanac/Fish/VanillaProvider.cs @@ -29,11 +29,11 @@ public VanillaProvider(ModEntry mod) { public string Name => nameof(VanillaProvider); public int Priority => 0; - public IEnumerable GetFish() { - Dictionary data = Game1.content.Load>(@"Data\Fish"); - List result = new(); + public IEnumerable GetFish() { + Dictionary data = Game1.content.Load>(@"Data\Fish"); + List result = new(); - Dictionary>> locations = Mod.Fish.GetFishLocations(); + Dictionary>> locations = Mod.Fish.GetFishLocations(); List pondData = Game1.content.Load>(@"Data\FishPondData"); @@ -52,15 +52,15 @@ public IEnumerable GetFish() { return result; } - private static FishInfo? GetFishInfo(int id, string data, Dictionary>? locations, List pondData) { - if (string.IsNullOrEmpty(data)) - return null; + private static FishInfo? GetFishInfo(string id, string data, Dictionary> locations, List pondData) { + if (string.IsNullOrEmpty(data)) + return null; if (FishHelper.SkipFish(Game1.player, id)) return null; - string[] bits = data.Split('/'); - SObject obj = new(id, 1); + string[] bits = data.Split('/'); + SObject obj = Utility.CreateItemByID(id, 1) as SObject; if (bits.Length < 7 || obj is null) return null; @@ -178,12 +178,12 @@ public IEnumerable GetFish() { initial = key - 1; } - pondInfo = new( - Initial: initial, - SpawnTime: pond.SpawnTime, - ProducedItems: pond.ProducedItems.Select(x => x.ItemID).Distinct().Select(x => (x == 812 ? FishHelper.GetRoeForFish(obj) : new SObject(x, 1)) as Item).ToList() - ); - } + pondInfo = new( + Initial: initial, + SpawnTime: pond.SpawnTime, + ProducedItems: pond.ProducedItems.Select(x => x.ItemID).Distinct().Select(x => (x == "812" ? FishHelper.GetRoeForFish(obj) : Utility.CreateItemByID(x, 1))).ToList() + ); + } return new FishInfo( Id: id.ToString(), // bits[0], diff --git a/Almanac/FishHelper.cs b/Almanac/FishHelper.cs index 1ddec21..2941220 100644 --- a/Almanac/FishHelper.cs +++ b/Almanac/FishHelper.cs @@ -33,17 +33,17 @@ public static bool SkipLocation(string key) { return false; } - public static bool SkipFish(Farmer? who, int id) { - who ??= Game1.player; - - switch(id) { - case 898: - case 899: - case 900: - case 901: - case 902: - return ! who.team.SpecialOrderRuleActive("LEGENDARY_FAMILY"); - } + public static bool SkipFish(Farmer who, string id) { + who ??= Game1.player; + + switch(id) { + case "898": + case "899": + case "900": + case "901": + case "902": + return ! who.team.SpecialOrderRuleActive("LEGENDARY_FAMILY"); + } return false; } @@ -101,17 +101,17 @@ public static SObject GetRoeForFish(SObject fish) { if (fish.ParentSheetIndex == 698) color = new Color(61, 55, 42); - ColoredObject result = new(812, 1, color); - result.name = fish.Name + " Roe"; - result.preserve.Value = SObject.PreserveType.Roe; - result.preservedParentSheetIndex.Value = fish.ParentSheetIndex; - result.Price += fish.Price / 2; + ColoredObject result = new ColoredObject("812", 1, color); + result.name = fish.Name + " Roe"; + result.preserve.Value = SObject.PreserveType.Roe; + result.preservedParentSheetIndex.Value = fish.ItemID; + result.Price += fish.Price / 2; - return result; - } + return result; + } - public static Dictionary>> GetFishLocations() { - Dictionary>> result = new(); + public static Dictionary>> GetFishLocations() { + Dictionary>> result = new(); var locations = Game1.content.Load>(@"Data\Locations"); @@ -129,9 +129,9 @@ public static Dictionary>> GetFishLocatio int zone = pair.Key; SubLocation sl = new(lp.Key, zone); - foreach (int fish in pair.Value) { - if (!result.TryGetValue(fish, out var locs)) - result[fish] = locs = new(); + foreach (string fish in pair.Value) { + if (!result.TryGetValue(fish, out var locs)) + result[fish] = locs = new(); if (locs.TryGetValue(sl, out var seasons)) seasons.Add(season); @@ -148,8 +148,8 @@ public static Dictionary>> GetFishLocatio return result; } - public static Dictionary> GetFishLocations(int season) { - Dictionary> result = new(); + public static Dictionary> GetFishLocations(int season) { + Dictionary> result = new(); var locations = Game1.content.Load>(@"Data\Locations"); foreach (var lp in locations) { @@ -164,28 +164,28 @@ public static Dictionary> GetFishLocations(int season) { int zone = pair.Key; SubLocation sl = new(lp.Key, zone); - foreach(int fish in pair.Value) { - if (result.TryGetValue(fish, out var subs)) - subs.Add(sl); - else - result.Add(fish, new List() { sl }); + foreach(string fish in pair.Value) { + if (result.TryGetValue(fish, out var subs)) + subs.Add(sl); + else + result.Add(fish, new List() { sl }); + } } } - } return result; } - public static Dictionary> GetLocationFish(GameLocation location, int season) { - return GetLocationFish(location.Name, season); - } + public static Dictionary> GetLocationFish(GameLocation location, int season) { + return GetLocationFish(location.Name, season); + } - public static Dictionary> GetLocationFish(string key, int season, Dictionary? locations = null) { - if (key == "BeachNightMarket") - key = "Beach"; + public static Dictionary> GetLocationFish(string key, int season, Dictionary locations = null) { + if (key == "BeachNightMarket") + key = "Beach"; - locations ??= Game1.content.Load>(@"Data\Locations"); - Dictionary> result; + locations ??= Game1.content.Load>("Data\\Locations"); + Dictionary> result = null; if (locations.ContainsKey(key)) result = GetLocationFish(season, locations[key]); @@ -233,59 +233,60 @@ public static Dictionary> GetLocationFish(string key, int season, } } - switch(Game1.whichFarm) { - case 1: - GetLocationFish(season, locations["Forest"], result, true, 1); - GetLocationFish(season, locations["Town"], result, true, 1); - break; - case 2: - AddFish(734, -1, result); - GetLocationFish(season, locations["Forest"], result, true, 1); - break; - case 3: - GetLocationFish(season, locations["Forest"], result, true, 0); - break; - case 4: - GetLocationFish(season, locations["Mountain"], result, true); - break; - case 5: - GetLocationFish(season, locations["Forest"], result, true, 1); - break; - case 6: - AddFish(152, -1, result); - AddFish(723, -1, result); - AddFish(393, -1, result); - AddFish(719, -1, result); - AddFish(718, -1, result); - GetLocationFish(season, locations["Beach"], result, true); - break; - } + switch(Game1.whichFarm) { + case 1: + GetLocationFish(season, locations?["Forest"], result, true, 1); + GetLocationFish(season, locations?["Town"], result, true, 1); + break; + case 2: + AddFish("734", -1, result); + GetLocationFish(season, locations?["Forest"], result, true, 1); + break; + case 3: + GetLocationFish(season, locations?["Forest"], result, true, 0); + break; + case 4: + GetLocationFish(season, locations?["Mountain"], result, true); + break; + case 5: + GetLocationFish(season, locations?["Forest"], result, true, 1); + break; + case 6: + AddFish("152", -1, result); + AddFish("723", -1, result); + AddFish("393", -1, result); + AddFish("719", -1, result); + AddFish("718", -1, result); + GetLocationFish(season, locations?["Beach"], result, true); + break; + } return result; } - private static void AddFish(int fish, int zone, Dictionary> existing) { - if (existing.TryGetValue(zone, out List? result)) { - if (!result.Contains(fish)) - result.Add(fish); - } else - existing.Add(zone, new() { fish }); - } + private static void AddFish(string fish, int zone, Dictionary> existing) { + if (existing.TryGetValue(zone, out List result)) { + if (!result.Contains(fish)) + result.Add(fish); + } else + existing.Add(zone, new() { fish }); + } - public static Dictionary> GetLocationFish(int season, string? data) { - return GetLocationFish(season, data, new()); - } + public static Dictionary> GetLocationFish(int season, string data) { + return GetLocationFish(season, data, new()); + } - public static Dictionary> GetLocationFish(int season, string? data, Dictionary> existing, bool no_zones = false, int limit_zone = -1) { - if (string.IsNullOrEmpty(data)) - return existing; + public static Dictionary> GetLocationFish(int season, string data, Dictionary> existing, bool no_zones = false, int limit_zone = -1) { + if (string.IsNullOrEmpty(data)) + return existing; string[] entries = data.Split('/')[4 + season].Split(' ', StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; (i + 1) < entries.Length; i += 2) { - if (int.TryParse(entries[i], out int fish) && int.TryParse(entries[i + 1], out int zone)) { - if (limit_zone != -1 && zone != -1 && limit_zone != zone) - continue; + for (int i = 0; (i + 1) < entries.Length; i += 2) { + string fish = entries[i]; + if (int.TryParse(entries[i + 1], out int zone)) { + if (limit_zone != -1 && zone != -1 && limit_zone != zone) + continue; AddFish(fish, no_zones ? -1 : zone, existing); } else diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index a44eb5f..9d0b615 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -359,13 +359,13 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { if (gathering && bush == null) bush = new(); - if (gathering && bush!.inBloom(date.Season, date.DayOfMonth)) { - Item? berry = null; - if (date.SeasonIndex == 0) - berry = InventoryHelper.CreateItemById("(O)296", 1); // Salmonberry + if (gathering && bush.inBloom(date.Season, date.DayOfMonth)) { + Item berry = null; + if (date.SeasonIndex == 0) + berry = Utility.CreateItemByID("(O)296", 1); // Salmonberry - else if (date.SeasonIndex == 2) - berry = InventoryHelper.CreateItemById("(O)410", 1); // Blackberry + else if (date.SeasonIndex == 2) + berry = Utility.CreateItemByID("(O)410", 1); // Blackberry if (berry != null) { bool first_day = date.DayOfMonth == 1 || !bush.inBloom(date.Season, date.DayOfMonth - 1); diff --git a/Almanac/Managers/WeatherManager.cs b/Almanac/Managers/WeatherManager.cs index 260d6b5..382e5f2 100644 --- a/Almanac/Managers/WeatherManager.cs +++ b/Almanac/Managers/WeatherManager.cs @@ -13,6 +13,7 @@ using StardewModdingAPI.Events; using StardewValley; +using StardewValley.GameData; using Leclair.Stardew.Almanac.Models; @@ -32,38 +33,53 @@ public class WeatherManager : BaseManager { private ulong CachedSeed; private int CachedYear; - private readonly Dictionary CachedWeather = new(); + private readonly Dictionary CachedWeather = new(); - public WeatherManager(ModEntry mod) : base(mod) { } + public WeatherManager(ModEntry mod) : base(mod) { } - public void Invalidate() { - lock ((CachedWeather as ICollection).SyncRoot) { - CachedSeed = 0; - CachedYear = -1; - CachedWeather.Clear(); - } + public void Invalidate() { + WithCache(() => { + CachedSeed = 0; + CachedYear = -1; + CachedWeather.Clear(); + }); - lock (RuleLock) { - Rules = null; - SortedRules = null; - RulesLoaded = false; + WithRules(() => { + Rules = null; + SortedRules = null; + RulesLoaded = false; + }); } } #region Events - [Subscriber] - private void OnSaveLoaded(object sender, SaveLoadedEventArgs e) { - CachedSeed = 0; - CachedYear = -1; - CachedWeather.Clear(); - } + [Subscriber] + private void OnSaveLoaded(object sender, SaveLoadedEventArgs e) { + WithCache(() => { + CachedSeed = 0; + CachedYear = -1; + CachedWeather.Clear(); + }); + } #endregion #region Lock Helpers - #endregion + private void WithRules(Action action) { + lock (RuleLock) { + action(); + } + } + + private void WithCache(Action action) { + lock ((CachedWeather as ICollection).SyncRoot) { + action(); + } + } + + #endregion #region Data Access @@ -74,102 +90,95 @@ public IEnumerable GetRules() { return SortedRules!; } - public int GetWeatherForDate(ulong seed, int day) { - return GetWeatherForDate(seed, day, GameLocation.LocationContext.Default); - } - - public int GetWeatherForDate(ulong seed, int day, GameLocation.LocationContext context) { - WorldDate date = new(); - date.TotalDays = day; - return GetWeatherForDate(seed, date, context); - } - - public int GetWeatherForDate(ulong seed, WorldDate date, GameLocation.LocationContext context) { - string key = "Default"; - if (context == GameLocation.LocationContext.Island) - key = "Island"; + public string GetWeatherForDate(ulong seed, int day) { + return GetWeatherForDate(seed, day, GameLocation.LocationContext.Default); + } - UpdateCache(seed, date.Year, key); + public string GetWeatherForDate(ulong seed, int day, LocationContextData context) { + WorldDate date = new(); + date.TotalDays = day; + return GetWeatherForDate(seed, date, context); + } - if (! CachedWeather.TryGetValue(key, out int[]? Forecast)) - return 0; + public string GetWeatherForDate(ulong seed, WorldDate date, LocationContextData context) { + UpdateCache(seed, date.Year, context); - return Forecast[date.SeasonIndex * ModEntry.DaysPerMonth + date.DayOfMonth - 1]; - } + string[] Forecast = CachedWeather[context.Name]; + return Forecast[date.SeasonIndex * ModEntry.DaysPerMonth + date.DayOfMonth - 1]; + } #endregion #region Calculation - private void UpdateCache(ulong seed, int year, string location) { - if (CachedSeed == seed && CachedYear == year && CachedWeather.ContainsKey(location)) - return; + private void UpdateCache(ulong seed, int year, LocationContextData context) { + lock((CachedWeather as ICollection).SyncRoot) { - CachedSeed = seed; - CachedYear = year; + if (CachedSeed != seed || CachedYear != year) + CachedWeather.Clear(); - int[] Forecast = new int[ModEntry.DaysPerMonth * 4]; - bool[] RuledDates = new bool[Forecast.Length]; + if (CachedWeather.ContainsKey(context.Name)) + return; - lock ((CachedWeather as ICollection).SyncRoot) { - CachedWeather[location] = Forecast; - } + CachedSeed = seed; + CachedYear = year; - WorldDate date = new(year, "spring", 1); + string[] forecast = new string[ModEntry.DaysPerMonth * 4]; + bool[] RuledDates = new bool[forecast.Length]; - // First, we calculate the raw weather on any given date. - for(int i = 1; i <= Forecast.Length; i++) { - Forecast[i - 1] = WeatherHelper.GetRawWeatherForDate( - seed, date, - location == "Island" ? - GameLocation.LocationContext.Island : - GameLocation.LocationContext.Default - ); + WorldDate date = new(year, "spring", 1); - // Standard weather is overwritten on some dates. Don't - // let us modify the weather on those days with rules. - if (location == "Default" && Game1.getWeatherModificationsForDate(date, -1) != -1) - RuledDates[i - 1] = true; - - date.TotalDays++; - } + // First, we calculate the raw weather on any given date. + for(int i = 1; i <= forecast.Length; i++) { + forecast[i - 1] = WeatherHelper.GetRawWeatherForDate(seed, date, context); - if (!Mod.Config.EnableWeatherRules) - return; - - // Now, we need to start processing rules. - IEnumerable rules = GetRules(); - - foreach (WeatherRule rule in rules) { - if (rule == null || !rule.Enabled) - continue; + // Standard weather is overwritten on some dates. Don't + // let us modify the weather on those days with rules. + // TODO: See how this changes for different contexts. + if (context == GameLocation.LocationContext.Default && Game1.getWeatherModificationsForDate(date, "didnotchange") != "didnotchange") + RuledDates[i - 1] = true; - if (rule.FirstYear > year || rule.LastYear < year) - continue; - - if (rule.ValidYears != null && rule.ValidYears.Contains(year)) - continue; + date.TotalDays++; + } - if (rule.Contexts != null) { - bool matched = false; - foreach (string ctx in rule.Contexts) { - if (location.Equals(ctx, StringComparison.OrdinalIgnoreCase)) { - matched = true; - break; + if (Mod.Config.EnableWeatherRules) { + // Now, we need to start processing rules. + IEnumerable rules = GetRules(); + + foreach (WeatherRule rule in rules) { + if (rule == null || !rule.Enabled) + continue; + + if (rule.FirstYear > year || rule.LastYear < year) + continue; + + if (rule.ValidYears != null && rule.ValidYears.Contains(year)) + continue; + + if (rule.Contexts != null) { + bool matched = false; + foreach (string ctx in rule.Contexts) { + if (context.Name.Equals(ctx, StringComparison.OrdinalIgnoreCase)) { + matched = true; + break; + } + } + if (!matched) + continue; + } + + ExecuteRule(rule, seed, year, forecast, RuledDates); } } - if (!matched) - continue; + + CachedWeather[context.Name] = forecast; } - - ExecuteRule(rule, seed, year, Forecast, RuledDates); } - } - private void ExecuteRule(WeatherRule rule, ulong seed, int year, int[] Weather, bool[] RuledDates) { - var pattern = rule.CalculatedPattern; - if (pattern == null) - return; + private void ExecuteRule(WeatherRule rule, ulong seed, int year, string[] Weather, bool[] RuledDates) { + var pattern = rule.CalculatedPattern; + if (pattern == null) + return; int[] seasons = rule.ValidSeasonIndices; @@ -190,8 +199,8 @@ private void ExecuteRule(WeatherRule rule, ulong seed, int year, int[] Weather, if (!seasons.Contains(i) && !seasons.Contains(-1)) continue; - ArraySegment weatherSeg = new(Weather, first, ModEntry.DaysPerMonth); - ArraySegment ruledSeg = new(RuledDates, first, ModEntry.DaysPerMonth); + ArraySegment weatherSeg = new(Weather, first, ModEntry.DaysPerMonth); + ArraySegment ruledSeg = new(RuledDates, first, ModEntry.DaysPerMonth); ExecuteRuleInner(rule, pattern, seed, year, first, weatherSeg, ruledSeg); } @@ -203,15 +212,15 @@ private void ExecuteRule(WeatherRule rule, ulong seed, int year, int[] Weather, if (!seasons.Contains(i / 4) && !seasons.Contains(-1)) continue; - ArraySegment weatherSeg = new(Weather, first, 7); - ArraySegment ruledSeg = new(RuledDates, first, 7); + ArraySegment weatherSeg = new(Weather, first, 7); + ArraySegment ruledSeg = new(RuledDates, first, 7); ExecuteRuleInner(rule, pattern, seed, year, first, weatherSeg, ruledSeg); } } } - private static void ExecuteRuleInner(WeatherRule rule, RulePatternEntry[][] pattern, ulong seed, int year, int offset, ArraySegment Weather, ArraySegment RuledDates) { + private static void ExecuteRuleInner(WeatherRule rule, RulePatternEntry[][] pattern, ulong seed, int year, int offset, ArraySegment Weather, ArraySegment RuledDates) { Dictionary possibleMatches = new(); @@ -263,7 +272,7 @@ private static void ExecuteRuleInner(WeatherRule rule, RulePatternEntry[][] patt List matches = possibleMatches.Values.Where(match => match.Cost <= min_cost).ToList(); matches.Sort((a, b) => a.Cost - b.Cost); - Random rnd = new((int) seed + (year * 112) + offset); + Random rnd = new((int)seed + (year * 112) + offset); // TODO: Apply weights when picking a random match. int idx = rnd.Next(0, matches.Count); @@ -273,7 +282,7 @@ private static void ExecuteRuleInner(WeatherRule rule, RulePatternEntry[][] patt ApplyPattern(rnd, matches[idx].Offset, pattern, Weather, RuledDates); } - private static void ApplyPattern(Random rnd, int offset, RulePatternEntry[][] pattern, ArraySegment Weather, ArraySegment RuledDates) { + private static void ApplyPattern(Random rnd, int offset, RulePatternEntry[][] pattern, ArraySegment Weather, ArraySegment RuledDates) { for (int i = 0; i < pattern.Length; i++) { int idx = offset + i - 1; @@ -283,16 +292,16 @@ private static void ApplyPattern(Random rnd, int offset, RulePatternEntry[][] pa RuledDates[idx] = true; - int current = Weather[idx]; - bool matched = false; - float total = 0f; - foreach (var item in pattern[i]) { - total += item.Weight; - if (current == ((int) item.Weather)) { - matched = true; - break; + string current = Weather[idx]; + bool matched = false; + float total = 0f; + foreach (var item in pattern[i]) { + total += item.Weight; + if (current == item.Weather) { + matched = true; + break; + } } - } if (matched) continue; @@ -303,17 +312,17 @@ private static void ApplyPattern(Random rnd, int offset, RulePatternEntry[][] pa if (item.Weight <= 0) continue; - if (val <= item.Weight) { - Weather[idx] = ((int)item.Weather); - break; - } + if (val <= item.Weight) { + Weather[idx] = item.Weather; + break; + } val -= item.Weight; } } } - private static int GetPatternCost(int offset, RulePatternEntry[][] pattern, ArraySegment Weather, ArraySegment RuledDates) { + private static int GetPatternCost(int offset, RulePatternEntry[][] pattern, ArraySegment Weather, ArraySegment RuledDates) { int cost = 0; @@ -323,15 +332,15 @@ private static int GetPatternCost(int offset, RulePatternEntry[][] pattern, Arra if (idx < 0 || idx > Weather.Count) return -1; - int current = Weather[idx]; + string current = Weather[idx]; - bool matched = false; - foreach (var item in pattern[i]) { - if (current == ((int)item.Weather)) { - matched = true; - break; + bool matched = false; + foreach (var item in pattern[i]) { + if (current == item.Weather) { + matched = true; + break; + } } - } if (!matched) { if (RuledDates[idx]) @@ -406,8 +415,45 @@ public void LoadRules() { } } - #endregion -} + #endregion + + #region Daily Update + + public void UpdateForNewDay() { + if (! Mod.Config.EnableDeterministicWeather) + return; + + if (!Game1.IsMasterGame) + return; + + var contexts = Game1.content.Load>(@"Data\LocationContexts"); + WorldDate date = new(Game1.year, Game1.currentSeason, Game1.dayOfMonth + 1); + foreach(var entry in contexts) { + var weather = Game1.netWorldState.Value.GetWeatherForLocation(entry.Key); + weather.weatherForTomorrow.Value = GetWeatherForDate(Mod.GetBaseWorldSeed(), date, entry.Value); + } + + // Since we've changed weather, we need to re-copy. + foreach(var entry in contexts) { + if (entry.Value.CopyWeatherFromLocation == null) + continue; + + try { + Game1.netWorldState.Value + .GetWeatherForLocation(entry.Key) + .CopyFrom( + Game1.netWorldState.Value + .GetWeatherForLocation( + entry.Value.CopyWeatherFromLocation + )); + } catch(Exception ex) { + Log($"Unable to copy weather from context {entry.Key} to {entry.Value.CopyWeatherFromLocation}.", LogLevel.Warn, ex, LogLevel.Trace); + } + } + } + + #endregion + } internal struct PossibleMatch { public int Offset { get; } diff --git a/Almanac/Menus/BookMenu.cs b/Almanac/Menus/BookMenu.cs new file mode 100644 index 0000000..70bf16e --- /dev/null +++ b/Almanac/Menus/BookMenu.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +using Leclair.Stardew.Common; +using Leclair.Stardew.Common.UI; +using Leclair.Stardew.Common.UI.FlowNode; +using Leclair.Stardew.Common.UI.SimpleLayout; +using Leclair.Stardew.Common.Types; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +using StardewValley; +using StardewValley.Menus; + +using StardewModdingAPI.Utilities; + +using Leclair.Stardew.Almanac.Pages; + +namespace Leclair.Stardew.Almanac.Menus; + +internal class BookState { + public string LastSection; + public int TabScroll; + public Dictionary SectionStates; +} + +internal class BookMenu : IClickableMenu { + + // State Storage + static readonly PerScreen> PreviousState = new(); + + #region Fields + + internal Texture2D background; + internal readonly ModEntry Mod; + internal readonly string BookId; + + // Tab Components + public List TabComponents = new(); + public ClickableTextureComponent btnTabsUp; + public ClickableTextureComponent btnTabsDown; + + // Sections + internal readonly IAlmanacPage[] Sections; + private int SectionIndex = -1; + + + + public List PageComponents = new(); + + #endregion + + #region Constructor + + #endregion + + #region Properties + + internal IAlmanacPage Section => (Sections == null || SectionIndex < 0 || SectionIndex >= Sections.Length) ? null : Sections[SectionIndex]; + + + + internal Models.Style Style => Mod.Theme?.Standard; + internal Models.Style MagicStyle => Mod.Theme?.Magic; + + + + + #endregion + + +} diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index 563f011..86b9e24 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -121,17 +121,18 @@ public override void Entry(IModHelper helper) { ThemeManager.ThemeChanged += OnThemeChanged; ThemeManager.Discover(); - // Init - RegisterBuilder(CoverPage.GetPage); - RegisterBuilder(CropPage.GetPage); - RegisterBuilder(WeatherPage.GetPage); - RegisterBuilder(WeatherPage.GetIslandPage); - RegisterBuilder(TrainPage.GetPage); - RegisterBuilder(FortunePage.GetPage); - RegisterBuilder(MinesPage.GetPage); - RegisterBuilder(NoticesPage.GetPage); - RegisterBuilder(FishingPage.GetPage); - } + // Init + RegisterBuilder(CoverPage.GetPage); + RegisterBuilder(CropPage.GetPage); + RegisterBuilder(WeatherPage.GetPage); + RegisterBuilder(WeatherPage.GetIslandPage); + RegisterBuilder(TrainPage.GetPage); + RegisterBuilder(FortunePage.GetPage); + RegisterBuilder(MinesPage.GetPage); + RegisterBuilder(NoticesPage.GetPage); + RegisterBuilder(FishingPage.GetPage); + RegisterBuilder(DebugItemsPage.GetPage); + } public override object GetApi() { return API; @@ -370,16 +371,25 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { } }); - Helper.ConsoleCommands.Add("al_forecast", "Get the forecast for the loaded save.", (name, args) => { - ulong seed = GetBaseWorldSeed(); - WorldDate date = new(Game1.Date); - for (int i = 0; i < 4 * 28; i++) { - int weather = Weather.GetWeatherForDate(seed, date, GameLocation.LocationContext.Default); - Log($"Date: {date.Localize()} -- Weather: {WeatherHelper.GetWeatherName(weather)}"); - date.TotalDays++; - } - }); - } + Helper.ConsoleCommands.Add("al_now", "Print information about the in-game time.", (_, _) => { + Log($"Date: {Game1.Date.Localize()}", LogLevel.Info); + Log($"- Year: {Game1.year}", LogLevel.Info); + Log($"- Season: {Game1.currentSeason}", LogLevel.Info); + Log($"- DayOf: {Game1.dayOfMonth}", LogLevel.Info); + Log($"- TDays: {Game1.Date.TotalDays}", LogLevel.Info); + Log($"DaysPlayed: {Game1.stats.DaysPlayed}", LogLevel.Info); + }); + + Helper.ConsoleCommands.Add("al_forecast", "Get the forecast for the loaded save.", (name, args) => { + ulong seed = GetBaseWorldSeed(); + WorldDate date = new(Game1.Date); + for (int i = 0; i < 4 * 28; i++) { + string weather = Weather.GetWeatherForDate(seed, date, GameLocation.LocationContext.Default); + Log($"Date: {date.Localize()} -- Weather: {weather}"); + date.TotalDays++; + } + }); + } [Subscriber] private void OnMenuChanged(object sender, MenuChangedEventArgs e) { diff --git a/Almanac/Models/WeatherRule.cs b/Almanac/Models/WeatherRule.cs index a2bdc3a..a12e935 100644 --- a/Almanac/Models/WeatherRule.cs +++ b/Almanac/Models/WeatherRule.cs @@ -40,15 +40,15 @@ public RuleDateRange(int start, int end) { } } -public struct RulePatternEntry { - public RuleWeather Weather { get; set; } - public float Weight { get; set; } = 1; + public struct RulePatternEntry { + public string Weather { get; set; } + public float Weight { get; set; } = 1; - public RulePatternEntry(RuleWeather weather, float weight) { - Weather = weather; - Weight = weight; + public RulePatternEntry(string weather, float weight) { + Weather = weather; + Weight = weight; + } } -} public class WeatherRule { public string? Id { get; set; } @@ -72,8 +72,8 @@ public class WeatherRule { public TimeScale Period { get; set; } public RuleDateRange[]? Dates { get; set; } - public RulePatternEntry[][]? WeightedPattern { get; set; } - public RuleWeather[][]? Pattern { get; set; } + public RulePatternEntry[][] WeightedPattern { get; set; } + public string[][] Pattern { get; set; } public RuleWeight Weight { get; set; } = RuleWeight.None; @@ -99,27 +99,27 @@ public RulePatternEntry[][]? CalculatedPattern { RulePatternEntry[][] result = new RulePatternEntry[Pattern.Length][]; - for (int i = 0; i < result.Length; i++) { - RuleWeather[] input = Pattern[i]; - result[i] = new RulePatternEntry[input.Length]; - for (int j = 0; j < input.Length; j++) { - result[i][j] = new RulePatternEntry { - Weather = input[j], - Weight = GetDefaultWeight(input[j]) - }; + for (int i = 0; i < result.Length; i++) { + string[] input = Pattern[i]; + result[i] = new RulePatternEntry[input.Length]; + for (int j = 0; j < input.Length; j++) { + result[i][j] = new RulePatternEntry { + Weather = input[j], + Weight = GetDefaultWeight(input[j]) + }; + } } - } return result; } } - public static float GetDefaultWeight(RuleWeather weather) { - if (weather == RuleWeather.Storm) - return 0.25f; + public static float GetDefaultWeight(string weather) { + if (weather == "Storm") + return 0.25f; - if (weather == RuleWeather.Festival) - return 0f; + if (weather == "Festival") + return 0f; return 1f; } diff --git a/Almanac/Pages/DebugItemsPage.cs b/Almanac/Pages/DebugItemsPage.cs new file mode 100644 index 0000000..9ac9a56 --- /dev/null +++ b/Almanac/Pages/DebugItemsPage.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +using Leclair.Stardew.Common; +using Leclair.Stardew.Common.Types; +using Leclair.Stardew.Common.UI; +using Leclair.Stardew.Common.UI.FlowNode; + +using StardewValley; +using StardewValley.Objects; +using StardewValley.GameData; +using StardewValley.Menus; + +using SObject = StardewValley.Object; + +using Leclair.Stardew.Almanac.Menus; + +namespace Leclair.Stardew.Almanac.Pages; + +internal class DebugItemsState : BaseState { + public string Item; +} + +internal class DebugItemsPage : BasePage { + + private Item CurrentItem; + + private readonly Cache, Item> ItemInfo; + private readonly Dictionary ItemNodes = new(); + + #region Life Cycle + + public static DebugItemsPage GetPage(AlmanacMenu menu, ModEntry mod) { + if (!mod.HasAlmanac(Game1.player) )// || !mod.Config.DebugMode) + return null; + + return new(menu, mod); + } + + public DebugItemsPage(AlmanacMenu menu, ModEntry mod) : base(menu, mod) { + ItemInfo = new(item => BuildRightPage(item), () => CurrentItem); + } + + public override void ThemeChanged() { + base.ThemeChanged(); + + foreach (var node in ItemNodes.Values) { + node.SelectedTexture = Menu.background; + node.HoverTexture = Menu.background; + } + } + + #endregion + + #region State Saving + + public override DebugItemsState SaveState() { + var state = base.SaveState(); + + state.Item = CurrentItem?.QualifiedItemID; + + return state; + } + + public override void LoadState(DebugItemsState state) { + base.LoadState(state); + + var item = string.IsNullOrEmpty(state.Item) ? null : Utility.CreateItemByID(state.Item, 1, allow_null: true); + SelectItem(item); + } + + #endregion + + #region Logic + + public bool SelectItem(Item item) { + if (item == null || CurrentItem == null || item.QualifiedItemID != CurrentItem.QualifiedItemID) { + CurrentItem = item; + SetRightFlow(ItemInfo.Value); + + foreach(var pair in ItemNodes) { + pair.Value.Selected = pair.Key == CurrentItem?.QualifiedItemID; + } + + return true; + } + + return false; + } + + public IFlowNode[] BuildRightPage(Item item) { + if (CurrentItem == null) + return null; + + FlowBuilder builder = FlowHelper.Builder(); + + var sprite = SpriteHelper.GetSprite(item); + + builder.Text(item.DisplayName, fancy: true, align: Alignment.Center); + builder.Text("\n\n"); + + builder + .Sprite(sprite, 1f, Alignment.Bottom | Alignment.Center) + .Text(" ") + .Sprite(sprite, 2f, Alignment.Bottom) + .Text(" ") + .Sprite(sprite, 4f, Alignment.Bottom) + .Text(" ") + .Sprite(sprite, 8f, Alignment.Bottom); + + builder.Text("\n\n"); + + builder + .Text("ID: ", bold: true) + .Text(item.QualifiedItemID, shadow: false); + + return builder.Build(); + } + + public override void Update() { + base.Update(); + + FlowBuilder builder = new(); + ItemNodes.Clear(); + + foreach (var type in ItemDataDefinition.IdentifierLookup.Values) { + builder.Text("\n").Text(type.Identifier, font: Game1.dialogueFont).Text("\n"); + foreach (string itemID in type.GetAllItemIDs()) { + var item = Utility.CreateItemByID($"{type.Identifier}{itemID}", 1, allow_null: true); + if (item == null) + continue; + + FlowBuilder sb = FlowHelper.Builder() + .Sprite(SpriteHelper.GetSprite(item), 3f); + + var node = new SelectableNode( + sb.Build(), + width: 72, + + onHover: (_, _, _) => { + Menu.HoveredItem = item; + Menu.HoverText = $"{item.DisplayName}\n{item.QualifiedItemID}"; + return true; + }, + + onClick: (_, _, _) => { + if (SelectItem(item)) + Game1.playSound("smallSelect"); + return true; + } + ) { + SelectedTexture = Menu.background, + SelectedSource = new(336, 352, 16, 16), + HoverTexture = Menu.background, + HoverSource = new(336, 352, 16, 16), + HoverColor = Color.White * 0.4f + }; + + ItemNodes.Add(item.QualifiedItemID, node); + builder.Add(node); + } + } + + SetLeftFlow(builder); + } + + #endregion + + #region ITab + + public override int SortKey => 1000; + public override string TabSimpleTooltip => "Debug: Items"; + public override Texture2D TabTexture => Game1.mouseCursors; + public override Rectangle? TabSource => Rectangle.Empty; + + #endregion + + #region IAlmanacPage + + public override PageType Type => PageType.Blank; + + #endregion + +} diff --git a/Almanac/Pages/WeatherPage.cs b/Almanac/Pages/WeatherPage.cs index 44fd49b..0ca3cbf 100644 --- a/Almanac/Pages/WeatherPage.cs +++ b/Almanac/Pages/WeatherPage.cs @@ -22,11 +22,11 @@ public class WeatherPage : BasePage, ICalendarPage { public static readonly Rectangle WEATHER_ICON = new(384, 352, 16, 16); - private readonly ulong Seed; - private IFlowNode[]? Nodes; - private int[]? Forecast; - private bool[]? Festivals; - private bool[]? Pirates; + private readonly ulong Seed; + private IFlowNode[] Nodes; + private string[] Forecast; + private bool[] Festivals; + private bool[] Pirates; readonly bool IsIsland; @@ -75,11 +75,11 @@ public WeatherPage(AlmanacMenu menu, ModEntry mod, bool isIsland) : base(menu, m public override void Update() { base.Update(); - Forecast = new int[ModEntry.DaysPerMonth]; - Nodes = new IFlowNode[ModEntry.DaysPerMonth]; - Festivals = IsIsland ? null : new bool[ModEntry.DaysPerMonth]; - Pirates = IsIsland ? new bool[ModEntry.DaysPerMonth] : null; - WorldDate date = new(Menu.Date); + Forecast = new string[ModEntry.DaysPerMonth]; + Nodes = new IFlowNode[ModEntry.DaysPerMonth]; + Festivals = IsIsland ? null : new bool[ModEntry.DaysPerMonth]; + Pirates = IsIsland ? new bool[ModEntry.DaysPerMonth] : null; + WorldDate date = new(Menu.Date); FlowBuilder builder = new(); List? pirateDays = IsIsland ? new() : null; @@ -90,12 +90,12 @@ public override void Update() { if (!IsIsland) builder.FormatText(I18n.Festival_About(Utility.getSeasonNameFromNumber(date.SeasonIndex))); - for (int day = 1; day <= ModEntry.DaysPerMonth; day++) { - date.DayOfMonth = day; - bool shown = forecastLength == -1 || date.TotalDays - today <= forecastLength; - int weather = Forecast[day - 1] = shown ? - Mod.Weather.GetWeatherForDate(Seed, date, IsIsland ? GameLocation.LocationContext.Island : GameLocation.LocationContext.Default) - : -1; + var context = IsIsland ? GameLocation.LocationContext.Island : GameLocation.LocationContext.Default; + + + for (int day = 1; day <= ModEntry.DaysPerMonth; day++) { + date.DayOfMonth = day; + string weather = Forecast[day - 1] = Mod.Weather.GetWeatherForDate(Seed, date, context); if (IsIsland) { bool pirates = Pirates![day - 1] = shown && day % 2 == 0 && ! WeatherHelper.IsRainOrSnow(weather); @@ -255,7 +255,7 @@ public void DrawUnderCell(SpriteBatch b, WorldDate date, Rectangle bounds) { bounds.X + (bounds.Width - 64) / 2, bounds.Y + (bounds.Height - 64) / 2 ), - WeatherHelper.GetWeatherIcon(Forecast[day], date.Season), + WeatherHelper.GetWeatherIcon(Forecast[day]), Color.White, 0f, Vector2.Zero, @@ -310,12 +310,9 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { if (Forecast == null) return; - int weather = Forecast[date.DayOfMonth - 1]; - if (weather == -1) - return; - - Menu.HoverText = WeatherHelper.LocalizeWeather(weather); - } + string weather = Forecast[date.DayOfMonth - 1]; + Menu.HoverText = WeatherHelper.LocalizeWeather(weather); + } #endregion diff --git a/Almanac/Patches/Game1_Patches.cs b/Almanac/Patches/Game1_Patches.cs new file mode 100644 index 0000000..8830b35 --- /dev/null +++ b/Almanac/Patches/Game1_Patches.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using HarmonyLib; + +using StardewModdingAPI; + +using StardewValley; + +namespace Leclair.Stardew.Almanac.Patches; + +internal static class Game1_Patches { + + private static IMonitor Monitor; + + internal static void Patch(ModEntry mod) { + Monitor = mod.Monitor; + + try { + mod.Harmony.Patch( + original: AccessTools.Method(typeof(Game1), nameof(Game1.UpdateWeatherForNewDay)), + postfix: new HarmonyMethod(typeof(Game1_Patches), nameof(UpdateWeatherForNewDay_Postfix)) + ); + + } catch(Exception ex) { + mod.Log("An error occurred while registering a harmony patch for Game1.", LogLevel.Error, ex); + } + } + + public static void UpdateWeatherForNewDay_Postfix() { + try { + ModEntry.Instance.Weather.UpdateForNewDay(); + + } catch (Exception ex) { + Monitor.Log($"An error occurred while running {typeof(Game1_Patches)}.{nameof(UpdateWeatherForNewDay_Postfix)}", LogLevel.Error); + Monitor.Log($"Details:\n{ex}", LogLevel.Error); + } + } +} diff --git a/Almanac/Patches/GameStateQuery_Patches.cs b/Almanac/Patches/GameStateQuery_Patches.cs new file mode 100644 index 0000000..66b1da2 --- /dev/null +++ b/Almanac/Patches/GameStateQuery_Patches.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Reflection.Emit; +using System.Reflection; + +using HarmonyLib; + +using StardewModdingAPI; + +using StardewValley; + +namespace Leclair.Stardew.Almanac.Patches; + +internal static class GameStateQuery_Patches { + + private static IMonitor Monitor; + + internal static void Patch(ModEntry mod) { + Monitor = mod.Monitor; + + try { + mod.Harmony.Patch( + original: AccessTools.Method(typeof(GameStateQuery), nameof(GameStateQuery.query_IS_FESTIVAL_DAY)), + transpiler: new HarmonyMethod(typeof(GameStateQuery_Patches), nameof(query_IS_FESTIVAL_DAY_Transpiler)) + ); + } catch (Exception ex) { + mod.Log("An error occurred while registering a harmony patch for GameStateQuery.", LogLevel.Warn, ex); + } + } + + public static WorldDate GetWorldDate() { + return new WorldDate(Game1.year, Game1.currentSeason, Game1.dayOfMonth); + } + + public static IEnumerable query_IS_FESTIVAL_DAY_Transpiler(IEnumerable instructions) { + foreach (var instr in instructions) { + if (instr.opcode == OpCodes.Call && instr.operand is MethodInfo method && method == AccessTools.PropertyGetter(typeof(Game1), nameof(Game1.Date))) { + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(GameStateQuery_Patches), nameof(GetWorldDate))); + } else + yield return instr; + } + } + +} diff --git a/Almanac/WeatherHelper.cs b/Almanac/WeatherHelper.cs index f7db148..907ec2d 100644 --- a/Almanac/WeatherHelper.cs +++ b/Almanac/WeatherHelper.cs @@ -1,10 +1,12 @@ #nullable enable using System; +using System.Collections.Generic; using Microsoft.Xna.Framework; using StardewValley; +using StardewValley.GameData; namespace Leclair.Stardew.Almanac; @@ -20,123 +22,148 @@ public static string GetSeasonName(int season) { }; } - public static string LocalizeWeather(int weather) { - return weather switch { - 0 => I18n.Weather_Sunny(), - 1 => I18n.Weather_Rain(), - 2 => I18n.Weather_Debris(), - 3 => I18n.Weather_Lightning(), - // 4 = Festival, Sunny - 5 => I18n.Weather_Snow(), - // 6 = Wedding, Sunny? - _ => I18n.Weather_Sunny() - }; - } - - public static string GetWeatherName(int weather) { - return weather switch { - 0 => "sunny", - 1 => "rain", - 2 => "debris", - 3 => "lightning", - 4 => "festival", - 5 => "snow", - 6 => "wedding", - _ => "unknown" - }; - } + public static string LocalizeWeather(string weatherID) { + return weatherID switch { + "Sun" => I18n.Weather_Sunny(), + "Rain" => I18n.Weather_Rain(), + "Wind" => I18n.Weather_Debris(), + "Storm" => I18n.Weather_Lightning(), + "Festival" => "Festival", + // 4 = Festival, Sunny + "Snow" => I18n.Weather_Snow(), + // 6 = Wedding, Sunny? + _ => I18n.Weather_Sunny() + }; + } - public static string GetWeatherStringID(int weather) { - return weather switch { - 0 => "Sun", - 1 => "Rain", - 2 => "Wind", - 3 => "Storm", - 4 => "Festival", - 5 => "Snow", - 6 => "Wedding", - _ => "unknown" - }; - } + public static string GetWeatherName(int weather) { + return weather switch { + 0 => "Sun", + 1 => "Rain", + 2 => "Wind", + 3 => "Storm", + 4 => "Festival", + 5 => "Snow", + 6 => "Wedding", + _ => "unknown" + }; + } - public static bool IsRainy(int weather) { - return weather == 1 || weather == 3; - } + public static bool IsRainy(string weatherID) { + switch (weatherID) { + case "Rain": + case "Storm": + return true; + } + return false; + } - public static bool IsRainOrSnow(int weather) { - return weather == 1 || weather == 3 || weather == 5; - } + public static bool IsRainOrSnow(string weatherID) { + switch(weatherID) { + case "Rain": + case "Storm": + case "Snow": + return true; + } + return false; + } - public static Rectangle GetWeatherIcon(int weather, string season) { - int offset = weather switch { - -1 => 5, - 0 => 0, - 1 => 2, - 2 => 1, - 3 => 3, - 4 => 0, - 5 => 4, - _ => 0 - }; + public static Rectangle GetWeatherIcon(string weatherID) { + int offset = weatherID switch { + "Sun" => 0, + "Rain" => 2, + "Wind" => 1, + "Storm" => 3, + "Festival" => 0, + "Snow" => 4, + "Wedding" => 0, + _ => 0 + }; return new Rectangle(448, 256 + offset * 16, 16, 16); } - public static int GetWeatherForDate(ulong seed, WorldDate date) { - return GetRawWeatherForDate(seed, date, GameLocation.LocationContext.Default); - } + public static string GetWeatherForDate(ulong seed, WorldDate date) { + return GetRawWeatherForDate(seed, date, GameLocation.LocationContext.Default); + } - public static int GetRawWeatherForDate(ulong seed, WorldDate date, GameLocation.LocationContext context) { - int offseed = (int) (((long) seed + date.TotalDays + (context == GameLocation.LocationContext.Island ? 1 : 0)) % uint.MaxValue - int.MinValue); - - Random rnd = new(offseed); - - int prewarm = rnd.Next(0, 100); - for (int j = 0; j < prewarm; j++) - rnd.NextDouble(); - - string season = date.Season; - int totalDays = date.TotalDays; - int dayOfMonth = date.DayOfMonth; - int result = 0; - - if (context == GameLocation.LocationContext.Default) { - if (Utility.isFestivalDay(dayOfMonth, season)) - result = 4; - else if (totalDays == 3U) - result = 1; - else { - double rainChance = !season.Equals("summer") ? (!season.Equals("winter") ? 0.183 : 0.63) : (dayOfMonth > 1 ? 0.12 + dayOfMonth * (3.0 / 1000.0) : 0.0); - if (rnd.NextDouble() < rainChance) { - if (season.Equals("winter")) - result = 5; - else if (season.Equals("summer") && rnd.NextDouble() < 0.85 || !season.Equals("winter") && rnd.NextDouble() < 0.25 && dayOfMonth > 2 && totalDays > 28U) - result = 3; - else - result = 1; - } else { - result = totalDays <= 2U || (!season.Equals("spring") || rnd.NextDouble() >= 0.2) && (!season.Equals("fall") || rnd.NextDouble() >= 0.6) ? 0 : 2; - } + public static Random GetRandom(ulong seed, WorldDate date, string context) { + int offseed = (int) (((long) seed + date.TotalDays + context.GetHashCode()) % uint.MaxValue - int.MinValue); + Random result = new(offseed); + + int prewarm = result.Next(0, 50); + for(int j = 0; j < prewarm; j++) + result.NextDouble(); + + prewarm = result.Next(0, 50); + for (int j = 0; j < prewarm; j++) + result.NextDouble(); + + return result; + } + + public static string GetRawWeatherForDate(ulong seed, WorldDate date, LocationContextData context) { + Random rnd = GetRandom(seed, date, context.Name); + + string result; + + // TODO: Handle CopyWeatherFromLocation + + // Subtract a day from the date. This doesn't make sense until you + // consider how the vanilla game's state is set when running it's + // own weather GameStateQuery. Specifically, for festival days. + WorldDate dt = new(date); + dt.TotalDays--; + + // Replace state on Game1 so that GameStateQuery will match. + int oldDayOfMonth = Game1.dayOfMonth; + string oldSeason = Game1.currentSeason; + int oldYear = Game1.year; + uint oldPlayed = Game1.stats.DaysPlayed; + + Game1.dayOfMonth = dt.DayOfMonth; + Game1.year = dt.Year; + Game1.currentSeason = dt.Season; + Game1.stats.DaysPlayed = (uint) (dt.TotalDays + 1); + + ModEntry.Instance.Log($"Getting weather for context {context.Name} for {date.Localize()}:", StardewModdingAPI.LogLevel.Trace); + + try { + result = ExecuteConditions(context.WeatherConditions, rnd); + + } finally { + Game1.dayOfMonth = oldDayOfMonth; + Game1.currentSeason = oldSeason; + Game1.year = oldYear; + Game1.stats.DaysPlayed = oldPlayed; } - result = Game1.getWeatherModificationsForDate(date, result); + if (context == GameLocation.LocationContext.Default) + return Game1.getWeatherModificationsForDate(date, result); - } else if (context == GameLocation.LocationContext.Island) { - if (rnd.NextDouble() < 0.24) - result = 1; + return result; } - return result; - } + private static string ExecuteConditions(IEnumerable conditions, Random rnd = null) { + GameStateQuery.PickRandomValue(rnd); + foreach(var entry in conditions) { + if (GameStateQuery.CheckConditions(entry.Condition, seeded_random: rnd)) { + ModEntry.Instance.Log($"Matched Condition: {entry.Weather} => {entry.Condition}", StardewModdingAPI.LogLevel.Trace); + return entry.Weather; + } + } - public static int GetRawWeatherForDate(ulong seed, int day) { - return GetRawWeatherForDate(seed, day, GameLocation.LocationContext.Default); - } + return "Sun"; + } - public static int GetRawWeatherForDate(ulong seed, int day, GameLocation.LocationContext context) { - WorldDate date = new(); - date.TotalDays = day; - return GetRawWeatherForDate(seed, date, context); - } + public static string GetRawWeatherForDate(ulong seed, int day) { + return GetRawWeatherForDate(seed, day, GameLocation.LocationContext.Default); + } + + public static string GetRawWeatherForDate(ulong seed, int day, LocationContextData context) { + WorldDate date = new(); + date.TotalDays = day; + return GetRawWeatherForDate(seed, date, context); + } } diff --git a/Almanac/assets/weather_rules.json b/Almanac/assets/weather_rules.json index cc4456f..16d0211 100644 --- a/Almanac/assets/weather_rules.json +++ b/Almanac/assets/weather_rules.json @@ -2,7 +2,8 @@ { "Id": "spring_can", "Contexts": [ - "Default" + "Default", + "Desert" ], "Priority": 10, "Period": "Year", @@ -41,7 +42,7 @@ ], "Period": "Week", "Pattern": [ - [ "Rain" ] + ["Rain"] ] }, { diff --git a/Common/Serialization/Converters/ItemConverter.cs b/Common/Serialization/Converters/ItemConverter.cs new file mode 100644 index 0000000..8bbbe44 --- /dev/null +++ b/Common/Serialization/Converters/ItemConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Microsoft.Xna.Framework; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using StardewValley; +using SObject = StardewValley.Object; + +namespace Leclair.Stardew.Common.Serialization.Converters +{ + public class ItemConverter : JsonConverter { + public override bool CanConvert(Type objectType) { + // This will get easier in 1.6. For now we only care about SObjects. + return typeof(SObject).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + switch (reader.TokenType) { + case JsonToken.Null: + return null; + case JsonToken.StartObject: + return ReadObject(JObject.Load(reader)); + default: + throw new JsonReaderException($"Can't parse Item from {reader.TokenType} node (path: {reader.Path})."); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + if (value is not SObject sobj) { + writer.WriteNull(); + return; + } + + var jo = new JObject { + {"Id", sobj.QualifiedItemID}, + {"Amount", sobj.Stack} + }; + + jo.WriteTo(writer); + } + + private static Item ReadObject(JObject obj) { + string id = obj.ValueIgnoreCase("Id"); + int stack = obj.ValueIgnoreCase("Amount"); + + return Utility.CreateItemByID(id, stack <= 0 ? 1 : stack, allow_null: true); + } + } +} diff --git a/SeeMeRollin/ModConfig.cs b/SeeMeRollin/ModConfig.cs index f178966..e37f453 100644 --- a/SeeMeRollin/ModConfig.cs +++ b/SeeMeRollin/ModConfig.cs @@ -5,7 +5,7 @@ public class ModConfig { public KeybindList UseKey { get; set; } = KeybindList.Parse("Space, LeftStick"); - public bool ShowBuff { get; set; } = true; + //public bool ShowBuff { get; set; } = true; public int SpeedModifier { get; set; } = 6; diff --git a/SeeMeRollin/Patches/BuffPatches.cs b/SeeMeRollin/Patches/BuffPatches.cs new file mode 100644 index 0000000..84897c1 --- /dev/null +++ b/SeeMeRollin/Patches/BuffPatches.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +using HarmonyLib; + +using Microsoft.Xna.Framework; + +using StardewModdingAPI; + +using StardewValley; +using StardewValley.Menus; + +namespace Leclair.Stardew.SeeMeRollin.Patches { + public class BuffPatches { + + public static IMonitor Monitor => ModEntry.instance.Monitor; + + [HarmonyPatch(typeof(Buff), nameof(Buff.update))] + public static class Buff_Update { + static bool Prefix(Buff __instance, GameTime time, ref bool __result) { + if (__instance is RollinBuff) { + __result = false; + return false; + } + + return true; + } + } + + [HarmonyPatch(typeof(Buff), nameof(Buff.getTimeLeft))] + public static class Buff_TimeLeft { + static bool Prefix(Buff __instance, ref string __result) { + if (__instance is RollinBuff) { + __result = ""; + return false; + } + + return true; + } + } + + } +} diff --git a/SeeMeRollin/RollinBuff.cs b/SeeMeRollin/RollinBuff.cs new file mode 100644 index 0000000..4dbcd54 --- /dev/null +++ b/SeeMeRollin/RollinBuff.cs @@ -0,0 +1,20 @@ + +using StardewValley; + +namespace Leclair.Stardew.SeeMeRollin { + public class RollinBuff : Buff { + + public int Speed; + + public RollinBuff(int speed) : base( + ModEntry.BUFF, + duration: 1, + icon_texture: Game1.buffsIcons, + icon_sheet_index: 9, + display_name: I18n.Buff_Name() + ) { + effects.speed.Value = speed; + Speed = speed; + } + } +} From 883177bb3efedebea175594297661839eed94c81 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:13:31 -0400 Subject: [PATCH 03/39] Trying to get file to find Stardew --- Almanac/Almanac.csproj | 12 +-- Almanac/Crops/VanillaProvider.cs | 126 ++++++++++++------------------- 2 files changed, 55 insertions(+), 83 deletions(-) diff --git a/Almanac/Almanac.csproj b/Almanac/Almanac.csproj index 3f53211..22957ba 100644 --- a/Almanac/Almanac.csproj +++ b/Almanac/Almanac.csproj @@ -1,19 +1,21 @@ + C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley Almanac 0.18.1 Leclair.Stardew.Almanac true true net6.0 + net6.0 - - + + + - - - + + diff --git a/Almanac/Crops/VanillaProvider.cs b/Almanac/Crops/VanillaProvider.cs index 936b7da..0800362 100644 --- a/Almanac/Crops/VanillaProvider.cs +++ b/Almanac/Crops/VanillaProvider.cs @@ -28,10 +28,9 @@ public VanillaProvider(ModEntry mod) { public int Priority => 0; - public IEnumerable GetCrops() { - Dictionary crops = Game1.content.Load>(@"Data\Crops"); - List result = new(); - + public IEnumerable GetCrops() { + Dictionary crops = Game1.content.Load>(@"Data\Crops"); + List result = new(); foreach (var entry in crops) { try { CropInfo? info = GetCropInfo(entry.Key, entry.Value); @@ -41,88 +40,66 @@ public IEnumerable GetCrops() { ModEntry.Instance.Log($"Unable to process crop: {entry.Key}", LogLevel.Warn, ex); } } - return result; } - private CropInfo? GetCropInfo(string id, string data) { - + private CropInfo? GetCropInfo(string id, string data) { // TODO: Create Crop instances so we can // lean more on vanilla logic. - string[] bits = data.Split('/'); if (bits.Length < 5) return null; - - int sprite = Convert.ToInt32(bits[2]); - string harvestID = bits[3]; - int regrow = Convert.ToInt32(bits[4]); - + int sprite = Convert.ToInt32(bits[2]); + string harvestID = bits[3]; + int regrow = Convert.ToInt32(bits[4]); bool isTrellisCrop = bits.Length > 7 && bits[7].Equals("true"); - - if (!Game1.objectInformation.ContainsKey(harvestID)) - return null; - + if (!Game1.objectInformation.ContainsKey(harvestID)) + return null; string[] seasons = bits[1].Split(' ', StringSplitOptions.RemoveEmptyEntries); - WorldDate? startDate = null; WorldDate? endDate = null; - // TODO: Handle weird crops with a gap. - foreach (string season in seasons) { WorldDate start; WorldDate end; - try { start = new(1, season, 1); end = new(1, season, ModEntry.DaysPerMonth); - // Sanity check the seasons, just in case. string test = start.Season; test = end.Season; - } catch (Exception) { ModEntry.Instance.Log($"Invalid season for crop {id} (harvest:{harvestID}): {season}", LogLevel.Warn); return null; } - if (startDate == null || startDate > start) startDate = start; if (endDate == null || endDate < end) endDate = end; } - - // Skip crops that don't have any valid seasons. // Who knows what else isn't valid? if (startDate == null || endDate == null) return null; - // Skip crops that are always active. if (startDate.SeasonIndex == 0 && startDate.DayOfMonth == 1 && endDate.SeasonIndex == (WorldDate.MonthsPerYear - 1) && endDate.DayOfMonth == ModEntry.DaysPerMonth) return null; - - // If the sprite is 23, it's a seasonal multi-seed - // so we want to show that rather than the seed. - Item item; - if (sprite == 23) - item = Utility.CreateItemByID(id, 1); - else - item = Utility.CreateItemByID(harvestID, 1); - + // If the sprite is 23, it's a seasonal multi-seed + // so we want to show that rather than the seed. + Item item; + if (sprite == 23) + item = Utility.CreateItemByID(id, 1); + else + item = Utility.CreateItemByID(harvestID, 1); // Phases int[] phases = bits[0].Split(' ').Select(val => Convert.ToInt32(val)).ToArray(); - - // Stupid hard coded bullshit. - // TODO: Wait for the new context tag / data bit and check that. - bool paddyCrop = harvestID == "271" || harvestID == "830"; - + // Stupid hard coded bullshit. + // TODO: Wait for the new context tag / data bit and check that. + bool paddyCrop = harvestID == "271" || harvestID == "830"; // Phase Sprites // We need an extra sprite for the finished crop, // as well as one for regrow if that's enabled. SpriteInfo[] sprites = new SpriteInfo[phases.Length + 1 + (regrow > 0 ? 1 : 0)]; - // Are we dealing with colors? Color? color = null; string[]? colorbits = regrow <= 0 && bits.Length > 8 ? bits[8].Split(' ') : null; @@ -135,10 +112,8 @@ public IEnumerable GetCrops() { Convert.ToByte(colorbits[3]) ); } - for (int i = 0; i < sprites.Length; i++) { bool final = i == (sprites.Length - (regrow > 0 ? 2 : 1)); - sprites[i] = new( Game1.cropSpriteSheet, new Rectangle( @@ -154,43 +129,38 @@ public IEnumerable GetCrops() { overlayColor: final ? color : null ); } - bool isGiantCrop = false; SpriteInfo? giantSprite = null; - - // Giant Crops - var giantCrops = Game1.content.Load>(@"Data\GiantCrops"); - if (giantCrops != null && giantCrops.TryGetValue(harvestID, out var gcdata) && gcdata != null) { - isGiantCrop = true; - - giantSprite = new( - Game1.content.Load(gcdata.Texture), - new Rectangle( - gcdata.Corner.X, gcdata.Corner.Y, - gcdata.TileSize.X * 16, - (gcdata.TileSize.Y + 1) * 16 - ) - ); - } - - return new( - Convert.ToString(id), - item, - item.DisplayName, - SpriteHelper.GetSprite(item), - isGiantCrop, - giantSprite, - new Item[] { - new SObject(id, 1) - }, - isTrellisCrop, - phases, - regrow, - paddyCrop, - sprites, - startDate, - endDate + // Giant Crops + var giantCrops = Game1.content.Load>(@"Data\GiantCrops"); + if (giantCrops != null && giantCrops.TryGetValue(harvestID, out var gcdata) && gcdata != null) { + isGiantCrop = true; + giantSprite = new( + Game1.content.Load(gcdata.Texture), + new Rectangle( + gcdata.Corner.X, gcdata.Corner.Y, + gcdata.TileSize.X * 16, + (gcdata.TileSize.Y + 1) * 16 + ) ); } + return new( + Convert.ToString(id), + item, + item.DisplayName, + SpriteHelper.GetSprite(item), + isGiantCrop, + giantSprite, + new Item[] { + new SObject(id, 1) + }, + isTrellisCrop, + phases, + regrow, + paddyCrop, + sprites, + startDate, + endDate + ); } } From 19bdbd7a1704d66d13bbaeabbc933ce33153427f Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:52:31 -0400 Subject: [PATCH 04/39] Update the mod packages and other common.targets stuff for 1.6 and Smapi 4.0.2 --- Almanac/Almanac.csproj | 6 ++---- _build/common.targets | 9 ++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Almanac/Almanac.csproj b/Almanac/Almanac.csproj index 22957ba..c07698b 100644 --- a/Almanac/Almanac.csproj +++ b/Almanac/Almanac.csproj @@ -8,13 +8,11 @@ true true net6.0 - net6.0 - - - + + diff --git a/_build/common.targets b/_build/common.targets index b21c286..440c320 100644 --- a/_build/common.targets +++ b/_build/common.targets @@ -20,7 +20,7 @@ 1.6 - 4.0 + 4.0.2 DevMods $(SmapiVersion) @@ -29,9 +29,8 @@ - - C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley - C:\ + C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley - $(GameVersion) with $(SmapiVersion) + C:\ $(GamePath)\$(ModSet) $(SolutionDir)\_releases @@ -51,8 +50,8 @@ - + $(GamePath)\smapi-internal\Newtonsoft.Json.dll false From 997c81fe7706ac497616c320bb66d9890295ac3a Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:19:54 -0400 Subject: [PATCH 05/39] Remove GamePath hardcoding I removed the GamePath hardcoding, planning to revert this commit before making a pull request to not mess up creator's builds. --- _build/common.targets | 2 -- 1 file changed, 2 deletions(-) diff --git a/_build/common.targets b/_build/common.targets index 440c320..45902ae 100644 --- a/_build/common.targets +++ b/_build/common.targets @@ -29,8 +29,6 @@ - C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley - $(GameVersion) with $(SmapiVersion) - C:\ $(GamePath)\$(ModSet) $(SolutionDir)\_releases From e69df611afa3584658e1b4342ae8bdbc95386ea4 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 27 Mar 2024 03:09:17 -0400 Subject: [PATCH 06/39] Update Weather --- Almanac/Managers/WeatherManager.cs | 284 +++++++++++++---------------- Almanac/WeatherHelper.cs | 240 ++++++++++++------------ 2 files changed, 241 insertions(+), 283 deletions(-) diff --git a/Almanac/Managers/WeatherManager.cs b/Almanac/Managers/WeatherManager.cs index 382e5f2..4d5e8e1 100644 --- a/Almanac/Managers/WeatherManager.cs +++ b/Almanac/Managers/WeatherManager.cs @@ -16,6 +16,7 @@ using StardewValley.GameData; using Leclair.Stardew.Almanac.Models; +using StardewValley.GameData.LocationContexts; namespace Leclair.Stardew.Almanac.Managers; @@ -29,27 +30,24 @@ public class WeatherManager : BaseManager { private readonly object RuleLock = new(); // Cache - private ulong CachedSeed; private int CachedYear; - private readonly Dictionary CachedWeather = new(); - - public WeatherManager(ModEntry mod) : base(mod) { } - - public void Invalidate() { - WithCache(() => { - CachedSeed = 0; - CachedYear = -1; - CachedWeather.Clear(); - }); - - WithRules(() => { - Rules = null; - SortedRules = null; - RulesLoaded = false; - }); - } + private readonly Dictionary CachedWeather = new(); + + public WeatherManager(ModEntry mod) : base(mod) { } + + public void Invalidate() { + WithCache(() => { + CachedSeed = 0; + CachedYear = -1; + CachedWeather.Clear(); + }); + WithRules(() => { + Rules = null; + SortedRules = null; + RulesLoaded = false; + }); } #region Events @@ -79,31 +77,30 @@ private void WithCache(Action action) { } } - #endregion + #endregion #region Data Access - public IEnumerable GetRules() { - if (!RulesLoaded) - LoadRules(); - - return SortedRules!; - } + public IEnumerable GetRules() { + if (!RulesLoaded) + LoadRules(); + return SortedRules!; + } public string GetWeatherForDate(ulong seed, int day) { - return GetWeatherForDate(seed, day, GameLocation.LocationContext.Default); + return GetWeatherForDate(seed, day, Game1.currentLocation); } - public string GetWeatherForDate(ulong seed, int day, LocationContextData context) { + public string GetWeatherForDate(ulong seed, int day, GameLocation location) { WorldDate date = new(); date.TotalDays = day; - return GetWeatherForDate(seed, date, context); + return GetWeatherForDate(seed, date, location.GetLocationContext(), location.GetLocationContextId()); } - public string GetWeatherForDate(ulong seed, WorldDate date, LocationContextData context) { - UpdateCache(seed, date.Year, context); + public string GetWeatherForDate(ulong seed, WorldDate date, LocationContextData context, string contextID) { + UpdateCache(seed, date.Year, context, contextID); - string[] Forecast = CachedWeather[context.Name]; + string[] Forecast = CachedWeather[contextID]; return Forecast[date.SeasonIndex * ModEntry.DaysPerMonth + date.DayOfMonth - 1]; } @@ -111,13 +108,13 @@ public string GetWeatherForDate(ulong seed, WorldDate date, LocationContextData #region Calculation - private void UpdateCache(ulong seed, int year, LocationContextData context) { + private void UpdateCache(ulong seed, int year, LocationContextData context, string contextID ) { lock((CachedWeather as ICollection).SyncRoot) { if (CachedSeed != seed || CachedYear != year) CachedWeather.Clear(); - if (CachedWeather.ContainsKey(context.Name)) + if (CachedWeather.ContainsKey(contextID)) return; CachedSeed = seed; @@ -129,14 +126,13 @@ private void UpdateCache(ulong seed, int year, LocationContextData context) { WorldDate date = new(year, "spring", 1); // First, we calculate the raw weather on any given date. - for(int i = 1; i <= forecast.Length; i++) { - forecast[i - 1] = WeatherHelper.GetRawWeatherForDate(seed, date, context); - + for(int i = 0; i < forecast.Length; i++) { + forecast[i] = WeatherHelper.GetRawWeatherForDate(seed, date, context, contextID); // Standard weather is overwritten on some dates. Don't // let us modify the weather on those days with rules. // TODO: See how this changes for different contexts. - if (context == GameLocation.LocationContext.Default && Game1.getWeatherModificationsForDate(date, "didnotchange") != "didnotchange") - RuledDates[i - 1] = true; + if (context == Game1.currentLocation.GetLocationContext() && Game1.getWeatherModificationsForDate(date, "didnotchange") != "didnotchange") + RuledDates[i] = true; date.TotalDays++; } @@ -158,7 +154,7 @@ private void UpdateCache(ulong seed, int year, LocationContextData context) { if (rule.Contexts != null) { bool matched = false; foreach (string ctx in rule.Contexts) { - if (context.Name.Equals(ctx, StringComparison.OrdinalIgnoreCase)) { + if (contextID.Equals(ctx, StringComparison.OrdinalIgnoreCase)) { matched = true; break; } @@ -166,12 +162,11 @@ private void UpdateCache(ulong seed, int year, LocationContextData context) { if (!matched) continue; } - ExecuteRule(rule, seed, year, forecast, RuledDates); } } - CachedWeather[context.Name] = forecast; + CachedWeather[contextID] = forecast; } } @@ -179,119 +174,101 @@ private void ExecuteRule(WeatherRule rule, ulong seed, int year, string[] Weathe var pattern = rule.CalculatedPattern; if (pattern == null) return; - - int[] seasons = rule.ValidSeasonIndices; - - int daysPerYear = WorldDate.MonthsPerYear * ModEntry.DaysPerMonth; - int weeksPerYear = daysPerYear / 7; - - if (rule.Period == TimeScale.Total) { - Log($"Weather rule has period \"Total\" which is unsupported for weather rules. Treating as \"Year\".", LogLevel.Warn); - ExecuteRuleInner(rule, pattern, seed, year, 0, Weather, RuledDates); - - } else if (rule.Period == TimeScale.Year) { - ExecuteRuleInner(rule, pattern, seed, year, 0, Weather, RuledDates); - - } else if (rule.Period == TimeScale.Season) { - for(int i = 0; i < 4; i++) { - int first = (i * ModEntry.DaysPerMonth); - - if (!seasons.Contains(i) && !seasons.Contains(-1)) + int[] seasons = rule.ValidSeasonIndices; + int daysPerYear = WorldDate.MonthsPerYear * ModEntry.DaysPerMonth; + int weeksPerYear = daysPerYear / 7; + if (rule.Period == TimeScale.Total) { + Log($"Weather rule has period \"Total\" which is unsupported for weather rules. Treating as \"Year\".", LogLevel.Warn); + ExecuteRuleInner(rule, pattern, seed, year, 0, Weather, RuledDates); + } else if (rule.Period == TimeScale.Year) { + ExecuteRuleInner(rule, pattern, seed, year, 0, Weather, RuledDates); + } else if (rule.Period == TimeScale.Season) { + for(int i = 0; i < 4; i++) { + int first = (i * ModEntry.DaysPerMonth); + if (!seasons.Contains(i) && !seasons.Contains(-1)) continue; ArraySegment weatherSeg = new(Weather, first, ModEntry.DaysPerMonth); ArraySegment ruledSeg = new(RuledDates, first, ModEntry.DaysPerMonth); - ExecuteRuleInner(rule, pattern, seed, year, first, weatherSeg, ruledSeg); - } - - } else if (rule.Period == TimeScale.Week) { - for (int i = 0; i < weeksPerYear; i++) { - int first = (i * 7); + ExecuteRuleInner(rule, pattern, seed, year, first, weatherSeg, ruledSeg); + } + } else if (rule.Period == TimeScale.Week) { + for (int i = 0; i < weeksPerYear; i++) { + int first = (i * 7); - if (!seasons.Contains(i / 4) && !seasons.Contains(-1)) - continue; + if (!seasons.Contains(i / 4) && !seasons.Contains(-1)) + continue; ArraySegment weatherSeg = new(Weather, first, 7); ArraySegment ruledSeg = new(RuledDates, first, 7); - ExecuteRuleInner(rule, pattern, seed, year, first, weatherSeg, ruledSeg); + ExecuteRuleInner(rule, pattern, seed, year, first, weatherSeg, ruledSeg); + } } } - } private static void ExecuteRuleInner(WeatherRule rule, RulePatternEntry[][] pattern, ulong seed, int year, int offset, ArraySegment Weather, ArraySegment RuledDates) { - - Dictionary possibleMatches = new(); - - RuleDateRange[] dates; - if (rule.Dates != null && rule.Dates.Length > 0) - dates = rule.Dates; - else - dates = new RuleDateRange[] { - new(1, Weather.Count) - }; - - - int min_cost = int.MaxValue; - - foreach(RuleDateRange range in dates) { - int start = range.Start; - int end = range.End; - - if (start < 1) start = 1; - if (end > Weather.Count) end = Weather.Count; - - end -= (pattern.Length - 1); - if (end < start) - continue; - - for(int i = start; i <= end; i++) { - if (possibleMatches.ContainsKey(i)) + Dictionary possibleMatches = new(); + RuleDateRange[] dates; + if (rule.Dates != null && rule.Dates.Length > 0) + dates = rule.Dates; + else + dates = new RuleDateRange[] { + new(1, Weather.Count) + }; + int min_cost = int.MaxValue; + foreach(RuleDateRange range in dates) { + int start = range.Start; + int end = range.End; + if (start < 1) start = 1; + if (end > Weather.Count) end = Weather.Count; + + end -= (pattern.Length - 1); + if (end < start) continue; - int cost = GetPatternCost(i, pattern, Weather, RuledDates); - if (cost < 0) - continue; + for(int i = start; i <= end; i++) { + if (possibleMatches.ContainsKey(i)) + continue; - if (cost < min_cost) - min_cost = cost; + int cost = GetPatternCost(i, pattern, Weather, RuledDates); + if (cost < 0) + continue; - possibleMatches[i] = new(i, cost); - } - } + if (cost < min_cost) + min_cost = cost; - // We don't return if we have a min_cost 0 because we want to flag - // the matching days as rule'd so other rules don't override the - // valid weather. - if (possibleMatches.Count == 0) - return; - - // Sort the matches + possibleMatches[i] = new(i, cost); + } + } + // We don't return if we have a min_cost 0 because we want to flag + // the matching days as rule'd so other rules don't override the + // valid weather. + if (possibleMatches.Count == 0) + return; - List matches = possibleMatches.Values.Where(match => match.Cost <= min_cost).ToList(); - matches.Sort((a, b) => a.Cost - b.Cost); + // Sort the matches + List matches = possibleMatches.Values.Where(match => match.Cost <= min_cost).ToList(); + matches.Sort((a, b) => a.Cost - b.Cost); Random rnd = new((int)seed + (year * 112) + offset); - // TODO: Apply weights when picking a random match. - int idx = rnd.Next(0, matches.Count); - if (idx == matches.Count) - idx--; + // TODO: Apply weights when picking a random match. + int idx = rnd.Next(0, matches.Count); + if (idx == matches.Count) + idx--; - ApplyPattern(rnd, matches[idx].Offset, pattern, Weather, RuledDates); - } + ApplyPattern(rnd, matches[idx].Offset, pattern, Weather, RuledDates); + } private static void ApplyPattern(Random rnd, int offset, RulePatternEntry[][] pattern, ArraySegment Weather, ArraySegment RuledDates) { + for (int i = 0; i < pattern.Length; i++) { + int idx = offset + i - 1; + if (idx < 0 || idx > Weather.Count) + continue; - for (int i = 0; i < pattern.Length; i++) { - int idx = offset + i - 1; - - if (idx < 0 || idx > Weather.Count) - continue; - - RuledDates[idx] = true; - + RuledDates[idx] = true; string current = Weather[idx]; bool matched = false; float total = 0f; @@ -302,38 +279,32 @@ private static void ApplyPattern(Random rnd, int offset, RulePatternEntry[][] pa break; } } - - if (matched) - continue; - - float val = (float) rnd.NextDouble() * total; - - foreach (var item in pattern[i]) { - if (item.Weight <= 0) + if (matched) continue; + float val = (float) rnd.NextDouble() * total; + foreach (var item in pattern[i]) { + if (item.Weight <= 0) + continue; if (val <= item.Weight) { Weather[idx] = item.Weather; break; } - val -= item.Weight; + val -= item.Weight; + } } } - } private static int GetPatternCost(int offset, RulePatternEntry[][] pattern, ArraySegment Weather, ArraySegment RuledDates) { + int cost = 0; + for (int i = 0; i < pattern.Length; i++) { + int idx = offset + i - 1; - int cost = 0; - - for (int i = 0; i < pattern.Length; i++) { - int idx = offset + i - 1; - - if (idx < 0 || idx > Weather.Count) - return -1; + if (idx < 0 || idx > Weather.Count) + return -1; string current = Weather[idx]; - bool matched = false; foreach (var item in pattern[i]) { if (current == item.Weather) { @@ -341,18 +312,16 @@ private static int GetPatternCost(int offset, RulePatternEntry[][] pattern, Arra break; } } - - if (!matched) { - if (RuledDates[idx]) - return -1; - else - cost++; + if (!matched) { + if (RuledDates[idx]) + return -1; + else + cost++; + } } + return cost; } - return cost; - } - #endregion #region Data Loading @@ -394,22 +363,17 @@ public void LoadRules() { } catch (Exception ex) { Log($"The weather_rules.json file of {cp.Manifest.Name} is invalid.", LogLevel.Error, ex); } - if (newRules != null) HydrateRules(Rules, newRules); } - // Alright, now we need to sort the rules. List sorted = new(); - foreach(WeatherRule rule in Rules.Values) { if (! rule.Enabled) continue; sorted.Add(rule); } - sorted.Sort((a,b) => b.Priority - a.Priority); SortedRules = sorted.ToArray(); - Log($"Loaded {Rules.Count} total and {SortedRules.Length} enabled weather rules.", LogLevel.Debug); RulesLoaded = true; } @@ -430,7 +394,7 @@ public void UpdateForNewDay() { WorldDate date = new(Game1.year, Game1.currentSeason, Game1.dayOfMonth + 1); foreach(var entry in contexts) { var weather = Game1.netWorldState.Value.GetWeatherForLocation(entry.Key); - weather.weatherForTomorrow.Value = GetWeatherForDate(Mod.GetBaseWorldSeed(), date, entry.Value); + weather.weatherForTomorrow.Value = GetWeatherForDate(Mod.GetBaseWorldSeed(), date, entry.Value, entry.Key); } // Since we've changed weather, we need to re-copy. diff --git a/Almanac/WeatherHelper.cs b/Almanac/WeatherHelper.cs index 907ec2d..9c10ef4 100644 --- a/Almanac/WeatherHelper.cs +++ b/Almanac/WeatherHelper.cs @@ -7,6 +7,7 @@ using StardewValley; using StardewValley.GameData; +using StardewValley.GameData.LocationContexts; namespace Leclair.Stardew.Almanac; @@ -22,148 +23,141 @@ public static string GetSeasonName(int season) { }; } - public static string LocalizeWeather(string weatherID) { - return weatherID switch { - "Sun" => I18n.Weather_Sunny(), - "Rain" => I18n.Weather_Rain(), - "Wind" => I18n.Weather_Debris(), - "Storm" => I18n.Weather_Lightning(), - "Festival" => "Festival", - // 4 = Festival, Sunny - "Snow" => I18n.Weather_Snow(), - // 6 = Wedding, Sunny? - _ => I18n.Weather_Sunny() - }; - } + public static string LocalizeWeather(string weatherID) { + return weatherID switch { + "Sun" => I18n.Weather_Sunny(), + "Rain" => I18n.Weather_Rain(), + "Wind" => I18n.Weather_Debris(), + "Storm" => I18n.Weather_Lightning(), + "Festival" => "Festival", + // 4 = Festival, Sunny + "Snow" => I18n.Weather_Snow(), + // 6 = Wedding, Sunny? + _ => I18n.Weather_Sunny() + }; + } - public static string GetWeatherName(int weather) { - return weather switch { - 0 => "Sun", - 1 => "Rain", - 2 => "Wind", - 3 => "Storm", - 4 => "Festival", - 5 => "Snow", - 6 => "Wedding", - _ => "unknown" - }; - } + public static string GetWeatherName(int weather) { + return weather switch { + 0 => "Sun", + 1 => "Rain", + 2 => "Wind", + 3 => "Storm", + 4 => "Festival", + 5 => "Snow", + 6 => "Wedding", + _ => "unknown" + }; + } - public static bool IsRainy(string weatherID) { - switch (weatherID) { - case "Rain": - case "Storm": - return true; - } - return false; + public static bool IsRainy(string weatherID) { + switch (weatherID) { + case "Rain": + case "Storm": + return true; } + return false; + } - public static bool IsRainOrSnow(string weatherID) { - switch(weatherID) { - case "Rain": - case "Storm": - case "Snow": - return true; - } - return false; + public static bool IsRainOrSnow(string weatherID) { + switch(weatherID) { + case "Rain": + case "Storm": + case "Snow": + return true; } + return false; + } - public static Rectangle GetWeatherIcon(string weatherID) { - int offset = weatherID switch { - "Sun" => 0, - "Rain" => 2, - "Wind" => 1, - "Storm" => 3, - "Festival" => 0, - "Snow" => 4, - "Wedding" => 0, - _ => 0 - }; - + public static Rectangle GetWeatherIcon(string weatherID) { + int offset = weatherID switch { + "Sun" => 0, + "Rain" => 2, + "Wind" => 1, + "Storm" => 3, + "Festival" => 0, + "Snow" => 4, + "Wedding" => 0, + _ => 0 + }; return new Rectangle(448, 256 + offset * 16, 16, 16); } - public static string GetWeatherForDate(ulong seed, WorldDate date) { - return GetRawWeatherForDate(seed, date, GameLocation.LocationContext.Default); - } - - public static Random GetRandom(ulong seed, WorldDate date, string context) { - int offseed = (int) (((long) seed + date.TotalDays + context.GetHashCode()) % uint.MaxValue - int.MinValue); - Random result = new(offseed); - - int prewarm = result.Next(0, 50); - for(int j = 0; j < prewarm; j++) - result.NextDouble(); + public static string GetWeatherForDate(ulong seed, WorldDate date) { + return GetRawWeatherForDate(seed, date, Game1.currentLocation.GetLocationContext(), Game1.currentLocation.GetLocationContextId()); + } - prewarm = result.Next(0, 50); - for (int j = 0; j < prewarm; j++) - result.NextDouble(); + public static Random GetRandom(ulong seed, WorldDate date, string context) { + int offseed = (int) (((long) seed + date.TotalDays + context.GetHashCode()) % uint.MaxValue - int.MinValue); + Random result = new(offseed); + int prewarm = result.Next(0, 50); + for(int j = 0; j < prewarm; j++) + result.NextDouble(); + prewarm = result.Next(0, 50); + for (int j = 0; j < prewarm; j++) + result.NextDouble(); + return result; + } - return result; + public static string GetRawWeatherForDate(ulong seed, WorldDate date, LocationContextData context, string contextID) { + Random rnd = GetRandom(seed, date, contextID); + string result; + // TODO: Handle CopyWeatherFromLocation + + // Subtract a day from the date. This doesn't make sense until you + // consider how the vanilla game's state is set when running it's + // own weather GameStateQuery. Specifically, for festival days. + WorldDate dt = new(date); + dt.TotalDays--; + + // Replace state on Game1 so that GameStateQuery will match. + int oldDayOfMonth = Game1.dayOfMonth; + Season oldSeason = Game1.season; + int oldYear = Game1.year; + uint oldPlayed = Game1.stats.DaysPlayed; + + Game1.dayOfMonth = dt.DayOfMonth; + Game1.year = dt.Year; + Game1.season = dt.Season; + Game1.stats.DaysPlayed = (uint) (dt.TotalDays + 1); + + ModEntry.Instance.Log($"Getting weather for context {contextID} for {date.Localize()}:", StardewModdingAPI.LogLevel.Trace); + + try { + result = ExecuteConditions(context.WeatherConditions, rnd); + } finally { + Game1.dayOfMonth = oldDayOfMonth; + Game1.season = oldSeason; + Game1.year = oldYear; + Game1.stats.DaysPlayed = oldPlayed; } - public static string GetRawWeatherForDate(ulong seed, WorldDate date, LocationContextData context) { - Random rnd = GetRandom(seed, date, context.Name); + if (context == Game1.currentLocation.GetLocationContext()) + return Game1.getWeatherModificationsForDate(date, result); - string result; - - // TODO: Handle CopyWeatherFromLocation - - // Subtract a day from the date. This doesn't make sense until you - // consider how the vanilla game's state is set when running it's - // own weather GameStateQuery. Specifically, for festival days. - WorldDate dt = new(date); - dt.TotalDays--; - - // Replace state on Game1 so that GameStateQuery will match. - int oldDayOfMonth = Game1.dayOfMonth; - string oldSeason = Game1.currentSeason; - int oldYear = Game1.year; - uint oldPlayed = Game1.stats.DaysPlayed; - - Game1.dayOfMonth = dt.DayOfMonth; - Game1.year = dt.Year; - Game1.currentSeason = dt.Season; - Game1.stats.DaysPlayed = (uint) (dt.TotalDays + 1); - - ModEntry.Instance.Log($"Getting weather for context {context.Name} for {date.Localize()}:", StardewModdingAPI.LogLevel.Trace); - - try { - result = ExecuteConditions(context.WeatherConditions, rnd); + return result; + } - } finally { - Game1.dayOfMonth = oldDayOfMonth; - Game1.currentSeason = oldSeason; - Game1.year = oldYear; - Game1.stats.DaysPlayed = oldPlayed; + private static string ExecuteConditions(IEnumerable conditions, Random rnd = null) { + //GameStateQuery.PickRandomValue(rnd); + foreach(var entry in conditions) { + if (GameStateQuery.CheckConditions(entry.Condition, random: rnd)) { + ModEntry.Instance.Log($"Matched Condition: {entry.Weather} => {entry.Condition}", StardewModdingAPI.LogLevel.Trace); + return entry.Weather; } - - if (context == GameLocation.LocationContext.Default) - return Game1.getWeatherModificationsForDate(date, result); - - return result; } - private static string ExecuteConditions(IEnumerable conditions, Random rnd = null) { - GameStateQuery.PickRandomValue(rnd); - foreach(var entry in conditions) { - if (GameStateQuery.CheckConditions(entry.Condition, seeded_random: rnd)) { - ModEntry.Instance.Log($"Matched Condition: {entry.Weather} => {entry.Condition}", StardewModdingAPI.LogLevel.Trace); - return entry.Weather; - } - } - - return "Sun"; - } + return "Sun"; + } - public static string GetRawWeatherForDate(ulong seed, int day) { - return GetRawWeatherForDate(seed, day, GameLocation.LocationContext.Default); - } + public static string GetRawWeatherForDate(ulong seed, int day) { + return GetRawWeatherForDate(seed, day, Game1.currentLocation); + } - public static string GetRawWeatherForDate(ulong seed, int day, LocationContextData context) { - WorldDate date = new(); - date.TotalDays = day; - return GetRawWeatherForDate(seed, date, context); - } + public static string GetRawWeatherForDate(ulong seed, int day, GameLocation location) { + WorldDate date = new(); + date.TotalDays = day; + return GetRawWeatherForDate(seed, date, location.GetLocationContext(), location.GetLocationContextId()); + } } From e1ceb2331a13acbdb95533860568e23036d9a706 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 27 Mar 2024 03:12:26 -0400 Subject: [PATCH 07/39] Update AssetManager --- Almanac/AssetManager.cs | 121 +++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 65 deletions(-) diff --git a/Almanac/AssetManager.cs b/Almanac/AssetManager.cs index e440e23..9591d15 100644 --- a/Almanac/AssetManager.cs +++ b/Almanac/AssetManager.cs @@ -39,7 +39,7 @@ public AssetManager(ModEntry mod) { public void Invalidate() { Loaded = false; - Mod.Helper.Content.InvalidateCache(asset => asset.AssetName.StartsWith(EventPath)); + Mod.Helper.GameContent.InvalidateCache(asset => asset.Name.StartsWith(EventPath)); } private void OnAssetRequested(object? sender, AssetRequestedEventArgs e) { @@ -70,48 +70,40 @@ private void OnAssetRequested(object? sender, AssetRequestedEventArgs e) { priority: AssetLoadPriority.Low ); } - #region IAssetLoader - public bool CanLoad(IAssetInfo asset) - { - return - asset.Name.IsEquivalentTo(CropOverridesPath) || - asset.Name.IsEquivalentTo(FishOverridesPath) || - asset.Name.IsEquivalentTo(LocalNoticesPath) || - asset.Name.IsEquivalentTo(NPCOverridesPath); - } - public T Load(IAssetInfo asset) - { - if (asset.Name.IsEquivalentTo(CropOverridesPath)) - { + #region IAssetLoader + public bool CanLoad(IAssetInfo asset) { + return + asset.Name.IsEquivalentTo(CropOverridesPath) || + asset.Name.IsEquivalentTo(FishOverridesPath) || + asset.Name.IsEquivalentTo(LocalNoticesPath) || + asset.Name.IsEquivalentTo(NPCOverridesPath); + } + public T Load(IAssetInfo asset) { + if (asset.Name.IsEquivalentTo(CropOverridesPath)) { var data = new Dictionary(); - return (T)(object)data; + return (T) (object) data; + } + if (asset.Name.IsEquivalentTo(FishOverridesPath)) { + var data = new Dictionary(); + return (T) (object) data; + } + if (asset.Name.IsEquivalentTo(LocalNoticesPath)) { + var data = new Dictionary(); + return (T) (object) data; + } + if (asset.Name.IsEquivalentTo(NPCOverridesPath)) { + var data = new Dictionary(); + return (T) (object) data; } - if (asset.Name.IsEquivalentTo(FishOverridesPath)) - { - var data = new Dictionary(); - return (T)(object)data; - } - if (asset.Name.IsEquivalentTo(LocalNoticesPath)) - { - var data = new Dictionary(); - return (T)(object)data; - } - if (asset.Name.IsEquivalentTo(NPCOverridesPath)) - { - var data = new Dictionary(); - return (T)(object)data; - } - return (T)(object)null; - } + return (T) (object) null; + } #endregion #region IAssetEditor - public bool CanEdit(IAssetInfo asset) - { - if (asset.Name.StartsWith(EventPath)) - { + public bool CanEdit(IAssetInfo asset) { + if (asset.Name.StartsWith(EventPath)) { Load(asset.Locale); if (ModEvents == null) return false; @@ -123,7 +115,7 @@ public bool CanEdit(IAssetInfo asset) } return false; } - + public void Edit(IAssetData asset) { if (!asset.Name.StartsWith(EventPath)) return; @@ -134,17 +126,16 @@ public void Edit(IAssetData asset) { string end = bits[^1]; if (!ModEvents.TryGetValue(end, out var events) || events == null) return; - var editor = asset.AsDictionary(); - foreach (var entry in events) - editor.Data[entry.Key] = entry.Localize(Mod.Helper.Translation); - } - - #endregion -private void Load(string? locale) -{ - if (Loaded && Locale == locale) - return; - try { + var editor = asset.AsDictionary(); + foreach (var entry in events) + editor.Data[entry.Key] = entry.Localize(Mod.Helper.Translation); + } + + #endregion + private void Load(string? locale) { + if (Loaded && Locale == locale) + return; + try { ModEvents = Mod.Helper.ModContent.LoadLocalized>>("assets/events.json"); } catch (Exception ex) { Mod.Log("Unable to load custom mod events.", ex: ex); @@ -153,27 +144,27 @@ private void Load(string? locale) Loaded = true; Locale = locale; -} + } -public struct EventData { - public static readonly Regex I18N_SPLITTER = new(@"{{(.+?)}}", RegexOptions.Compiled); + public struct EventData { + public static readonly Regex I18N_SPLITTER = new(@"{{(.+?)}}", RegexOptions.Compiled); - public string Id { get; set; } - public string[] Conditions { get; set; } - public string[] Script { get; set; } + public string Id { get; set; } + public string[] Conditions { get; set; } + public string[] Script { get; set; } - public string Key => $"{Id}/{string.Join("/", Conditions)}"; - public string RealScript => string.Join("/", Script); + public string Key => $"{Id}/{string.Join("/", Conditions)}"; + public string RealScript => string.Join("/", Script); - public string Localize(ITranslationHelper helper) { - string id = Id; + public string Localize(ITranslationHelper helper) { + string id = Id; - return I18N_SPLITTER.Replace(RealScript, match => { - string key = match.Groups[1].Value; - if (key.StartsWith('.')) - key = $"event.{id}{key}"; - return helper.Get(key).ToString(); - }); + return I18N_SPLITTER.Replace(RealScript, match => { + string key = match.Groups[1].Value; + if (key.StartsWith('.')) + key = $"event.{id}{key}"; + return helper.Get(key).ToString(); + }); + } } - } From a12dcd030c7578113394dec2deaa824df8eedc62 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 27 Mar 2024 03:19:17 -0400 Subject: [PATCH 08/39] Update Fish --- Almanac/Fish/FishManager.cs | 2 +- Almanac/Fish/FishingPage.cs | 12 ++++++------ Almanac/Fish/VanillaProvider.cs | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Almanac/Fish/FishManager.cs b/Almanac/Fish/FishManager.cs index 56e200e..4d21b35 100644 --- a/Almanac/Fish/FishManager.cs +++ b/Almanac/Fish/FishManager.cs @@ -243,7 +243,7 @@ public Dictionary>> GetFishLocations() continue; int[] seasons; - if (ovr.Season == Season.All) + if (ovr.Season == Common.Enums.Season.All) seasons = new int[] { 0, 1, 2, 3 }; else seasons = new int[] { (int) ovr.Season }; diff --git a/Almanac/Fish/FishingPage.cs b/Almanac/Fish/FishingPage.cs index f3b601b..2cdda3a 100644 --- a/Almanac/Fish/FishingPage.cs +++ b/Almanac/Fish/FishingPage.cs @@ -299,7 +299,7 @@ public override void LoadState(FishingState state) { if (Tank == null) return null; - var urchin = Utility.CreateItemByID("(O)397", 1); + var urchin = ItemRegistry.Create("(O)397", 1); FillTank(urchin, 4); builder.Text("\n\n\n\n"); @@ -673,7 +673,7 @@ public override void Update() { var selected = CurrentFish; - var sorted = Mod.Fish.GetSeasonFish(Menu.Date.Season); + var sorted = Mod.Fish.GetSeasonFish(((int)Menu.Date.Season)); sorted.Sort((a, b) => { return a.Name.CompareTo(b.Name); }); @@ -814,9 +814,9 @@ public void FillTank(Item item, int count = -1) { // Decor if (Mod.Config.DecorateFishTank) { - Tank.heldItems.Add(Utility.CreateItemByID("(O)152", 1)); - Tank.heldItems.Add(Utility.CreateItemByID("(O)390", 1)); - Tank.heldItems.Add(Utility.CreateItemByID("(O)393", 1)); + Tank.heldItems.Add(ItemRegistry.Create("(O)152", 1)); + Tank.heldItems.Add(ItemRegistry.Create("(O)390", 1)); + Tank.heldItems.Add(ItemRegistry.Create("(O)393", 1)); } // Add the Fish @@ -843,7 +843,7 @@ public void FillTank(Item item, int count = -1) { for (int i = 0; i < 4; i++) { string hat = dictionary.Keys.ElementAt(Game1.random.Next(0, dictionary.Count)); - Tank.heldItems.Add(Utility.CreateItemByID($"(H){hat}", 1)); + Tank.heldItems.Add(ItemRegistry.Create($"(H){hat}", 1)); } } } diff --git a/Almanac/Fish/VanillaProvider.cs b/Almanac/Fish/VanillaProvider.cs index 6f9bfc5..2fc7ffd 100644 --- a/Almanac/Fish/VanillaProvider.cs +++ b/Almanac/Fish/VanillaProvider.cs @@ -8,11 +8,12 @@ using Leclair.Stardew.Common; using StardewValley; -using StardewValley.GameData.FishPond; +using StardewValley.GameData.FishPonds; using StardewValley.Tools; using StardewModdingAPI; using Leclair.Stardew.Almanac.Models; +using StardewValley.ItemTypeDefinitions; namespace Leclair.Stardew.Almanac.Fish; @@ -60,7 +61,7 @@ public IEnumerable GetFish() { return null; string[] bits = data.Split('/'); - SObject obj = Utility.CreateItemByID(id, 1) as SObject; + SObject obj = new SObject(id, 1); if (bits.Length < 7 || obj is null) return null; @@ -181,17 +182,22 @@ public IEnumerable GetFish() { pondInfo = new( Initial: initial, SpawnTime: pond.SpawnTime, - ProducedItems: pond.ProducedItems.Select(x => x.ItemID).Distinct().Select(x => (x == "812" ? FishHelper.GetRoeForFish(obj) : Utility.CreateItemByID(x, 1))).ToList() + ProducedItems: pond.ProducedItems.Select(x => x.ItemId).Distinct().Select(x => (x == "812" ? FishHelper.GetRoeForFish(obj) : ItemRegistry.Create(x, 1))).ToList() ); - } + } + //Hardcoded the ids of Legendary fish in since FishingRod.isFishBossFish no longer exists. + bool legend = false; + + if(id == "(O)159"||id =="(O)160"||id=="(O)163"||id=="(O)775"||id=="(O)902"||id=="(O)900"||id=="(O)682"||id=="(O)899"||id=="(O)901"||id=="(O)898") + legend = true; return new FishInfo( - Id: id.ToString(), // bits[0], + Id: id, // bits[0], Item: obj, Name: obj.DisplayName, Description: desc, Sprite: SpriteHelper.GetSprite(obj), - Legendary: FishingRod.isFishBossFish(id), + Legendary: legend, MinSize: minSize, MaxSize: maxSize, NumberCaught: who => { From c0123d70fb3cc47139128c4ddb593067d7b04cf1 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 27 Mar 2024 03:55:11 -0400 Subject: [PATCH 09/39] Update Managers --- Almanac/Managers/LuckManager.cs | 14 ++++---- Almanac/Managers/NoticesManager.cs | 53 +++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Almanac/Managers/LuckManager.cs b/Almanac/Managers/LuckManager.cs index 6578e20..a2c67d4 100644 --- a/Almanac/Managers/LuckManager.cs +++ b/Almanac/Managers/LuckManager.cs @@ -229,7 +229,7 @@ public double GetModifiedLuckForDate(ulong seed, WorldDate date) { #region Events - public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, GameStateQuery.GameState state, string? key = null) { + public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, Common.GameStateQuery.GameState state, string? key = null) { if (notice == null) return null; @@ -241,7 +241,7 @@ public double GetModifiedLuckForDate(ulong seed, WorldDate date) { return null; // Season Validation - if (notice.ValidSeasons != null && !notice.ValidSeasons.Contains(Season.All) && !notice.ValidSeasons.Contains((Season) date.SeasonIndex)) + if (notice.ValidSeasons != null && !notice.ValidSeasons.Contains(Common.Enums.Season.All) && !notice.ValidSeasons.Contains((Common.Enums.Season) date.SeasonIndex)) return null; // Date Range Validation @@ -280,7 +280,7 @@ public double GetModifiedLuckForDate(ulong seed, WorldDate date) { } // Condition Validation - if (!string.IsNullOrEmpty(notice.Condition) && !GameStateQuery.CheckConditions(notice.Condition, state)) + if (!string.IsNullOrEmpty(notice.Condition) && !Common.GameStateQuery.CheckConditions(notice.Condition, state)) return null; // Get icon @@ -353,7 +353,7 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { Load(); - var state = new GameStateQuery.GameState( + var state = new Common.GameStateQuery.GameState( Random: Game1.random, Date: date, TimeOfDay: 600, @@ -525,7 +525,7 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { return new RichEvent( I18n.Page_Fortune_Event_Owl(), null, - SpriteHelper.GetSprite(new SObject(Vector2.Zero, 95)) + SpriteHelper.GetSprite(new SObject(Vector2.Zero, "95")) ); // Don't track Strange Capsule, because that relies on whether @@ -586,14 +586,14 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { return new RichEvent( I18n.Page_Fortune_Event_Ufo(), null, - SpriteHelper.GetSprite(new SObject(Vector2.Zero, 96)) + SpriteHelper.GetSprite(new SObject(Vector2.Zero, "96")) ); if (rnd.NextDouble() < 0.005) return new RichEvent( I18n.Page_Fortune_Event_Owl(), null, - SpriteHelper.GetSprite(new SObject(Vector2.Zero, 95)) + SpriteHelper.GetSprite(new SObject(Vector2.Zero, "95")) ); // Don't track Strange Capsule, because that relies on whether diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index 9d0b615..617e8b5 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -22,6 +22,8 @@ using Leclair.Stardew.Almanac.Models; using StardewValley.GameData.Movies; +using StardewValley.GameData.Shops; +using StardewValley.Internal; namespace Leclair.Stardew.Almanac.Managers; @@ -161,7 +163,7 @@ public void RegisterHook(IManifest mod, Func GetEventsForDate(int seed, WorldDate date) { Load(); - var state = new GameStateQuery.GameState( + var state = new Common.GameStateQuery.GameState( Random: Game1.random, Date: date, TimeOfDay: 600, @@ -359,23 +361,23 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { if (gathering && bush == null) bush = new(); - if (gathering && bush.inBloom(date.Season, date.DayOfMonth)) { + if (gathering && IsBlooming(date.Season, date.DayOfMonth)) { Item berry = null; if (date.SeasonIndex == 0) - berry = Utility.CreateItemByID("(O)296", 1); // Salmonberry + berry = ItemRegistry.Create("(O)296", 1); // Salmonberry else if (date.SeasonIndex == 2) - berry = Utility.CreateItemByID("(O)410", 1); // Blackberry + berry = ItemRegistry.Create("(O)410", 1); // Blackberry if (berry != null) { - bool first_day = date.DayOfMonth == 1 || !bush.inBloom(date.Season, date.DayOfMonth - 1); + bool first_day = date.DayOfMonth == 1 || !IsBlooming(date.Season, date.DayOfMonth - 1); int last = date.DayOfMonth; // If it's the first day, then we also need the last day // so we can display a nice string to the user. if (first_day) for (int d = date.DayOfMonth + 1; d <= ModEntry.DaysPerMonth; d++) { - if (bush.inBloom(date.Season, d)) + if (IsBlooming(date.Season, d)) last = d; else break; @@ -461,7 +463,7 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { // TODO: This. // NPC Weddings and Anniversaries - if ((who.isEngaged() || who.isMarried()) && who.friendshipData != null) { + if ((who.isEngaged() || who.isMarriedOrRoommates()) && who.friendshipData != null) { foreach (var entry in who.friendshipData.Pairs) { if (entry.Value == null || entry.Value.WeddingDate == null) continue; @@ -630,7 +632,7 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { ); else { - var stock = Utility.getTravelingMerchantStock((int) (Game1.uniqueIDForThisGame + (uint)date.TotalDays + 1)); + var stock = ShopBuilder.GetShopStock(Game1.shop_travelingCart); if (stock.Count > 0) { var builder = FlowHelper.Builder() .FormatText(I18n.Page_Notices_Merchant_Stock(), align: Alignment.VCenter) @@ -666,6 +668,35 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { } } } + //Copied IsBloom from Bush.cs and switched calls for current season & day to passed values + public bool IsBlooming(StardewValley.Season season, int dayOfMonth) { + if (bush.size.Value == 4) { + return bush.tileSheetOffset.Value == 1; + } + if (bush.size.Value == 3) { + bool inBloom = bush.getAge() >= 20 && dayOfMonth >= 22 && (season != StardewValley.Season.Winter || bush.IsSheltered()); + if (inBloom && bush.Location != null && bush.Location.IsFarm) { + foreach (Farmer allFarmer in Game1.getAllFarmers()) { + allFarmer.autoGenerateActiveDialogueEvent("cropMatured_815"); + } + } + return inBloom; + } + switch (season) { + case StardewValley.Season.Spring: + if (dayOfMonth > 14) { + return dayOfMonth < 19; + } + return false; + case StardewValley.Season.Fall: + if (dayOfMonth > 7) { + return dayOfMonth < 12; + } + return false; + default: + return false; + } + } #endregion From 58556a3c2553d9ad1180ce76dadc3ccbf95f3bf3 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:46:43 -0400 Subject: [PATCH 10/39] Ledger and MoreRandomEvents not found Since those two projects are not found, I removed them from the solution to get rid of the "Cannot find csproj" error. --- StardewMods.sln | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/StardewMods.sln b/StardewMods.sln index 0a6c8c8..1b46162 100644 --- a/StardewMods.sln +++ b/StardewMods.sln @@ -30,16 +30,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AlmanacDGA", "AlmanacDGA\Al EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SeeMeRollin", "SeeMeRollin\SeeMeRollin.csproj", "{A06575C4-B53E-418A-8F57-B3D4B984D01C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ledger", "Ledger\Ledger.csproj", "{B255563F-7605-4271-80D2-688C1EE8347A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCBuildings", "BCBuildings\BCBuildings.csproj", "{2481E747-58E1-4858-A4F1-4D533ABACE8F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThemeManager", "ThemeManager\ThemeManager.csproj", "{93D485FD-4438-40D7-9725-C456159A1C72}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThemeManagerExample", "ThemeManagerExample\ThemeManagerExample.csproj", "{55AAAC30-053E-44C8-9C86-87A3B90C29C1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreRandomEvents", "MoreRandomEvents\MoreRandomEvents.csproj", "{14D16DCF-1690-4A17-9449-A2325F51A790}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GiantCropTweaks", "GiantCropTweaks\GiantCropTweaks.csproj", "{19A9299F-B3C0-4ED5-95D9-94BF26A50417}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InputTest", "InputTest\InputTest.csproj", "{9E918555-5563-40E7-BDD6-5F5E1CFF0912}" @@ -76,10 +72,6 @@ Global {A06575C4-B53E-418A-8F57-B3D4B984D01C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A06575C4-B53E-418A-8F57-B3D4B984D01C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A06575C4-B53E-418A-8F57-B3D4B984D01C}.Release|Any CPU.Build.0 = Release|Any CPU - {B255563F-7605-4271-80D2-688C1EE8347A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B255563F-7605-4271-80D2-688C1EE8347A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B255563F-7605-4271-80D2-688C1EE8347A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B255563F-7605-4271-80D2-688C1EE8347A}.Release|Any CPU.Build.0 = Release|Any CPU {2481E747-58E1-4858-A4F1-4D533ABACE8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2481E747-58E1-4858-A4F1-4D533ABACE8F}.Debug|Any CPU.Build.0 = Debug|Any CPU {2481E747-58E1-4858-A4F1-4D533ABACE8F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -92,10 +84,6 @@ Global {55AAAC30-053E-44C8-9C86-87A3B90C29C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {55AAAC30-053E-44C8-9C86-87A3B90C29C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {55AAAC30-053E-44C8-9C86-87A3B90C29C1}.Release|Any CPU.Build.0 = Release|Any CPU - {14D16DCF-1690-4A17-9449-A2325F51A790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14D16DCF-1690-4A17-9449-A2325F51A790}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14D16DCF-1690-4A17-9449-A2325F51A790}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14D16DCF-1690-4A17-9449-A2325F51A790}.Release|Any CPU.Build.0 = Release|Any CPU {19A9299F-B3C0-4ED5-95D9-94BF26A50417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {19A9299F-B3C0-4ED5-95D9-94BF26A50417}.Debug|Any CPU.Build.0 = Debug|Any CPU {19A9299F-B3C0-4ED5-95D9-94BF26A50417}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -129,7 +117,6 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Common\Common.projitems*{0b07a44a-9a9e-4231-b48f-d77952899deb}*SharedItemsImports = 5 Common\Common.projitems*{13180c19-64f0-4053-9fff-538b7b0b0855}*SharedItemsImports = 13 - Common\Common.projitems*{14d16dcf-1690-4a17-9449-a2325f51a790}*SharedItemsImports = 5 Common\Common.projitems*{19a9299f-b3c0-4ed5-95d9-94bf26a50417}*SharedItemsImports = 5 Common\Common.projitems*{2481e747-58e1-4858-a4f1-4d533abace8f}*SharedItemsImports = 5 Common\Common.projitems*{252e40aa-3f93-406e-bf83-6d7bb335cdad}*SharedItemsImports = 5 @@ -140,7 +127,6 @@ Global Common\Common.projitems*{9e918555-5563-40e7-bdd6-5f5e1cff0912}*SharedItemsImports = 5 Common\Common.projitems*{a06575c4-b53e-418a-8f57-b3d4b984d01c}*SharedItemsImports = 5 Common\Common.projitems*{b107224e-dcfb-437c-86b0-bf63d4b9324b}*SharedItemsImports = 5 - Common\Common.projitems*{b255563f-7605-4271-80d2-688c1ee8347a}*SharedItemsImports = 5 Common\Common.projitems*{bc9904a0-5cab-4f9f-9258-10f7334df1b5}*SharedItemsImports = 5 Common\Common.projitems*{c1067058-fe50-4819-8d93-13ad37154543}*SharedItemsImports = 5 EndGlobalSection From 11aa2bbb51bef71a0a031cdcb409eee20961ed57 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:47:06 -0400 Subject: [PATCH 11/39] Update Pages --- Almanac/ModEntry.cs | 4 +++ Almanac/Pages/DebugItemsPage.cs | 25 ++++++++---------- Almanac/Pages/NoticesPage.cs | 4 +-- Almanac/Pages/WeatherPage.cs | 46 ++++++++++++++++++++++----------- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index 86b9e24..9ae4c64 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -856,6 +856,10 @@ public bool HasIsland(Farmer who) { return Config.IslandAlwaysAvailable || who.mailReceived.Contains(Mail_Has_Island); } + public bool HasDesertAccess() { + return Game1.locationContextData.ContainsKey("Desert"); + } + public bool HasMagic(Farmer who) { return Config.MagicAlwaysAvailable || who.mailReceived.Contains(Mail_Has_Magic); } diff --git a/Almanac/Pages/DebugItemsPage.cs b/Almanac/Pages/DebugItemsPage.cs index 9ac9a56..0bf841d 100644 --- a/Almanac/Pages/DebugItemsPage.cs +++ b/Almanac/Pages/DebugItemsPage.cs @@ -15,7 +15,7 @@ using StardewValley.GameData; using StardewValley.Menus; -using SObject = StardewValley.Object; +using SDObject = StardewValley.Object; using Leclair.Stardew.Almanac.Menus; @@ -61,7 +61,7 @@ public override void ThemeChanged() { public override DebugItemsState SaveState() { var state = base.SaveState(); - state.Item = CurrentItem?.QualifiedItemID; + state.Item = CurrentItem?.QualifiedItemId; return state; } @@ -69,7 +69,7 @@ public override DebugItemsState SaveState() { public override void LoadState(DebugItemsState state) { base.LoadState(state); - var item = string.IsNullOrEmpty(state.Item) ? null : Utility.CreateItemByID(state.Item, 1, allow_null: true); + var item = string.IsNullOrEmpty(state.Item) ? null : ItemRegistry.Create(state.Item, 1, allowNull: true); SelectItem(item); } @@ -78,12 +78,12 @@ public override void LoadState(DebugItemsState state) { #region Logic public bool SelectItem(Item item) { - if (item == null || CurrentItem == null || item.QualifiedItemID != CurrentItem.QualifiedItemID) { + if (item == null || CurrentItem == null || item.QualifiedItemId != CurrentItem.QualifiedItemId) { CurrentItem = item; SetRightFlow(ItemInfo.Value); foreach(var pair in ItemNodes) { - pair.Value.Selected = pair.Key == CurrentItem?.QualifiedItemID; + pair.Value.Selected = pair.Key == CurrentItem?.QualifiedItemId; } return true; @@ -116,7 +116,7 @@ public IFlowNode[] BuildRightPage(Item item) { builder .Text("ID: ", bold: true) - .Text(item.QualifiedItemID, shadow: false); + .Text(item.QualifiedItemId, shadow: false); return builder.Build(); } @@ -126,11 +126,10 @@ public override void Update() { FlowBuilder builder = new(); ItemNodes.Clear(); - - foreach (var type in ItemDataDefinition.IdentifierLookup.Values) { + foreach (var type in ItemRegistry.ItemTypes) { builder.Text("\n").Text(type.Identifier, font: Game1.dialogueFont).Text("\n"); - foreach (string itemID in type.GetAllItemIDs()) { - var item = Utility.CreateItemByID($"{type.Identifier}{itemID}", 1, allow_null: true); + foreach (string itemID in type.GetAllIds()) { + var item = ItemRegistry.Create($"{type.Identifier}{itemID}", 1, allowNull: true); if (item == null) continue; @@ -139,11 +138,9 @@ public override void Update() { var node = new SelectableNode( sb.Build(), - width: 72, - onHover: (_, _, _) => { Menu.HoveredItem = item; - Menu.HoverText = $"{item.DisplayName}\n{item.QualifiedItemID}"; + Menu.HoverText = $"{item.DisplayName}\n{item.QualifiedItemId}"; return true; }, @@ -160,7 +157,7 @@ public override void Update() { HoverColor = Color.White * 0.4f }; - ItemNodes.Add(item.QualifiedItemID, node); + ItemNodes.Add(item.QualifiedItemId, node); builder.Add(node); } } diff --git a/Almanac/Pages/NoticesPage.cs b/Almanac/Pages/NoticesPage.cs index b0636cb..8510146 100644 --- a/Almanac/Pages/NoticesPage.cs +++ b/Almanac/Pages/NoticesPage.cs @@ -144,7 +144,7 @@ public override void Update() { ); // Build a map of this month's birthdays. - DisposableList? chars = null; + List? chars = null; try { chars = Utility.getAllCharacters(); } catch(Exception ex) { @@ -158,7 +158,7 @@ public override void Update() { if (chars is not null) foreach (NPC npc in chars) - if (npc.isVillager() && date.Season.Equals(npc.Birthday_Season)) { + if (npc.IsVillager && date.Season.Equals(npc.Birthday_Season)) { // Don't show villagers we can't socialize with. if (!npc.CanSocialize && !Game1.player.friendshipData.ContainsKey(npc.Name)) continue; diff --git a/Almanac/Pages/WeatherPage.cs b/Almanac/Pages/WeatherPage.cs index 0ca3cbf..e621c8e 100644 --- a/Almanac/Pages/WeatherPage.cs +++ b/Almanac/Pages/WeatherPage.cs @@ -15,6 +15,7 @@ using StardewModdingAPI.Utilities; using StardewValley; +using StardewValley.GameData.LocationContexts; namespace Leclair.Stardew.Almanac.Pages; @@ -29,6 +30,7 @@ public class WeatherPage : BasePage, ICalendarPage { private bool[] Pirates; readonly bool IsIsland; + readonly bool IsDesert; private readonly SpriteInfo? FestivalFlag; @@ -38,7 +40,7 @@ public class WeatherPage : BasePage, ICalendarPage { if (!mod.Config.ShowWeather || !mod.Config.EnableDeterministicWeather) return null; - return new(menu, mod, false); + return new(menu, mod, false, false); } public static WeatherPage? GetIslandPage(AlmanacMenu menu, ModEntry mod) { @@ -48,12 +50,23 @@ public class WeatherPage : BasePage, ICalendarPage { if (!mod.HasIsland(Game1.player)) return null; - return new(menu, mod, true); + return new(menu, mod, true, false); } - public WeatherPage(AlmanacMenu menu, ModEntry mod, bool isIsland) : base(menu, mod) { + public static WeatherPage? GetDesertPage(AlmanacMenu menu, ModEntry mod) { + if (!mod.Config.ShowWeather || !mod.Config.EnableDeterministicWeather) + return null; + + if (!mod.HasDesertAccess()) + return null; + + return new(menu, mod, false, true); + } + + public WeatherPage(AlmanacMenu menu, ModEntry mod, bool isIsland, bool isDesert) : base(menu, mod) { Seed = Mod.GetBaseWorldSeed(); IsIsland = isIsland; + IsDesert = isDesert; if (!IsIsland) FestivalFlag = new SpriteInfo( @@ -75,11 +88,11 @@ public WeatherPage(AlmanacMenu menu, ModEntry mod, bool isIsland) : base(menu, m public override void Update() { base.Update(); - Forecast = new string[ModEntry.DaysPerMonth]; - Nodes = new IFlowNode[ModEntry.DaysPerMonth]; - Festivals = IsIsland ? null : new bool[ModEntry.DaysPerMonth]; - Pirates = IsIsland ? new bool[ModEntry.DaysPerMonth] : null; - WorldDate date = new(Menu.Date); + Forecast = new string[ModEntry.DaysPerMonth]; + Nodes = new IFlowNode[ModEntry.DaysPerMonth]; + Festivals = IsIsland ? null : new bool[ModEntry.DaysPerMonth]; + Pirates = IsIsland ? new bool[ModEntry.DaysPerMonth] : null; + WorldDate date = new(Menu.Date); FlowBuilder builder = new(); List? pirateDays = IsIsland ? new() : null; @@ -90,13 +103,16 @@ public override void Update() { if (!IsIsland) builder.FormatText(I18n.Festival_About(Utility.getSeasonNameFromNumber(date.SeasonIndex))); - var context = IsIsland ? GameLocation.LocationContext.Island : GameLocation.LocationContext.Default; - - - for (int day = 1; day <= ModEntry.DaysPerMonth; day++) { - date.DayOfMonth = day; - string weather = Forecast[day - 1] = Mod.Weather.GetWeatherForDate(Seed, date, context); + string contextID; + if (IsIsland) contextID = "Island"; + else if (IsDesert) contextID = "Desert"; + else contextID = "Default"; + LocationContextData context = Game1.locationContextData[contextID]; + for (int day = 1; day <= ModEntry.DaysPerMonth; day++) { + date.DayOfMonth = day; + bool shown = forecastLength == -1 || date.TotalDays - today <= forecastLength; + string weather = Forecast[day - 1] = shown ? Mod.Weather.GetWeatherForDate(Seed, date, context, contextID): ""; if (IsIsland) { bool pirates = Pirates![day - 1] = shown && day % 2 == 0 && ! WeatherHelper.IsRainOrSnow(weather); if (pirates) @@ -247,7 +263,7 @@ public void DrawUnderCell(SpriteBatch b, WorldDate date, Rectangle bounds) { int day = date.DayOfMonth - 1; - if (Forecast[day] != -1) + if (Forecast[day] != "") Utility.drawWithShadow( b, Menu.background, From a0586346c0326e7f496846caac70b12716cb38cc Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 17:38:59 -0400 Subject: [PATCH 12/39] Update Helpers --- Almanac/FishHelper.cs | 4 ++-- Almanac/NoticesHelper.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Almanac/FishHelper.cs b/Almanac/FishHelper.cs index 2941220..b5f6717 100644 --- a/Almanac/FishHelper.cs +++ b/Almanac/FishHelper.cs @@ -104,8 +104,8 @@ public static SObject GetRoeForFish(SObject fish) { ColoredObject result = new ColoredObject("812", 1, color); result.name = fish.Name + " Roe"; result.preserve.Value = SObject.PreserveType.Roe; - result.preservedParentSheetIndex.Value = fish.ItemID; - result.Price += fish.Price / 2; + result.preservedParentSheetIndex.Value = fish.QualifiedItemId; + result.Price += fish.sellToStorePrice() / 2; return result; } diff --git a/Almanac/NoticesHelper.cs b/Almanac/NoticesHelper.cs index b229ae8..fe13dca 100644 --- a/Almanac/NoticesHelper.cs +++ b/Almanac/NoticesHelper.cs @@ -26,7 +26,7 @@ public static bool CanVisitIsland(NPC who, WorldDate date) { try { Game1.dayOfMonth = date.DayOfMonth; - Game1.currentSeason = date.Season; + Game1.currentSeason = date.SeasonKey; result = IslandSouth.CanVisitIslandToday(who); @@ -43,7 +43,7 @@ public static bool CanVisitIsland(NPC who, WorldDate date) { if (Utility.isFestivalDay(date.DayOfMonth, date.Season)) return null; - if (date.Season == "winter" && date.DayOfMonth >= 15 && date.DayOfMonth <= 17) + if (date.SeasonKey == "winter" && date.DayOfMonth >= 15 && date.DayOfMonth <= 17) return null; if (Game1.getLocationFromName("IslandSouth") is not IslandSouth isle) @@ -67,7 +67,7 @@ public static bool CanVisitIsland(NPC who, WorldDate date) { try { Game1.dayOfMonth = date.DayOfMonth; - Game1.currentSeason = date.Season; + Game1.currentSeason = date.SeasonKey; foreach (NPC npc in chars) { if (IslandSouth.CanVisitIslandToday(npc)) { @@ -87,7 +87,7 @@ public static bool CanVisitIsland(NPC who, WorldDate date) { List visitors = new(); if (rnd.NextDouble() < 0.4) { for (int i = 0; i < 5; i++) { - string who = Utility.GetRandom(valid, rnd); + string who = valid[rnd.Next(valid.Count)]; if (who != null && ! young.Contains(who)) { valid.Remove(who); visitors.Add(who); @@ -127,7 +127,7 @@ public static bool CanVisitIsland(NPC who, WorldDate date) { } for(int i = 0; i < 5 - visitors.Count; i++) { - string who = Utility.GetRandom(valid, rnd); + string who = valid[rnd.Next(valid.Count)]; if (who != null && ! young.Contains(who)) { valid.Remove(who); visitors.Add(who); From 0d5ed716932655304988ebc763ca5f93eb217ff7 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 17:39:33 -0400 Subject: [PATCH 13/39] Update Mod files Update files that begin with Mod --- Almanac/ModAPI.cs | 36 +++++++++++++++--------------------- Almanac/ModEntry.cs | 16 ++++++++-------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Almanac/ModAPI.cs b/Almanac/ModAPI.cs index 7a8fc78..0c552bf 100644 --- a/Almanac/ModAPI.cs +++ b/Almanac/ModAPI.cs @@ -13,6 +13,7 @@ using StardewValley.Locations; using Leclair.Stardew.Almanac.Models; +using StardewValley.GameData.LocationContexts; namespace Leclair.Stardew.Almanac { @@ -554,42 +555,35 @@ public string GetWeatherForDate(WorldDate date, GameLocation location) { string val; - if (location is Desert) + if (location.InDesertContext()) val = "Desert"; - else { - GameLocation.LocationContext ctx = location.GetLocationContext(); - switch(ctx) { - case GameLocation.LocationContext.Default: - val = "Default"; - break; - case GameLocation.LocationContext.Island: - val = "Island"; - break; - default: - throw new ArgumentException("Invalid location context"); - } - } - + else if (location.InIslandContext()) + val = "Island"; + else if (location.InValleyContext()) + val = "Default"; + else + throw new ArgumentException("Invalid location context"); return GetWeatherForDate(date, val); } public string GetWeatherForDate(WorldDate date, string context = "Default") { - GameLocation.LocationContext ctx; + LocationContextData ctx; switch(context) { case "Default": - ctx = GameLocation.LocationContext.Default; + ctx = Game1.locationContextData["Default"]; break; case "Island": - ctx = GameLocation.LocationContext.Island; + ctx = Game1.locationContextData["Island"]; break; case "Desert": - return "Sun"; + ctx = Game1.locationContextData["Desert"]; + break; default: throw new ArgumentException("Invalid location context"); } - int weather = Mod.Weather.GetWeatherForDate(Mod.GetBaseWorldSeed(), date, ctx); - return WeatherHelper.GetWeatherStringID(weather); + string weather = Mod.Weather.GetWeatherForDate(Mod.GetBaseWorldSeed(), date, ctx, context); + return weather; } #endregion diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index 9ae4c64..4e9f5bc 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -276,17 +276,17 @@ private void OnDayStarted(object? sender, DayStartedEventArgs e) { tomorrow.TotalDays++; // Main Weather - Game1.weatherForTomorrow = Weather.GetWeatherForDate(seed, tomorrow, GameLocation.LocationContext.Default); + Game1.weatherForTomorrow = Weather.GetWeatherForDate(seed, tomorrow, Game1.locationContextData["Default"], "Default"); if (Game1.IsMasterGame) - Game1.netWorldState.Value.GetWeatherForLocation(GameLocation.LocationContext.Default).weatherForTomorrow.Value = Game1.weatherForTomorrow; + Game1.netWorldState.Value.GetWeatherForLocation("Default").weatherForTomorrow.Value = Game1.weatherForTomorrow; // Island Weather if (Game1.IsMasterGame && Utility.doesAnyFarmerHaveOrWillReceiveMail("Visited_Island")) { - var ctx = GameLocation.LocationContext.Island; + var ctx = Game1.locationContextData["Island"]; - Game1.netWorldState.Value.GetWeatherForLocation(ctx) + Game1.netWorldState.Value.GetWeatherForLocation("Island") .weatherForTomorrow.Value = Weather - .GetWeatherForDate(seed, tomorrow, ctx); + .GetWeatherForDate(seed, tomorrow, ctx, "Island"); } } } @@ -343,7 +343,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { Log($" Query: {query}"); if (seed != -1) Log($" Seed: {seed}"); - Log($"Result: {GameStateQuery.CheckConditions(query, rnd: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); + Log($"Result: {Common.GameStateQuery.CheckConditions(query, rnd: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); }); Helper.ConsoleCommands.Add("al_update", "Invalidate cached data.", (name, args) => { @@ -359,7 +359,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { Log($" - Which: {Game1.whichFarm}", LogLevel.Info); Log($" - Fish Override: {farm.getMapProperty("FarmFishLocationOverride")}", LogLevel.Info); } - Log($" - Fish Sample: {Game1.currentLocation.getFish(0f, 0, 4, Game1.player, 0, Microsoft.Xna.Framework.Vector2.Zero).Name}", LogLevel.Info); + Log($" - Fish Sample: {Game1.currentLocation.getFish(0f, "", 4, Game1.player, 0, Microsoft.Xna.Framework.Vector2.Zero).Name}", LogLevel.Info); }); Helper.ConsoleCommands.Add("al_retheme", "Reload all themes.", ThemeManager.PerformReloadCommand); @@ -384,7 +384,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { ulong seed = GetBaseWorldSeed(); WorldDate date = new(Game1.Date); for (int i = 0; i < 4 * 28; i++) { - string weather = Weather.GetWeatherForDate(seed, date, GameLocation.LocationContext.Default); + string weather = Weather.GetWeatherForDate(seed, date, Game1.locationContextData["Default"], "Default"); Log($"Date: {date.Localize()} -- Weather: {weather}"); date.TotalDays++; } From e3414c7ea3c6d9b20803162a05dbeb907c06b460 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 17:43:56 -0400 Subject: [PATCH 14/39] Update Query Patches --- Almanac/Patches/GameStateQuery_Patches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Almanac/Patches/GameStateQuery_Patches.cs b/Almanac/Patches/GameStateQuery_Patches.cs index 66b1da2..9f4be65 100644 --- a/Almanac/Patches/GameStateQuery_Patches.cs +++ b/Almanac/Patches/GameStateQuery_Patches.cs @@ -20,7 +20,7 @@ internal static void Patch(ModEntry mod) { try { mod.Harmony.Patch( - original: AccessTools.Method(typeof(GameStateQuery), nameof(GameStateQuery.query_IS_FESTIVAL_DAY)), + original: AccessTools.Method(typeof(GameStateQuery), nameof(GameStateQuery.DefaultResolvers.IS_FESTIVAL_DAY)), transpiler: new HarmonyMethod(typeof(GameStateQuery_Patches), nameof(query_IS_FESTIVAL_DAY_Transpiler)) ); } catch (Exception ex) { From 3769004644d567462db198ad90d30156f25b7ea4 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 18:25:31 -0400 Subject: [PATCH 15/39] Update Menus --- Almanac/Menus/BookCollectionMenu.cs | 2 +- Almanac/Menus/BookMenu.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Almanac/Menus/BookCollectionMenu.cs b/Almanac/Menus/BookCollectionMenu.cs index 233f069..3ee08f0 100644 --- a/Almanac/Menus/BookCollectionMenu.cs +++ b/Almanac/Menus/BookCollectionMenu.cs @@ -101,7 +101,7 @@ private void DiscoverBooks() { if (value == null || !value.Enable) continue; - bool locked = !string.IsNullOrEmpty(value.Condition) && !GameStateQuery.CheckConditions(value.Condition); + bool locked = !string.IsNullOrEmpty(value.Condition) && !Common.GameStateQuery.CheckConditions(value.Condition); if (value.Secret && locked) continue; diff --git a/Almanac/Menus/BookMenu.cs b/Almanac/Menus/BookMenu.cs index 70bf16e..fc034eb 100644 --- a/Almanac/Menus/BookMenu.cs +++ b/Almanac/Menus/BookMenu.cs @@ -21,9 +21,9 @@ namespace Leclair.Stardew.Almanac.Menus; internal class BookState { - public string LastSection; + public string? LastSection; public int TabScroll; - public Dictionary SectionStates; + public Dictionary? SectionStates; } internal class BookMenu : IClickableMenu { From 4244bf9ea29162d6f9836bd9bef7d037d20d1294 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 19:34:52 -0400 Subject: [PATCH 16/39] Alignment Update "Middle" became "VCenter", "Center" became "HCenter", and now "Center" is between "HCenter" and "VCenter" --- Almanac/Fish/FishingPage.cs | 2 +- Almanac/Managers/NoticesManager.cs | 4 ++-- Almanac/Pages/DebugItemsPage.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Almanac/Fish/FishingPage.cs b/Almanac/Fish/FishingPage.cs index 2cdda3a..5817b97 100644 --- a/Almanac/Fish/FishingPage.cs +++ b/Almanac/Fish/FishingPage.cs @@ -650,7 +650,7 @@ bool OnHover(IFlowNodeSlice slice, int x, int y) { foreach(var item in pond.ProducedItems) builder .Text("\n ") - .Sprite(SpriteHelper.GetSprite(item), 2f, align: Alignment.Middle, extra: item) + .Sprite(SpriteHelper.GetSprite(item), 2f, align: Alignment.VCenter, extra: item) .Text(" ") .Text(item.DisplayName, extra: item); diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index 617e8b5..2fc9a0d 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -362,7 +362,7 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { bush = new(); if (gathering && IsBlooming(date.Season, date.DayOfMonth)) { - Item berry = null; + Item? berry = null; if (date.SeasonIndex == 0) berry = ItemRegistry.Create("(O)296", 1); // Salmonberry @@ -403,7 +403,7 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { // Festivals if (Mod.Config.NoticesShowFestivals && Utility.isFestivalDay(date.DayOfMonth, date.Season)) { - var data = Game1.temporaryContent.Load>("Data\\Festivals\\" + date.Season + date.DayOfMonth); + var data = Game1.temporaryContent.Load>("Data\\Festivals\\" + date.SeasonKey + date.DayOfMonth); if (data.ContainsKey("name") && data.ContainsKey("conditions")) { string name = data["name"]; string[] conds = data["conditions"].Split('/'); diff --git a/Almanac/Pages/DebugItemsPage.cs b/Almanac/Pages/DebugItemsPage.cs index 0bf841d..76ff6e3 100644 --- a/Almanac/Pages/DebugItemsPage.cs +++ b/Almanac/Pages/DebugItemsPage.cs @@ -100,11 +100,11 @@ public IFlowNode[] BuildRightPage(Item item) { var sprite = SpriteHelper.GetSprite(item); - builder.Text(item.DisplayName, fancy: true, align: Alignment.Center); + builder.Text(item.DisplayName, fancy: true, align: Alignment.HCenter); builder.Text("\n\n"); builder - .Sprite(sprite, 1f, Alignment.Bottom | Alignment.Center) + .Sprite(sprite, 1f, Alignment.Bottom | Alignment.HCenter) .Text(" ") .Sprite(sprite, 2f, Alignment.Bottom) .Text(" ") From 2ef6d1da86d66bc7576b9f95519078c9289640fa Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 22:40:15 -0400 Subject: [PATCH 17/39] Update Common --- Almanac/Menus/AlmanacMenu.cs | 2 +- Common/CombinedRingSpriteInfo.cs | 33 +++++++++++++----------- Common/GameStateQuery.cs | 36 +++++++++++++------------- Common/SpriteHelper.cs | 43 +++++++++++++++++--------------- Common/StringTokenizer.cs | 2 +- 5 files changed, 61 insertions(+), 55 deletions(-) diff --git a/Almanac/Menus/AlmanacMenu.cs b/Almanac/Menus/AlmanacMenu.cs index 5618d33..7b207c5 100644 --- a/Almanac/Menus/AlmanacMenu.cs +++ b/Almanac/Menus/AlmanacMenu.cs @@ -311,7 +311,7 @@ public bool ChangeSeason(int delta) { public void SetErrorState() { SetLeftFlow(null); SetRightFlow(FlowHelper.Builder() - .Texture(Game1.mouseCursors, new Rectangle(268, 470, 16, 16), 3f, align: Alignment.HCenter | Alignment.VCenter) + .Texture(Game1.mouseCursors, new Rectangle(268, 470, 16, 16), 3f, align: Alignment.Center) .Text(" ") .FormatText(I18n.Page_Error(), font: Game1.dialogueFont, align: Alignment.VCenter) .Text("\n\n") diff --git a/Common/CombinedRingSpriteInfo.cs b/Common/CombinedRingSpriteInfo.cs index af93796..183208c 100644 --- a/Common/CombinedRingSpriteInfo.cs +++ b/Common/CombinedRingSpriteInfo.cs @@ -17,16 +17,19 @@ public class CombinedRingSpriteInfo : SpriteInfo { public CombinedRingSpriteInfo(CombinedRing ring) : base(Game1.objectSpriteSheet, Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, ring.indexInTileSheet.Value, SObject.spriteSheetTileSize, SObject.spriteSheetTileSize)) { Ring = ring; } + Texture = data.texture; + BaseSource = data.GetSourceRect(0); + } public override void Draw(SpriteBatch batch, Vector2 location, float scale, Vector2 size, int frame = -1, Color? baseColor = null, Color? overlayColor = null, float alpha = 1) { - Draw(batch, location, scale, frame, size.X, baseColor, overlayColor, alpha); - } - public override void Draw(SpriteBatch batch, Vector2 location, float scale, int frame = -1, float size = 16, Color? baseColor = null, Color? overlayColor = null, float alpha = 1) { int firstIdx = Ring.combinedRings[0].indexInTileSheet.Value; int secondIdx = Ring.combinedRings[1].indexInTileSheet.Value; //location.Y -= 8 * scale; + var secondData = Utility.GetItemDataForItemID(Ring.combinedRings[1].QualifiedItemID); + + //location.Y -= 8 * scale; Vector2 Origin = new(2f, 1f); @@ -34,9 +37,6 @@ public override void Draw(SpriteBatch batch, Vector2 location, float scale, int Rectangle p1 = Game1.getSourceRectForStandardTileSheet(Texture, firstIdx, 16, 16); p1.X += 5; p1.Y += 7; - p1.Width = 4; - p1.Height = 6; - batch.Draw( texture: Texture, position: location + new Vector2(5f, 7f) * scale, @@ -48,14 +48,14 @@ public override void Draw(SpriteBatch batch, Vector2 location, float scale, int effects: SpriteEffects.None, layerDepth: 1f ); + effects: SpriteEffects.None, + layerDepth: 1f + ); // Part Two - Left Ring Slice p1.X++; p1.Y += 4; - p1.Width = 3; - p1.Height = 1; - batch.Draw( texture: Texture, position: location + new Vector2(6f, 6f) * scale, @@ -67,15 +67,15 @@ public override void Draw(SpriteBatch batch, Vector2 location, float scale, int effects: SpriteEffects.None, layerDepth: 1f ); + effects: SpriteEffects.None, + layerDepth: 1f + ); // Part Three - Right Ring Arc Rectangle p2 = Game1.getSourceRectForStandardTileSheet(Texture, secondIdx, 16, 16); p2.X += 9; p2.Y += 7; - p2.Width = 4; - p2.Height = 6; - batch.Draw( texture: Texture, position: location + new Vector2(9f, 7f) * scale, @@ -87,13 +87,13 @@ public override void Draw(SpriteBatch batch, Vector2 location, float scale, int effects: SpriteEffects.None, layerDepth: 1f ); + effects: SpriteEffects.None, + layerDepth: 1f + ); // Part Four - Right Ring Slice p2.Y += 4; - p2.Width = 3; - p2.Height = 1; - batch.Draw( texture: Texture, position: location + new Vector2(9f, 6f) * scale, @@ -105,6 +105,9 @@ public override void Draw(SpriteBatch batch, Vector2 location, float scale, int effects: SpriteEffects.None, layerDepth: 1f ); + effects: SpriteEffects.None, + layerDepth: 1f + ); // Dye Colors diff --git a/Common/GameStateQuery.cs b/Common/GameStateQuery.cs index c26171f..afb82ef 100644 --- a/Common/GameStateQuery.cs +++ b/Common/GameStateQuery.cs @@ -396,14 +396,14 @@ private static void Initialize() { if (args.Length > 1) return state => { foreach (string season in args) { - if (string.Equals(state.Date.Season, season, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(state.Date.SeasonKey, season, StringComparison.OrdinalIgnoreCase)) return true; } return false; }; string season = args[0]; - return state => string.Equals(state.Date.Season, season, StringComparison.OrdinalIgnoreCase); + return state => string.Equals(state.Date.SeasonKey, season, StringComparison.OrdinalIgnoreCase); }); RegisterCondition("YEAR", (value, state) => @@ -490,14 +490,14 @@ private static void Initialize() { return state => CheckMatchingPlayers(playerKey, state, farmer => { Stats stats = farmer.stats; - if (stats.stat_dictionary.TryGetValue(statName, out uint val)) + if (stats.Values.TryGetValue(statName, out uint val)) return val >= min; return stats.GetType().GetField(statName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public)?.GetValue(stats) is uint v && v >= min; }); }); RegisterCondition("FOUND_ALL_LOST_BOOKS", args => state => - Game1.netWorldState.Value.LostBooksFound.Value >= 21 + Game1.netWorldState.Value.LostBooksFound >= 21 ); RegisterCondition("PLAYER_COMBAT_LEVEL", (farmer, level, _) => @@ -622,11 +622,11 @@ private static void Initialize() { if (loc is null) return false; - else if (loc is Desert) + else if (loc.InDesertContext()) name = "Desert"; - else if (loc.GetLocationContext() == GameLocation.LocationContext.Default) + else if (loc.InValleyContext()) name = "Default"; - else if (loc.GetLocationContext() == GameLocation.LocationContext.Island) + else if (loc.InIslandContext()) name = "Island"; else return false; @@ -661,7 +661,7 @@ private static void Initialize() { RegisterCondition("PLAYER_IS_MARRIED", (farmer, name, _) => { if (string.Equals(name, "Any", StringComparison.OrdinalIgnoreCase)) - return farmer.isMarried(); + return farmer.isMarriedOrRoommates(); if (string.Equals(name, "Player", StringComparison.OrdinalIgnoreCase)) return farmer.team.GetSpouse(farmer.UniqueMultiplayerID).HasValue; @@ -710,7 +710,7 @@ private static void Initialize() { return _ => { int count = 0; - foreach (int item in Game1.netWorldState.Value.MuseumPieces.Values) { + foreach (string item in Game1.netWorldState.Value.MuseumPieces.Values) { var obj = new SObject(item, 1); if (obj.Type is null) continue; @@ -759,14 +759,14 @@ private static void Initialize() { if (name != "NightMarket") return false; - return state.Date.Season == "winter" && state.Date.DayOfMonth >= 15 && state.Date.DayOfMonth <= 17 && state.TimeOfDay >= 1700; + return state.Date.SeasonKey == "winter" && state.Date.DayOfMonth >= 15 && state.Date.DayOfMonth <= 17 && state.TimeOfDay >= 1700; }); RegisterCondition("IS_PASSIVE_FESTIVAL_TODAY", (name, state) => { if (name != "NightMarket") return false; - return state.Date.Season == "winter" && state.Date.DayOfMonth >= 15 && state.Date.DayOfMonth <= 17; + return state.Date.SeasonKey == "winter" && state.Date.DayOfMonth >= 15 && state.Date.DayOfMonth <= 17; }); RegisterCondition("IS_FESTIVAL_DAY", (offset, state) => { @@ -849,7 +849,7 @@ private static void Initialize() { ); RegisterCondition("PLAYER_HAS_ITEM", (farmer, id, _) => - farmer.hasItemInInventory(id, 1) + farmer.Items.Contains(ItemRegistry.Create(id, 1)) ); RegisterCondition("PLAYER_HAS_CAUGHT_FISH", (farmer, fish, _) => @@ -891,7 +891,7 @@ private static void Initialize() { // TODO: Handle Desert var ctx = loc.GetLocationContext(); - var wtr = Game1.netWorldState.Value.GetWeatherForLocation(ctx); + var wtr = Game1.netWorldState.Value.GetWeatherForLocation(loc.GetLocationContextId()); bool wind = wtr.isDebrisWeather.Value; bool storm = wtr.isLightning.Value; @@ -908,11 +908,11 @@ private static void Initialize() { case 3: // Storm return storm; case 4: // Festival - return ctx == GameLocation.LocationContext.Default && Utility.isFestivalDay(state.Date.DayOfMonth, state.Date.Season); + return ctx == Game1.locationContextData["Default"] && Utility.isFestivalDay(state.Date.DayOfMonth, state.Date.Season); case 5: // Snow return snow; case 6: // Wedding - return ctx == GameLocation.LocationContext.Default && Game1.weddingToday; + return ctx == Game1.locationContextData["Default"] && Game1.weddingToday; } return false; @@ -920,7 +920,7 @@ private static void Initialize() { }); RegisterCondition("LOCATION_SEASON", args => state => - state.Date.Season == args[1] + state.Date.SeasonKey == args[1] ); RegisterCondition("LOCATION_IS_MINES", args => state => @@ -942,9 +942,9 @@ private static void Initialize() { return false; if (loc is Desert) ctx = "Desert"; - else if (loc.GetLocationContext() == GameLocation.LocationContext.Default) + else if (loc.GetLocationContext() == Game1.locationContextData["Default"]) ctx = "Default"; - else if (loc.GetLocationContext() == GameLocation.LocationContext.Island) + else if (loc.GetLocationContext() == Game1.locationContextData["Island"]) ctx = "Island"; else return false; diff --git a/Common/SpriteHelper.cs b/Common/SpriteHelper.cs index fa019a9..96220e1 100644 --- a/Common/SpriteHelper.cs +++ b/Common/SpriteHelper.cs @@ -12,6 +12,9 @@ using StardewModdingAPI; using StardewValley; +using StardewValley.Buildings; +using StardewValley.ItemTypeDefinitions; +using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Projectiles; @@ -251,10 +254,10 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch if (ts.Equals("CustomFurniture.CustomFurniture")) { // TODO: More advanced furniture with layers. if (Helper != null) - return Helper.Reflection.GetField(item, "texture", required: false)?.GetValue() ?? Furniture.furnitureTexture; + return Helper.Reflection.GetField(item, "texture", required: false)?.GetValue() ?? Game1.content.Load(Furniture.furnitureTextureName); } - return Furniture.furnitureTexture; + return Game1.content.Load(Furniture.furnitureTextureName); } // Fence @@ -263,7 +266,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch // Wallpaper if (item is Wallpaper wallpaper) { - var moddata = wallpaper.GetModData(); + var moddata = wallpaper.GetSetData(); Texture2D? texture = null; if (moddata != null) { try { @@ -378,9 +381,9 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch if (item is Boots || item is Ring) { int idx; if (item is Boots boots) - idx = boots.indexInTileSheet.Value; + idx = boots.ParentSheetIndex; else - idx = ((Ring) item).indexInTileSheet.Value; + idx = ((Ring) item).ParentSheetIndex; return Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, idx, tileSize, tileSize); } @@ -390,8 +393,8 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch int idx; switch (clothing.clothesType.Value) { - case (int) Clothing.ClothesType.SHIRT: - idx = clothing.indexInTileSheetMale.Value; + case Clothing.ClothesType.SHIRT: + idx = clothing.ParentSheetIndex; return new Rectangle( idx * 8 % 128, @@ -399,9 +402,9 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch 8, 8 ); - case (int) Clothing.ClothesType.PANTS: + case Clothing.ClothesType.PANTS: int tiles = FarmerRenderer.pantsTexture.Width / 192; - idx = clothing.indexInTileSheetMale.Value; + idx = clothing.ParentSheetIndex; return new Rectangle( 192 * (idx % tiles), @@ -413,7 +416,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch // Hat if (item is Hat hat) { - int idx = hat.which.Value; + int idx = hat.ParentSheetIndex; return new Rectangle( idx * 20 % FarmerRenderer.hatsTexture.Width, idx * 20 / FarmerRenderer.hatsTexture.Width * 20 * 4, @@ -475,7 +478,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch // Furniture if (item is Furniture furniture) { - Texture2D texture = Furniture.furnitureTexture; + Texture2D texture = ItemRegistry.GetData(furniture.QualifiedItemId).GetTexture(); if (ts.Equals("CustomFurniture.CustomFurniture")) { // TODO: More advanced furniture with layers. @@ -517,8 +520,8 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch Texture2D? texture = null; if (moddata != null) { try { - texture = Game1.content.Load(moddata.); - } catch (Exception) { /* no-op * / } + texture = Game1.content.Load(moddata.Name); + } catch (Exception) { /* no-op */ } } if (texture == null) @@ -603,9 +606,9 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch if (item is Boots || item is Ring) { int idx; if (item is Boots boots) - idx = boots.indexInTileSheet.Value; + idx = boots.ParentSheetIndex; else - idx = ((Ring) item).indexInTileSheet.Value; + idx = ((Ring) item).ParentSheetIndex; return new SpriteInfo( Game1.objectSpriteSheet, @@ -618,8 +621,8 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch int idx; switch (clothing.clothesType.Value) { - case (int) Clothing.ClothesType.SHIRT: - idx = clothing.indexInTileSheetMale.Value; + case Clothing.ClothesType.SHIRT: + idx = clothing.ParentSheetIndex; Rectangle source = new( idx * 8 % 128, idx * 8 / 128 * 32, @@ -637,9 +640,9 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch isPrismatic: clothing.isPrismatic.Value ); - case (int) Clothing.ClothesType.PANTS: + case Clothing.ClothesType.PANTS: int tiles = FarmerRenderer.pantsTexture.Width / 192; - idx = clothing.indexInTileSheetMale.Value; + idx = clothing.ParentSheetIndex; return new SpriteInfo( FarmerRenderer.pantsTexture, @@ -656,7 +659,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch // Hat if (item is Hat hat) { - int idx = hat.which.Value; + int idx = hat.ParentSheetIndex; return new SpriteInfo( FarmerRenderer.hatsTexture, diff --git a/Common/StringTokenizer.cs b/Common/StringTokenizer.cs index 76e5334..62edc0a 100644 --- a/Common/StringTokenizer.cs +++ b/Common/StringTokenizer.cs @@ -183,7 +183,7 @@ public static bool Handle_PositiveAdjective(string input, IGameState state, out } public static bool Handle_Season(string input, IGameState state, out string result) { - result = state.Date.Season; + result = state.Date.SeasonKey; return true; } From 5de3a95bdc5a7ec5399feb7f1536ea4271b5d175 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:06:46 -0400 Subject: [PATCH 18/39] Update Crops --- Almanac/Crops/CropPage.cs | 17 ++++++----- Almanac/Crops/VanillaProvider.cs | 51 +++++++++++++++----------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Almanac/Crops/CropPage.cs b/Almanac/Crops/CropPage.cs index 8dac542..5a1a2ba 100644 --- a/Almanac/Crops/CropPage.cs +++ b/Almanac/Crops/CropPage.cs @@ -38,10 +38,10 @@ public class CropState : BaseState { public class CropPage : BasePage, ICalendarPage, ITab { - private readonly static Tuple[] FERTILIZERS = new Tuple[] { - new(465, 0.10), - new(466, 0.25), - new(918, 0.33) + private readonly static Tuple[] FERTILIZERS = new Tuple[] { + new("(O)465", 0.10), + new("(O)466", 0.25), + new("(O)918", 0.33) }; private List[]? LastDays; @@ -223,9 +223,9 @@ public CropPage(AlmanacMenu menu, ModEntry mod) : base(menu, mod) { FertComponents = new(FERTILIZERS.Length); for (int i = 0; i < FERTILIZERS.Length; i++) { - int id = FERTILIZERS[i].Item1; + string id = FERTILIZERS[i].Item1; - SObject? obj = id == -1 ? null : new(FERTILIZERS[i].Item1, 1); + SObject? obj = id == "" ? null : new(FERTILIZERS[i].Item1, 1); Item? item = obj?.getOne(); SpriteInfo? sprite = item == null ? null : SpriteHelper.GetSprite(item); @@ -418,7 +418,10 @@ public override void Update() { break; case SeedFilter.Owned: items = new List(); - Utility.iterateAllItems(item => items.Add(item)); + Utility.ForEachItem(item => { + items.Add(item); + return true; + }); break; case SeedFilter.Disabled: default: diff --git a/Almanac/Crops/VanillaProvider.cs b/Almanac/Crops/VanillaProvider.cs index 0800362..c0cbefa 100644 --- a/Almanac/Crops/VanillaProvider.cs +++ b/Almanac/Crops/VanillaProvider.cs @@ -13,6 +13,8 @@ using StardewValley.GameData; using StardewModdingAPI; +using StardewValley.GameData.Crops; +using StardewValley.GameData.GiantCrops; namespace Leclair.Stardew.Almanac.Crops; @@ -29,7 +31,7 @@ public VanillaProvider(ModEntry mod) { public int Priority => 0; public IEnumerable GetCrops() { - Dictionary crops = Game1.content.Load>(@"Data\Crops"); + Dictionary crops = Game1.content.Load>(@"Data\Crops"); List result = new(); foreach (var entry in crops) { try { @@ -43,31 +45,29 @@ public IEnumerable GetCrops() { return result; } - private CropInfo? GetCropInfo(string id, string data) { + private CropInfo? GetCropInfo(string id, CropData data) { // TODO: Create Crop instances so we can // lean more on vanilla logic. - string[] bits = data.Split('/'); - if (bits.Length < 5) + int sprite = data.SpriteIndex; + string harvestID = data.HarvestItemId; + int regrow = data.RegrowDays; + bool isTrellisCrop = data.IsRaised; + bool isPaddyCrop = data.IsPaddyCrop; + if (!Game1.objectData.ContainsKey(harvestID)) return null; - int sprite = Convert.ToInt32(bits[2]); - string harvestID = bits[3]; - int regrow = Convert.ToInt32(bits[4]); - bool isTrellisCrop = bits.Length > 7 && bits[7].Equals("true"); - if (!Game1.objectInformation.ContainsKey(harvestID)) - return null; - string[] seasons = bits[1].Split(' ', StringSplitOptions.RemoveEmptyEntries); + List seasons = data.Seasons; WorldDate? startDate = null; WorldDate? endDate = null; // TODO: Handle weird crops with a gap. - foreach (string season in seasons) { + foreach (Season season in seasons) { WorldDate start; WorldDate end; try { start = new(1, season, 1); end = new(1, season, ModEntry.DaysPerMonth); // Sanity check the seasons, just in case. - string test = start.Season; - test = end.Season; + string test = start.SeasonKey; + test = end.SeasonKey; } catch (Exception) { ModEntry.Instance.Log($"Invalid season for crop {id} (harvest:{harvestID}): {season}", LogLevel.Warn); return null; @@ -88,21 +88,18 @@ public IEnumerable GetCrops() { // so we want to show that rather than the seed. Item item; if (sprite == 23) - item = Utility.CreateItemByID(id, 1); + item = ItemRegistry.Create(id, 1); else - item = Utility.CreateItemByID(harvestID, 1); + item = ItemRegistry.Create(harvestID, 1); // Phases - int[] phases = bits[0].Split(' ').Select(val => Convert.ToInt32(val)).ToArray(); - // Stupid hard coded bullshit. - // TODO: Wait for the new context tag / data bit and check that. - bool paddyCrop = harvestID == "271" || harvestID == "830"; + int[] phases = [.. data.DaysInPhase]; // Phase Sprites // We need an extra sprite for the finished crop, // as well as one for regrow if that's enabled. SpriteInfo[] sprites = new SpriteInfo[phases.Length + 1 + (regrow > 0 ? 1 : 0)]; // Are we dealing with colors? Color? color = null; - string[]? colorbits = regrow <= 0 && bits.Length > 8 ? bits[8].Split(' ') : null; + string[]? colorbits = regrow <= 0 && data.TintColors.Count>0 ? data.TintColors.ToArray(): null; if (colorbits != null && colorbits.Length >= 4 && colorbits[0].Equals("true")) { // Technically there could be many colors, but we just use // the first one. @@ -132,33 +129,33 @@ public IEnumerable GetCrops() { bool isGiantCrop = false; SpriteInfo? giantSprite = null; // Giant Crops - var giantCrops = Game1.content.Load>(@"Data\GiantCrops"); + var giantCrops = Game1.content.Load>(@"Data\GiantCrops"); if (giantCrops != null && giantCrops.TryGetValue(harvestID, out var gcdata) && gcdata != null) { isGiantCrop = true; giantSprite = new( Game1.content.Load(gcdata.Texture), new Rectangle( - gcdata.Corner.X, gcdata.Corner.Y, + gcdata.TexturePosition.X, gcdata.TexturePosition.Y, gcdata.TileSize.X * 16, (gcdata.TileSize.Y + 1) * 16 ) ); } return new( - Convert.ToString(id), + id, item, item.DisplayName, SpriteHelper.GetSprite(item), + isTrellisCrop, isGiantCrop, giantSprite, new Item[] { new SObject(id, 1) }, - isTrellisCrop, phases, - regrow, - paddyCrop, sprites, + regrow, + isPaddyCrop, startDate, endDate ); From 0096fb81be2ea799ef4c118ed68ffa9362e1b5c3 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:11:36 -0400 Subject: [PATCH 19/39] Update MenuFishTank --- Almanac/MenuFishTank.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Almanac/MenuFishTank.cs b/Almanac/MenuFishTank.cs index 857c2e3..81519a9 100644 --- a/Almanac/MenuFishTank.cs +++ b/Almanac/MenuFishTank.cs @@ -39,7 +39,7 @@ public class MenuFishTank : FishTankFurniture { private Rectangle _Inside; public MenuFishTank(Rectangle bounds) - : base(2304, Vector2.Zero) { + : base("(F)2304", Vector2.Zero) { _Bounds = bounds; _Inside = new Rectangle( @@ -104,7 +104,7 @@ public void draw(SpriteBatch batch, float alpha = 1) { return; // Update our fish - updateWhenCurrentLocation(Game1.currentGameTime, Game1.currentLocation); + updateWhenCurrentLocation(Game1.currentGameTime); RenderHelper.WithScissor(batch, SpriteSortMode.FrontToBack, _Inside, () => { From 5bc33101e3c781a89464f4ba6aaeb2b0a54594e9 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:20:44 -0400 Subject: [PATCH 20/39] Rectangle shenanigans --- Almanac/Overlays/InventoryOverlay.cs | 2 +- Common/UI/Overlay/BaseOverlay.cs | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Almanac/Overlays/InventoryOverlay.cs b/Almanac/Overlays/InventoryOverlay.cs index ae2fb62..71b797d 100644 --- a/Almanac/Overlays/InventoryOverlay.cs +++ b/Almanac/Overlays/InventoryOverlay.cs @@ -185,7 +185,7 @@ protected override void HandleUpdateTicked() { AddButtonsToMenu(); } - protected override void ReceiveGameWindowResized(Rectangle NewViewport) { + protected override void ReceiveGameWindowResized(xTile.Dimensions.Rectangle NewViewport) { base.ReceiveGameWindowResized(NewViewport); MoveUIElements(); diff --git a/Common/UI/Overlay/BaseOverlay.cs b/Common/UI/Overlay/BaseOverlay.cs index 4e7db7c..7b63362 100644 --- a/Common/UI/Overlay/BaseOverlay.cs +++ b/Common/UI/Overlay/BaseOverlay.cs @@ -26,7 +26,7 @@ public class BaseOverlay : IOverlay, IDisposable where T : IClickableMenu { private bool hasOtherKeys; private readonly int ScreenID; - private Rectangle LastViewport; + private xTile.Dimensions.Rectangle LastViewport; #region Lifecycle @@ -36,7 +36,7 @@ protected BaseOverlay(T menu, Mod mod, bool? assumeUI = null) { AssumeUIMode = assumeUI; ScreenID = Context.ScreenId; - LastViewport = Game1.uiViewport.ToXna(); + LastViewport = Game1.uiViewport; RegisterEvents(); } @@ -88,7 +88,7 @@ protected virtual void ReceiveButtonsChanged(object? sender, ButtonsChangedEvent protected virtual bool ReceiveButtonPressed(object? sender, ButtonPressedEventArgs e) { return false; } - protected virtual void ReceiveGameWindowResized(Rectangle NewViewport) { } + protected virtual void ReceiveGameWindowResized(xTile.Dimensions.Rectangle NewViewport) { } #endregion @@ -143,9 +143,8 @@ private void OnUpdateTicked(object? sender, UpdateTickedEventArgs e) { var view = Game1.uiViewport; if (view.Width != LastViewport.Width || view.Height != LastViewport.Height) { - Rectangle rect = view.ToXna(); - ReceiveGameWindowResized(rect); - LastViewport = rect; + ReceiveGameWindowResized(view); + LastViewport = view; } HandleUpdateTicked(); From a2d76a0a72a8358c9510794197519c79bcae2caa Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:21:02 -0400 Subject: [PATCH 21/39] It builds now!! --- Almanac/Almanac.csproj | 1 - Almanac/manifest.json | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Almanac/Almanac.csproj b/Almanac/Almanac.csproj index c07698b..e53797e 100644 --- a/Almanac/Almanac.csproj +++ b/Almanac/Almanac.csproj @@ -1,7 +1,6 @@ - C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley Almanac 0.18.1 Leclair.Stardew.Almanac diff --git a/Almanac/manifest.json b/Almanac/manifest.json index 0916c97..0938918 100644 --- a/Almanac/manifest.json +++ b/Almanac/manifest.json @@ -1,4 +1,11 @@ { + /* + | This file is automatically updated by ModManifestBuilder + | when the project is compiled. + | + | Changes made to this file may be overwritten. + | + */ "$schema": "https://smapi.io/schemas/manifest.json", "UniqueID": "leclair.almanac", "Name": "Almanac", @@ -32,4 +39,4 @@ "UpdateKeys": [ "Nexus:11022@Main" ] -} \ No newline at end of file +} From 2d66a7607e5e86730037df7e8109cb6f12dcbfcc Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:29:41 -0400 Subject: [PATCH 22/39] REVERT: Remove Mods Paths Removed Mods Paths lines from common.targets so the mods would build in my Steam folder --- _build/common.targets | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/_build/common.targets b/_build/common.targets index 45902ae..60a68cc 100644 --- a/_build/common.targets +++ b/_build/common.targets @@ -28,10 +28,7 @@ - - $(GamePath)\$(ModSet) - $(SolutionDir)\_releases - + From e55f2c9b4f09efb70175974f36eff5487d23af8d Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:31:51 -0400 Subject: [PATCH 23/39] (O) identifier unneeded --- Almanac/Crops/CropPage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Almanac/Crops/CropPage.cs b/Almanac/Crops/CropPage.cs index 5a1a2ba..5efa34b 100644 --- a/Almanac/Crops/CropPage.cs +++ b/Almanac/Crops/CropPage.cs @@ -39,9 +39,9 @@ public class CropState : BaseState { public class CropPage : BasePage, ICalendarPage, ITab { private readonly static Tuple[] FERTILIZERS = new Tuple[] { - new("(O)465", 0.10), - new("(O)466", 0.25), - new("(O)918", 0.33) + new("465", 0.10), + new("466", 0.25), + new("918", 0.33) }; private List[]? LastDays; From 2d90b8de1a9a94e5125ff0c893d0083bf99fde93 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Mon, 1 Apr 2024 03:50:55 -0400 Subject: [PATCH 24/39] Add Green Rain --- Almanac/WeatherHelper.cs | 20 +++++--------------- Almanac/assets/Menu.png | Bin 117133 -> 132420 bytes Almanac/i18n/default.json | 1 + 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/Almanac/WeatherHelper.cs b/Almanac/WeatherHelper.cs index 9c10ef4..a0792c1 100644 --- a/Almanac/WeatherHelper.cs +++ b/Almanac/WeatherHelper.cs @@ -29,31 +29,19 @@ public static string LocalizeWeather(string weatherID) { "Rain" => I18n.Weather_Rain(), "Wind" => I18n.Weather_Debris(), "Storm" => I18n.Weather_Lightning(), - "Festival" => "Festival", - // 4 = Festival, Sunny + "Festival" => I18n.Weather_Festival(), "Snow" => I18n.Weather_Snow(), // 6 = Wedding, Sunny? + "GreenRain" => I18n.Weather_Green(), _ => I18n.Weather_Sunny() }; } - public static string GetWeatherName(int weather) { - return weather switch { - 0 => "Sun", - 1 => "Rain", - 2 => "Wind", - 3 => "Storm", - 4 => "Festival", - 5 => "Snow", - 6 => "Wedding", - _ => "unknown" - }; - } - public static bool IsRainy(string weatherID) { switch (weatherID) { case "Rain": case "Storm": + case "GreenRain": return true; } return false; @@ -64,6 +52,7 @@ public static bool IsRainOrSnow(string weatherID) { case "Rain": case "Storm": case "Snow": + case "GreenRain": return true; } return false; @@ -78,6 +67,7 @@ public static Rectangle GetWeatherIcon(string weatherID) { "Festival" => 0, "Snow" => 4, "Wedding" => 0, + "GreenRain" => 5, _ => 0 }; return new Rectangle(448, 256 + offset * 16, 16, 16); diff --git a/Almanac/assets/Menu.png b/Almanac/assets/Menu.png index ed561d7de4c0c8f3a07c99ba700da0e8dcc39b13..a6b20a4fe037523ec81cd88712d2c59944f7bfa9 100644 GIT binary patch literal 132420 zcmaHRcUV)));A!CfP#X6h!CWz7>e{71q3+=u~MXj9+1#GgoGj>P1;qI76cW~Q9|z} z^rAp03JEPVDN+)8Nc}kH-ur&vAMf++=h@HRGkeyWHNUlH_RLx<>4AmGIqu8cEG#VN zOz#;zVqsy6KDp)3oIa^R3SHwpd9VgPGBIF54_{e7DX>HDnBQSxsZ8ggIdPnnx%}?g z1hTM*O#FSbUK;(jby6u3bk{oQv9Ehjs8fI&%N(h#Mj_1G?}VC#MHdw2=j7t$79`>92KDsOlO#4{Bqcmu^(3v- z%$3aj4Bb3D??nW-J&v$=;u7KIqU9`I~?H1%D5#|l^3DgeLll+IT_R0I- z)ryi5|1=5m(v$qRP}b%TBn*86+$7W#)PXL_s+tlSS_;bQs!A&AHzjTG$j6WNuKl;;OegZ$jJCVeVvr_Bt3$H{InGnLqkIqLRA!e1E7k^T3RPO zZYkcn1w3g13=H=PatZ_b1WNsvgOOXHOMs_ekf*PY#NQm9oPC3X^dwIt{r3>O{mjk( zhp|uKe}FoHOfk&KPf=MxNzvQ;?{WRpIxy&w+y52ge{CK3B;3zU@sV4gZ*YLii9Xz= z{!4s<-T%L%zZ;%NqYV!5JW+}h%*fXz*xSt~$ka$r^5lzxtEa2BvYM*1vZlHlP}xOE z4d|q%>I!t$a#jMK0P3Qls^X%huBG;0KK~p2ty?O}Mh16{ZfV@TqpW<_$UsTm$VgT7 zu8PX7yJ|{C#{Z=?^$84e@^NwdZ{MCLegB8n@c&DzZ5ZI@6yzK5#Mc-0Un_Xv;Tz-| z=;7-paq9$7iECD#KCZr@f!F`m(Z7dm6Vh4tD3Upf9PHR7w`;>l~s)HC~IhH85^Ap-tdlr zs+N|av6|*xW8*t2hN_Z^Cju${Z6*JY)%>^W#1#L2{hv8-Quv=)zYP<~D zz3~s2tXpT2)qBtq&!3xgyz%Jb=HN(q6@qx@dEvsl^}X=nFw5<|xyNwd?xFsM3toP` zHeSP8zqKZ>xL|9KsC0P@g^OHHPh@T}Nj}WZPUH&a2!y0Dzt0mvT;$Xp6#HCw_yU*> znHzkhcx=V&n^QaL^yOeEGV_?jsOQYc$weLz2eT3RlexZ)5Niu&QXdw}%p$Rv4am$P z#E3l29A|o!^ISwRC8<;}n~)1BPj(sGNi4_#>EQC|kqkj*rvoRF$|NC~fogo4wC785 z6``cOudNyjZKU*5h;ajweWD_bpT@_CW1^a=!Q=@#aq_f&3I=mPq)#$_Bbguuq@F;B zsLoGklZFYxh~|oez1n%kbuwjPsURB^Nro%vFM#L}3g65uD4bl*6lPLGkMcSc%P?{~ zj17iVYYXY`L0K_p9T0z>hfflC2;9s@5UqS>w!UkUPBh_Z$1W1h+ZAbbj`2NUnS~SV zIzd|Gn^QxH8>rGnJ{WK-uCu;9c?u=Z1`;3vPT+L+g@i4Dd5~L#JrtQC8$S;gjIocj zBDZ=G<6~;~9&aolX%O=EHbMjeV^TW|X!!9Q45NIIX>cOPdSk*SfI**Ojw24fV3^D; z61bDVWX5lmGk#lPgbkSsAo9<0MhBo&WfH?QnPhYke?76>?jX`@iKArF5p(6J3P*~} zw9<;%?Uik*m05xrcI9Du9cD2{bU{KWl1`;ECz+d87z&gjdeR5-H}ai4WYoxto@y7s zV700INEySkqJ?jptdkcwa|gbr1VF;QzU-Lax8tGl=x$Xn?Hu>6uF|&Q?CwVdjM_e# zE~B6AOGk9BwRKXMIPG(=t#ftmC^DGY!>rt+FxQAt$O182;k-mh0SK&9p|%sX6gl&3 zOZ%JkjHs?)~B|6QV{`o=(z$tk0#3i zCSM7)JmWn8>`F7#^{h_XibnTdr!&i$$1c+tBIv;P7(tVp2hada-&V#Ph6edTJu!i@ z4(4y>A>=S>DfBA~Esyz&*;~#SMlwLbWx|XW1D+1Rbv7$nYdnDmvarhC&Ky8)Kq5jx zyqFp$usU>)dPM&b`kXQE`wN+s_~ELSo4^|*OIqz2YOFl>)NpTW@#mJz5^$_48l~$U zKgWOdGKb*hB(qx{thxTX2WAfPuKU$H<(b#tu}ilJN^#3cyt}r{&@vV0p94_|GqZDUdM7ipY4(a(AKF4}~M zU#sSa)D_OJy!TQH8SO!#duFPCzTE)rL5}E&#mHgIu@!OyL_=-b(aV@4$bG6_dty80 zxSR_Tc5G%sPfB0;7=Ps@n9|g?{(`cW9Cdq!(NQ){Azp3-GMweb_0?o4qw`5Ky5*p2 z_mHPy@M)SI@^?20dW0dc(GD;NTl z(%f}hswC<48Pn9pqnQ!H@)ywV0_10(aTWf0=9Mk}O8p*tFmj9mWo%$3?d|!UJOrJ9 zmu?LoVx23Jo`;wmZ8GlH)MnpIQal2#IH&6u0SlK)LIdihHJI(8%v72p4P2EzoK{_1 zM@k}8dhTi{*2-Bo%|pLBWe51h-*e+c6^GNiM2R$Zt9!w}NHP?sB`iocfy~hRdWOag zy2fY^d$TW!WOP)!Xo5tk^R!6r&)vMQ3h!TA0lE)w@0b%*$|1eYh*&S%>5QSC?qa|h zcdH;_to{a%PhmoJDB=|&;PlEBAB20~;*bgUlE=;lW3%4!Ym6X#&7fGIszwTM+z4?a4jlq$$OAzVb2PWs6Ykh7{JWgom&7HXMg(pe0n8 zlU5yt=u6R z_><_ZJanvUxqx@C`8Q7B0~n2!p2b@zJC$x!(I%T*V>@G4<>}15{iDQmgI~E2bD0i) znByhzz#~6Z#8)|JX)}^Pv==uaseS|tBy?YDnltI8!8&_{!^1fF%AaKJeEYBmZ(nWF zRCTJ9pA>xhT6j+AJk&S*KxVy`lts&MgK^wh3OLX+7v)WA9BZVRUC;GX>&rOk@8OEgc5)Ose}QI0<99ZZm;49j;$pCibdh*rAdm(Rn-{>3iy zFfW2X+V!d@TvjEZ5WE-WdqwrQ5YCb8I_rAD9=Y$|IVPRiCc7`4DD*0nFj?j~**2mk zB3YW!f~%8!*(RGeZ{f6W`XTun*q*#DC0g?~C12L{R%g93eX-+o%km{J$@D19$y!$L zcGoB+jil4@ud}x7cAb+<>8>@;%=5X!cVN!VLlc8K zeshN?;H51)%Hi_&250&Fn(*&JmiQ4?bR->eupt-4i5+nC5Eof0_&RWOxw4#p+)BHy zN^9RwE)sYxcJa|goyyim#7h3zOH&lDGIpzLh&BZ0UZ_$VUZph!(>Rrzz$XTZTVh^5 zD%WI;mPHffw<&Mbx(*G3Nagjy>AkYIE~lryn-qk?cW+Ld-j~=-3)WlI!a!5%vfGfC zn1gqsxcB$L`Tgl)%-gj7?sVBG`0l}J_3&WA+Yfvd@ol?e-jlM=A`oYoCCpytVh4&1 z6^rB*sugx1_npf+B2bYu6!Kt_$-x{0J(XmdFX)-mX5+BqZ7A(yYACahiREKlq~RDx zaeBnMIe>w{AL=f7tL`G{s<(e2Xb&f|>#J2(YpB9tRb0E7!-A(aJsw{0U`rO` zP<`6E<0kU&%|6x+{icC|xh$|15Lpa7ux z11J3C(x_Ie-_Dg;za{w$@mWqYMp|B2L4FTQQ8~8ndtE=R$Ey8j+96r?)Yl5Hj*h+# z{LXFH_HP$TY$3LCqL79C*U0_!{AxFjl|@zUJr>S!FJKLecG$A`TPyNcu_g&r92S1o z7f#UyRmkn>JATw8YM&X>zhALr16dpAYw|Ie6c|}Mv?Hos{6QEY&;QhNk6feNOhnfi z)rIZHJ)PnRvN|gyRf3giTv6T!vMbKjzAn%F^~1lWK&}e!?{yA{a|kS23ryamCX-f^ z+fk?9w30d@u4H>%bDG@2Ukt1~oZe2VU86QilH_rj0P-Cd31wOvdzSgs>6=_@8=s;E zi~=$*Nx$gy{%xM$==-QqdW<^VFZ0rejGvbgJOWnT(#P}1G3;d$Wu*eD+?WdlQ=Lgc znaz=OOwTL!0PXdMq@TkhYmj0De-^bGOt7%bT^j>k1V*J@cBtVN&>p=h6!ZH`L3hug zP>5%9`PcSEyz>QL3i3SPl8F>UFXnq~MDg^~D{Ewhcov-LY*^^nq$DFOW%DEWeylU@ zI5jP=>s1QU51y7?Sz>#Y+=nDa0**Tr^+ifmNRQ>BitN@%4{R2p;0ideQa@~)a9)LvSC+oYm&K{#6o`tB47ciVm9=~j!s zZj^-zxbXe&d(*lZ2PVCNga$UNkV;O#*>viV=d4o3w0^OCqc6RQ(a4+t`CriV zbqHIC2Z2Zkr26zaIG;wRGN+l_&EFMJJEL%~3*_&S4jS8h8TsD(DEchPdq0OodfjIe zUn3}iR^Ku}&(3GF=fb21r2mrbEfTn}R6$o#U6T=vzbjx^VJd5t^m)KSGV5A{;D>B) zbB8sY5i;X!Rcg8Q)xIxHLg#Z-^P-D*rp&r&Su|VPuH5dkmUf&@-m~bB;5=Q9J1%z; z5$YTfRe~av9nvW=+vlLx(P|@fy5e|Ag;$lFPD=sjUZqyd!gUAcsZ3Tu`wYgE`6T}A zfTBabXnG1{_1ALxFzt5fJv~#3<$Tw1WjEi7iOY3`pw11*`{rsvc0W0ZCJV*Hc^xiYn#B(}V`hnqT+sgoL z{Eb&0v1w_z1Qc4oxnuZ(jl>rE%g99Ah=w{6S6q&HWJx=g0U>q;V*w;Ny$kS#_>&c` zI@(w37ws8)6Tx$S8~ODC?Iu5_GX&qsTw6l%g}0Y7+GKSy`tw+LpM@Ev8eP45b7zWX zd&$kvr{jF?m|M%FG>7VJO`v^j=EacLsVz<3J5tviifNPtoHBAipa0Mfw4x=i}cMb7vF4@qK_+zeUl~*I2rSNVDIlZ z0bl=LR+NqON;^@^UZawpNE|g#F3rDUkM;Nu)QcNCiGV+T`_)YjXLC(}7z5KSXMfw0 zyqJo2c%H?Zx(Z7*2S)A|#sWEb?O*P4s=*9j~;m@Fj zFWYOLMassET2`$U(^Pz=!0o{5#X$YTt~ZUcM^dNbNlz5TYwBNJ z&Lh$H7-9tBNQQzFJ#E%w@w)|Ur~w&xcRqa^sYq)~Jc{&Iq@la&3j@&Y)SHa9P7i&I z0GY_dGC+}ZZv0&&E9D!hA2x_+S0aBw(gEHR>PJz&eF?M0H(m5AyJQ<-W56H%)O!}9 zk6-+&Pc5h-i3>vUO8k(q!Qa4Mnaw)CTryz1$emRV)KXwof7i@+k8G~HAa|;)Ql?k7 zx_=bQs~u_1M86ghF@M!KTrc#LJTh_tAbs8Rt0@`gS)8v}$!Sl2J!0X$#eMB?MVS6t zz$10shyOm!>T_pLv3~+AaZ-G9*)x9L0v(c7uXQ}-u+VL6-3%gKcWe{HU9`(o(wQtQ z6nyFBTNdoIo{CLTp=mQ%a_1M-=+5|XExi8G!O&oJKju|_o=8?h=?D+VLD6ST#v*}ho@O-fYsR(H3 z78g=Ll)fs}YQDTKZ0J&($b>`U}Aq|n58BWfnNYGOWMEqtkp^Lx((Fjz}d!)r=#jW)uf_%uvF%)HL!mwhYzj9dUL9^$wJ`K1f0I{CvqLaq?+@MPpEdLW+JUHK|u z3qNvN@S=_)>Dl$2TbW*G#dY^N_n?d;RUCOhz>BeN2(^eb5W7^~rU#Z+Dk~TxX}AOx z%T#-9z$biXhdyne3Q91(gXiP%!29XjOc#6^DJ$Q_iP~dZL{etbf@h{)MFm+ZoB`8~ z0IsD}JPyhZuz^D7a~w`Fml;Q$f5X3e#ge?m=$r3zI0XqIi})Exn_hbf`wX`l9&G`%ubpwFLGIwxSl>_A28DP;-?oS zCsn$2?1O!Hqq?%m#HL(gsx17ll*n0Xp2@WqLTqx?Z(c{gN*#Z!L#oWNe}AD_2m>{V z3f0rWN!6i)R1tDki}`?}9jTmh)?NAZtrbHy-of6fgrkhF-=qKuyy~W1YY_efY!dGH zfP?~>t)Y>4T7MC zyczN@2ofW3izYpaftD1kyIg|S@!L#+p5( zFSPyiLf>@5X_AlFqAe40oYw(r(67r7nOoJWdeiy%?EY~yG;VSbUAq-8%-HRu$~|EO z#7L5=&(l6SE9o35w)5lLMu1munyyby8vSdiK1orua#IqfKn zG1wy+ofZ|WbJYkh0RKel*R${g0-(NrT$sBF?7_-Wix==_ztisUe~?EgKYtj!|2j`! z*SvEPJ`mu~N?zbbbvWGNwfa2trti~|SLTiDfXyHN@!z;h1@D;+ZZIuzW9p|jF_JvBeZg$BFs~Xky-rhN;!h;Uu;==jP`PF_}Bknot zR((A{e4YnS(WPc#C7?iSNR>OTv#+NN5!1iZaRHg4kM;K`eB2U={$85GM=38S=zP$K zq6=tL#{0s6Lw~P2okVe(tXw$6DPDr6)i6tk-gd73Q+xgSqiQ^~dh5Ca+PqNRRkWOH z#x59T>XH|nZN;B@Zc3JM5!ASS@eAs(tA07wieIMQ)DTI@{gCDHhna8z>tXE9;U7oq znQJ4HE6o$U^c2*X3tbmYNln{@>n) zEL{?(yB2wGF8b1SB6mj2PR<6fD{F}#dC<|FJUW~mf+64zel0&5UzKtLTRa-P8F9t} zOEzYfRW!WaLzl*tl9C38G~OcP`XK(?`f`Piap73u~AN&TpTRXf`tdk6}m%&p2hx>t*n zD}n4OSy=Rc;yQCIRcF>}}JVsMwwr@r)O?xBuwPEVH%?K*ML>mqjhO zEb-Ux$l}cSUxOVyE}z!6KmOb>h~t)upeX9+=ga=WHNLQX$7=fJ{N*KnH@EJ-HpGfo zMUw6BU$9n6NDfGeb7@T)od=9;Q~E*nq^T2rZLvF z%cKx@x>))akD%xFyb*O(=<`dve}u_2H}auzOD7Xe+BbB@iRiLTd_i_ z8PU74N>ty)2l5h)>Owvs^(z9YMqt(b7uc@ z5UcqUb(=iz3NG1!P19@mwdS4a?(s?RBCDyJs6R3EwVkxe!1A`!)qQiJOy%(XvS__PCqdFGH`IPi8y=BhRqPWN6@U;d$G%W3GiNMJx znh1U08WbhOyI<+j^S)c7s`KKuIB#bp@<$!$?cWjhH%4I%5sH@f*`KYav4mbh@@7{;B#_Xvq^pr{8tB}F534#tBHR#DCm(~Txe=L~AsR6WueR~`GzxpK zQF7-~KSJL3VPuYF9_-#0P;2a4`o5=UlQiV0esEwNW=VPinC=#gBIl4+1D61;2jTRU z8~ul}kz5Dxgi{eIi=CE9^sLC$-!!thQ^+v7HnM3y2Kd0;&<_;hc{6s;K`cc#|;G51oAe@Id z`OI%9SzA~G99_28s=7{mR;=*=fhZb1yuwJv>Qr~ehmqypiH!}t#;p5ju;fsyGq<_GgCa(T-Z6O$nh&A#X0yN5 z?|JVPUYjkQhwDFc?^CagdVshnTh_!>b_bubZ|Bf!#b`TKy&}<+XZnQ{fkzCB!ne7niR9no~4Z%_r_KI(*{=pQWY0tyu3u3r2K7X?}+< zIpFh@BgF`q_zH7sshUwl?i}@|tz$F?+X{+K@Wu}$RJ&luxx0Ia3p!0bv-zFn4D@XT zLMw_6+qyjFURoQ9IJhkC*b;bGgL!Kh6!_q{k!V-c724tqZd2P5A4yt09uaB;s!HwI ztg@;CNo!Jys#_&eM;uWia)H?7kR=1OT5t$!0GV_Wyhom-QvB_ud)P2bhB~4Sy=S&_ zHY2nh_v$VvUWdP&UYS0pB^tz&B$GT{k#jz;eOtWXgTZ!nV=08~hh@;QxNW#W+^SAF zYoJ^9^5~0@DqN+3`w{S&q!-6BKMYck0M4?l#It{7!q>{9@vkd?^WB)}E)F=w_?NN3 zqzOMUDa#1iw4<)>wK`1R}ak#7szwX z-r#=>Du7D8xsp4QZ)eX|fmifY?U?sJr@*D2X@oew_=r_IpY)y>#a%1=gQR!YBdms1 z?_*{~0HJf9`*opy!Pn8w-!gKCPaV+wL;UMmqHo1_p9fAk*o?*uH@IpwLrqeY)Xkh5 ztKL^hp5zUJ_hSSlz+aCCKc%R($swSSV=!lUEy%_Ev&k2GU5_0&SDnWz39(fwKk$iP z-8>*wK|9$*BMN*ceAkK$m%k=qInya5+);MHi`Pd%bh76f4#OF`v+=Az?|r;8cl~Lr zdud2YPe35IRakH672!X4 z3)IE-%CLSbrE+o3hR|gOT^EBSpt884m|MR{>E-^?SQSR-7z$c(KHUBkV5e*lq(`eo z&Jh}#i3{GCEiie!4%6vt%a~?-pJhO4RHj(y(S~C^BfMh4>=D0B_NU?LmV@BEoU?Tc zb1Q9v3u!<}uR!9E@yM;gU zmjPF0Zb}Q9exhf#j?0d8-vh(S?yIk~-94Shx1P_`dwuoDb?FN|DEW?Ksf;{q4VdPd zBR@B0p15_uV0k$sSux+fy1wTj>H6R>Z(xnu$idL+ z{NqbE;Arx7pY3v{ja1^q>3rIOBKl+|^_5p+ynacw#Ua)f;(`UV4h|+*Kl5?^e7)0` zAVJs>-J{hN^`Ig_!=IU)o1T?_hJ9}Z2`t@s6}6ozXY5+(vY+zBz#K;Lf^3y+sEAMX z-bYgYDCIMjMLpkhep9E9xwsuF)>ZOZl~Ee){oy1Tj?&!PwpD))VY^jMntsEqBdC*^ zG(IM@m2nB@gcQ$h@UBr`G_zB;%xg5C>Ye?Noo37LIhOfAF}z{>V*96AHYr`b>#X=6 z%>!mH)yX5OHrb-N-z<$B*nWRP=$b3U$eiq#(VAZd`?FQQzS|-;QC9mY{w%1WQNc?KnQ|uN=Pu1z#gWq>ON|S#7xQ^$77m~FXF;n z&QCuFh*hJ$ZVNIn2Nstm%&7qou?5ew>7SH!?4!?0oZft^ zSQN1@!^@$cz>7pVh3s*z@hIA(HsWTawAQ3hcw*7O!0T3iKbS|yWf8Nn*8}1%l^?m(l6*KGT-}x4mi=O; zzMV577%^pzzOxi2ZNn(?96M|7y{OTK>^&;4!A*1v=E}nS@8|ykn3~G;ZzEdu!F*!7 z^B9c9fXao(+dCv-J=iX<`t_`#PiC9u+fb$-z4q$8kWAYAVxs}HK`V?qEAAK+*(T_Y zC5L297B6-UqtoV9%Bw@emcX|6^<@L4`bIbanTfz2yA@Do#H^)@ZF;bw!J2{woQTt4oc@k7E!DxexC9$ z{0d*wv3p4!>yer@S}aG}N5IGVz9dKQfM;=oYcXG%{%-!%la+(ocCP25USm2UKc(KS zwdrdon*ZQ8sffpW?3^B%tn1Hgm#z~0^Y-q0(J1zr)BRg1pYnhqRK&u+dqvN_o_T3$Oe6YW{J5 z{3kArC_j!~Zp9|bGbG2m@DD`CpzQDUz1z#QUdQ2BcmfqYvpCIW3>%&sI^zCP9$|8n zDyQzZvkphj)7D!!sRecN3Ez+*y5|eg_-q}jaC+2aKu8&KuQEKZfF2sWFlvrk{e+!9 zW)4D}OJ-N^UihxK<1B#hE5+2%mPBh!_H0y>J~H&v@3aM_*_;v)NReBi&4;>LOQnf8 z_>bu7E%pir`~|u-;v*iah^UPR$(5WxhCTnPVq7oJjQnF5Qke?F4x-MtsN+|T`!FBD zi+@jr+JFBT5YzMEBkl$+t4H4wrd(HUoaFtL`P5xlnv#}y=qgZe>UH$w7hiy{3!kqB zv47ni%zGbY^n)07=&J<8TDVS%hW_0V~K_zu*>XLXmw8%T~?#XVEBkDj_|3ED&-cCnjlV_w}^LC@(bpt2EWiEWS;$tc6 zLe6=-ipPS4vT1ClmZ($8n>FiV*F{umE^{8t`gmjFa<4oxz<;9%6gq&aA)=AY#qTQ(Xcf$6#G0Gq`9nKX^O})sT4dFli z0*^=#-L_qzc#1EQENwaA;TONQ_*1|?eqw=piqi)eVje~D_GVx0Qvt~xg+CpHwoGU| zoH*#|v|b&Ka;1#RPyIACnP^%asP&R{IZ#`Ile;hisekWCzl+XrHV+x{G|yDm*H!TC zYFX`+n~xrIktr||wZfklrS%RxwH!noXw|fq+AH&CX*}XAW?rmF3nJAK4&`W`)Bn-M z7K9Byzq-(TQHuUp3GbJqn+t~xoALs$NoU@s^2)>rY1Nz7J)So@=Jz37cU{O^O6|u?m<`1&YVfT zz^7IUjQ{M2qJPsXU;1Vtu{H_PG3S+qQolX92yEEF^E{WM^ZQb8rU)Jr)3>vkCWzHv zEC5*Mm4B^)cEVqa+Go&YeozG7`O&+6^G(z{x&*bwtw-%6rQpK=*wqWmQg)aFHqoQ` z&F8vt@(spJPaSw~*R)#L?y5#g`ui!7Lr&{11mPCff^p z_MM{06-C|Xw8dHfqvbgqcX*WRf$JXax)^{Mr*8a&<|+M%AJ6qnBR|O~=Ng z-;-wWm?{O)9l0Sfs%8P_22$bk8`q`8CL+Q>bllXH{bQ-=>VR7&RJmwWr1$PWkuPCZ z{g4q9OP)U_qj0D;X+Rb!dg6_#7SM7OaCqc>D?`G&cRuUaDxXQdk#6)V{~y`L%eZBk zL_v>UN`ay2YxB`jqw{mJuh)uJw7l6%&2}G=GGa;xw%~uAz2u(sX+Zj@R%W2|wi2Z% zTipAWG>E2`mQ==q4T_O%CjW?4#YsnoOtw{*>~_{?U%b_CQVJZVym`{|lw!oTWXW z6iHc`Tw5iqIPOOV)8Hn=6!D$JWPV&@2(t&>nl(GH_Sn&3=F>?O=3PA)zS2P{;xzYT zC~U`}Q;JqAIUFWlbZ&^G+{D_sm|6sZuz&R407Ym(k_XUvw?TgM-^nW_b(t615YbIx z(S>Le<6i|^_TK^eKZzqFPFE@hHVDI<6kN*oP7b{n7g$%TDA8LWzZ8^T&;t^o&3I-1 zeJ+c~obhWu#73LrjAtiZO?}jZO*=l#xjMOEja!U7y0&pfiJ!QLl%wPUy*b(0)L<2C zEm!;J)`l^at#tirDit6RnU`-ub#f6f$w1X=we-ro zkp=D6^hi~axpHr-D5#U4WmM{@4-r6=$i8gNYtj)?$ZySAmWjB7l~RU>H?vGcLy z?!eHgJRLr$+10w2r*87u)3|rVvL4$k!{<+hSwPl(<}Ce!3dsXPpRMD)0Q4yW_krH$ z5&KFWD@}7I?b7g^paT##IK4B(T-Z)$M0HRpRvZ83&!IWFto<_*)^unU>!dHdM@qJeg z3IUtXQe7gKF4}k<8Cs(YCi{KAN|pO3eVyv=wbS4XYS?a+^g1M&`ERLJDEGi7AEAGx z@Rt9atsm(My#+O-AJIOQbLh4GqXJ>XS=U5J^z@8<^L6M_Fkh}TtZqF{-Jh$-R5Iyq zKtxuW(WPMiBsOSI2_kB?WgOCoA6bWIHHBn#27!`Zh%J1XyDcCb_1-t@<;;}dzWMO` zrx^!24>mFWNzf!;uD6@A{P1-mpiB?cFO{cfZtStfMJfLjy59uS24K$jGiw zx*INa752|0$z5vQ5ftC_f)DU!+)dC_)e^5LsjCn~uu?XrS!dJ48t)`w0Bmc#V-IbQ z53V2aFrGOboB6@Bn++1v5B6vdSNCz@aQPp-lWvGgd-wubxO8Xh0~RjKS18b4?L`~c zp(3!8vfGjQew43V4cWaN6aa|#6LiC$BHq|Y^qf^{tHLO25~#2h?c>@5S9SH=`Vj4$ z#%I23gKEfV1c%Ml0Ns?SUunSh2$@Mhyttx1c7ZnKP}!h|ZFHZCJl#Plcs{Yc_9rr? zr>B|sxs0ik4v`77h>s z=?u>a!6@Z)H485gB0s7cgYAIkmo%$-U~aFD*tyT@RyKU8+&w*rstYeqbL~seV-^G= z`pSRHrAO6^qk;p}eG6iVk1h(6&m%^H^_v`*GW9_(*n$o&w~}?UVN(uD1=VE%qbIp3 zne=}k?&NFVrtXcT>4|Rf7a6^O@cRo!zPRZsWTA+~tFz()RNzg2OdDgzCQnt%(=gzU z09VDps>AMs?$N|Vc-0!4SuzmD_A_+AnSgxmoT1eJ(%$^JN9QCc^MW2w_7Ux?HEfJq zjM?%@1zpb6zm6eanUX1xeRwNw?YC|3z~@2I@h?{aXz%5?{mH^T5ki(kW0o_<@Kb{T zIG}j4HP*G#`)0Si`eh;)kRa1Xva}>!f6}dwo1E8BbPNKXkCktKHophjl|apNSJY}S zqTjNc)U)m7EiB^Sa#*z|V+Ho+lb0BsQ-6wz;x9n-!N|iKjT=wl77SY6%yFe=SIvw&2#VOiiKI;y1+2(RYw_;CE zcb`paYV%pfFX!DSiKUxk9dqRVP)5vbeS1*I#VRP8vs?;rRvs3$R)AE~^*IxO=z*pA45Msu}U}f8p!S;qCR{MGz zrnb5Rv4#wLw7g*cY!}TMf ze;V;4*eOjU@TcWq0(v)&@iXH;3;4y&zd(Fgt~hg0-q*C5(_hL(YhAXp4B@*%Ho`3J*t2oJIuIb6H z4#{o_ab`g^9vfRPsA$Cv8~P~=eIKTGJod21@UiQa6(BiR*z{?=`ZQFleDo^Q51y_! z(Oz~6cihq9n7M8Px4INkzz%3O+>0YS5XfJ%%b>g6uV$Uc)R2zm z;j#mB>1`qWGdugu8hy0C_%cW9D*J!MeIJ5MuWYNx{v9jfQG^?Wj6qoP+N4RdfaP5C7Orb~wgMM;t_Vi?!kP=TNP-@UiZE-vHtTV)TEYY9KI_U8wp`0b{+ zFWfa3K!zPGtpn=Q3V=^C65i-*HZeSN`u`esn%DdGVKQ%CQ5;YgbJ_&eG^OXS7-av!8A+qwl6$A^65~0@g+_jIp^TRdI?8c7`+=^GEc+FFX2Jf z9d3F@uNQmX?+N6KjZ5|VaX{rHGR-`>e`|tv4d;dw zm(G$&knMr}g$A-q7uuz1TyQ_-QyH4)mlAgy{7~Ue*(HN9a~%z`Ao%sWI-{%C?_J~{ zsQwe$a`&EVS~-_N@k<|5gGXQVI%JX@@PbW3I{T&>`$i@L^8+_t@PebVpWNOb9k;Th zeY=DjfMne?U&~>Msas^bH&qMVT35`vpiMWQ%50UMbj_DGy>MW^^9j1`MLA4=rf;OXw6is&t$$rx8A>>_BtY$DrnCsg?)iEJ zD(Q&6ou?sBJc!1$Zp}R|)ZwuX<@iYO)2axnU_AIxf3N6FHM$(haj5x-L>Q^bq$Axd;q&st~Px31Xg9%<(DZ?OeMdlHZEP(YR8vGcABwv zT`x(QnKCl&<*rt7ksqUeiL6jo|A^TuO$RjYmTLE${!rc%Nt+FG`^V^8NG=rxTjs8JhMUvx~;)|YjK&Q zJL80th)ebZwx8JHc~_D0mJ!C&BtrvUdx2q@)hX8h!_#*^QrZ9iXNRQ7%4!&O3mF~T zDHXcKX(H<=LOI9I<`_xHJQZaeqo~{7+i`FXr)+WTW5tnqIF7S@&-?Sf-``)ZKj3=3 zuIKf9KGu^oiLM)ND z8aUybXf7Z=%J@=rBG@I^ZxVHg(cDX1kLLI&GiraxDr+iNeE~Z}D$oIeNg_(Lh zv;Ma1e`UPJpT#-p5vKpXGUy#P8Akf`Wr#2FR^U^O`Ywg9Z&UM zP8w*830bF!h<4nz-qM#hh)2~UY~}HrIY#u_t8`z?#BTw*_$~^ynLGP*?P3#H_Ncz6 zDoe6!N{?2-6HH?yx3F^cJGc>)oVlSOb@X2zUW8;oyhOPfp@pj}9Y-{z8@8u-$TCB?imkrt`Ti$| z@2}IfdUH#6LwWki+hS7tkbt}3fH5>?`V`%=yDH6MS*^84xg4^rg zdC--wWhi1P+AXGx5m>J@Dj3np{()dAUA_+5&Ti7K2L(aFH;4YQ;_(`+8s5FNsgZ z*?KbF=FE&Rgp+Mb-F`ePyt`5vb=GRW(PWL!4!9I}j(1r9iW{^un$lfTWzd2WsO9UJ zZ137;Q{V%s(YlS3`~$08gF_{qR#%b|WHn|#OO1{X-iOkCt<8}(0%L=k!_ zf;5IbX>(3V)x4;5$kz3J@8@IH?Y?@{{Eb{#`k2YRO|LS)QLoc)4>vsxP4LzG5gG-8 z?bQ{Ef!xV#tg?sg5llINKcR1Ny`bl>OzRcvT$gK~@PhLHQq%sos7qeqVi*Q24`Ke1 zB;IK~%(za=J_fq0gRje${OJ6S?<4o;O9~IA!(6qo`FbrJc6*AQ;_m{SM&~@Mpo|$3 z{p?@M;|Su9)DsEc_~Ak~Y|xHWtL+Rcc>OIA@Oi`(hdpHFM^Ih8a?PKc;eRD_a)0s0 z4j4JARM`F69m-mKXM-W8%{+5M4-kh^rdn*5;9ta!S+=Z{c*k`Kr0w0ED8X~>b7@&m z6_i6glRT>`H)5p0=QSg%!C(z&@;rvi;g?SooHFXfLq6*54Dlzc*enkbP}tY^!!{Bxspt9n7&+j>5?E z_PI(p>zvI;gl|+-*A8+WoUgh5qW?P}cd;CyqrSa<7zAK`D}p5nt0FiZy_?l#{DG8qJ z-S|~JIeg2HzYJ^M#qTZpbX!cx!mc9YBlnGo#Me_=NbBKYY^Gu;|2Om{J;bR`2g+|t z60R~-I+RMx-`L*5G5%poWUCc7cPZ6MmuTr0#n;i<`cqtz%9UPvy70y?Im2?9L^&sm6~o|LUqyydx~!E_(o#>)>^= zN7`zTv2G`v=U=CXa1xuCJVNt%z z8if7{A)1tU;Lqv++a)ZFt8|!2@<%!dN9jpT7NNhr^6sT~I*qCmh1omFFShA9!5vezZ8Tct9mr6A=or{L$kk=FH0aHT9$qYt7O-|v~A3}3^n&f4Ifz1qNrMr>;4xfgjVY^`xkVW;BFOoI@YTR4-vsltRm zc0$RL>S`;mt_1FL$?JJw9b{;WZ&LmTW_j5#r6xBfOaON5Y=U`9gKUUsjBA=kb{jEX zjv$zv+{|NMvGU&OzVrTIv2mVK&akwy1*f`tuB&?+Puv%g&q@yxE*@t1z+juzNJSrP-whNiWzIHAK7F`Scph&HmqD)TG)ut)+~alcnAhL0d7m?xN&J)O z(4bQ63C_RG^S1oJ$k4T+%A&R8GUMXK7W>(Z(mQgwDB4g5Ar?f>-xtxw?ScNPryZ$`{^A^>rZZIX5}QqRPI{2M7AI%d5d9HoRyvzC}&(DYd1ZT_T!ULBLk_* z#&B3DA~?6LH;-^7uZmh9FJ@Ba#j?<%6g^`VzxOs*lQ8z26V*MN;)qL{wuEU5~ z;E}c4s(7y6B6tlB2?!>ngh%9Z0dgEd4KuUN+M>#G1-J~O+7kyx6Mi3{(7qi3a%V!6 zT_U%+9XDx>x^sDs=GBrme<=HF#Q#jQE5Ww9dMA$J`pC z52^lbObX6>4KIC|0Hhh<>9>a94w{%(2_F!f&qgzt^JbM`LOWRIsEb3uu#m=8McfZufqTa$tYB!ku7tQIiiqVK2YgTjx`3? zD~;k0hPEA&3a0EfRd8h=#9`6K7VG;uS8^p2gK#O6%7EZc6=6>^NR!F+*u*`kvEb!l|qR}KvUOOlK zO7yPCBREVXL1>D1Zh-0WImaLRU@*%PZwk@k6Oq1-69q6_Wz{|C0OAipS|-oO>$K@V zv0ln)s;p7dv0JAgeo3m9S4Zx5Oo4(J>F+*52+o32Uz^$JfzEu`x%AtI8Bd`JUTw6G zn`T25Q}*D(FyE0t?-NIu$qP?M2U5iO(;rSjfV8fWkr!nNsY5jEkfa00%|YR-+EvD| zrFFSLFJP5_?;!l4d<>Wx>5?^<@Z2SQ3$q|vqT?Iom`R`xs%?;;jb=U1?Yx1ry4SYI za_BEQ6ugo`uhkeT^(VgJTM`yiqL2SoCzrqURq`{2xJ2q9V+;@zBkME4;G)RFXI3Mk zw~&(ovN^LAU$5h`niB48qw6}kbd%pR!1geEX8P2p-_3zc6Z(JQ#i8gqmPBWb-@hp>(a~bcCRYAE#x9$gL`@ZI^A$aYwlO_KZ4zar?9Ggg*ZZ zqRp1PE2Eiov2fR8{LgM%h?XhD0v0k~TSc1MJC5*wc6hCVQ_5XlKVZPG1jQ7!>n7ZE zDWFqcb_b*C4>G|Ltz%w(t3{j`>Ek`F)8Y>$$2U#S~6 z?_Ap|l>D+KMQEq2O&=r!Y&19ASH=#{1SX^(G$HFJ*9>h8>(w0-f8}ND8M#2V>@+U9 zQ7(ustheiljB!YhHa6!D)E`x-I{d4Syxf#acE5hXGp^&5P& zoHg~2O zHtjAq>sZU`90vR~S*Aly;DDX6Un({)?}w|4ALzKK*2OFVrP#lz62r~bNY*1TV@p5v zL=`&(2YJ|DVVqq+HzKdGs3nURdV_JQ8(zB(sD80~)*rO%2kShZ3!&>8_) zzD{1=5p@OlW|vzAVaxK>r32t!lV!O2gGF5l*^>dye+y7Mq@` zsx+qjre3;I!#L3$@}kQ%-c~ZnA@HvyH)+R;@t?fa310d?EZnyd_xSDwG z&jo>Ws!nLjqQH;AXo`Z`ZQqy?4ry3laT2<*b5rI>prjtXA_{{V8V3yQUe;|r!>bb6 zN$4r_HGrW{Hu$tOU&>*yBS`GS;z&!R@jSkvZQ`e(rmoQah9+2UFFI_WEXC&wh$po2 ziU&1tPYAd0u7)||_T0-#eim$nf2r)amFnKvWNWQFED^fwVSo252q`HQcFyn27euv3 zwX$0+%J6m|LZ_pdx1JesV`ET@XVna64*t`#D5EN9`&B*az5%fx-;FjF_PDhw zuziydvQb2u;m6}zYNvw-1U{Q93?0kMv!>FY$@?`D8*}MOdwp>3zjW^DnQ={4uPCCL z&cdBTABcB|D@POm6j?kd@91;(Klth$*AoykdK+}!rPZ6$-Qu1h!MQiX2_%PpmLLbM za8=PKK88_-b?(2(48J4XzVr^7ANOEE>-!Lp|1|BIlFX(n6dzKWjLLORP94Pf#bN9D zl2KEFhcErBVoLU55wcV#wN~w%BKf{hxw! z<_tpe=Ec4xEidC21(ruZ+taCxu~tfXez+Cc4}Z;IWP}HZLr<6<+**TpZfZy+l~tJS znJhmcw9L=u>_9z>=U#nKIqG|1fGbB~U3T^x&#m)I_S&?-*+MZt4cIeKAZ8LgYy}LJ zhrE0k5Ky`#5dz`Q_e?)~eaOofYIyUg@Bfk!f)`aLAH(DzO-8vti(mRTd=K1b3r*hu zhjFjdg<2ADywH6o&a{Bt)t9lRYaQ6TCUsMMXQU%6KZ0nMb$7U#$}L$cA$hVE9z*M# zMw!=VZTRTPC3B*^v$*18%@zT!=~GL8GF;%&q56G4Df=%4LEH4d^@m@_rGl2@3Vwq{ zpFqANG9CucsoWxX9^HUXt)rF&uoXy(i-qW)smiL4Z^fKtOYGmp#FB4zR2{9<)N9;G ziuVd;LJSCQt( zag9c;?qW;6_>Ot5tfYD^$Hse+UDclAJcnnySp(<=#{MFg+IFqK^Hvx7X`sER%KL>S zj>+Fs>urfP(+6-hea?xqdckEzNT+z?s4AqVy1ChblcA^migsXghT|NhO724GR^N_o z#l*LcaxELbhIuMZ#hCmST#>&4pC2Y?Vqe&($r;Zzk)Kr)e;7>tJKdk{GX@GVY~u+IF2So^N4YQzO0weLj{ovX6%-?k)_{6zAOzE;*V*S^zr z{lWIe4`ils9Ehf%m%!@?AIwmSfJBL$H(FN_RlMO6@z~RlX+g-xDmBC?s&CE53mbJP z<|1EbVy%*K{<189>9ZmM!8Cx!-pxP%4U1uKZ7hkwn-4nlnb zVlkI4Np*8%F9_tJrk6cYr)QC`iH}+8ob*LhV(h?}4e)I10$aASQWh0T5teW5Ak4G{ z4Fc~kx~R&Qonu_nHHwYQs;+o;bHr{coU5aAf4KnIQn`PXekePHlk@Wb8`mWORJaaQF??z2B@$yDo9!4)< zyb?%+0HsIgIu$3RPwaKroktgKA3(B}z3dAW@6TGWK0sUm9{^5e}+ha$QzQdv)cX1Lf zBgmL2D@HPUF?>!KqF`ndS*VPk-M#UkY?h?1bO6*ff+wwty6-={^t*%Sm^5MeCqqL# z59AQNNAaiRbTZ%U67;U_9tka9uu}#hBP*C?@A zUufBdnO7|W?(vbH`ISq;|8&cwQ~%oyrlQn$(g2P;Z(Sce@`$s1_Hr~+VfZ=M1nzZM zc8g_8$*CLs&w1{i5BJDD#bAMt%&_^`i!S2@Aiv|y$@9#eqhwgXKf?-FI9@m4N0zJ- z{F}dws@(dSH(9N4>BaR>xzD>5Shxa zv^6{A)JFWuW8_8Yz+yF4{FmL#TVQ5U|*^#rB3QXdZ= zqwusU#ni%{FJ&T3PU##QC$bF!Y`bdUA~?7tko0DHCvRJ*#&DR0+Iz8B6L0LL^YSR4mz58t_> z>UKud;uwl(ZobjG51}pE$6zePn=Mn;b&72{ElJ7u>Mi>|Y~pISv7qeaENrvRMxR5&2j(rb?i@OGq!m zI56#}l75dTHd8jj@=CO+I7`Wb7Nc@SU*ZOhZuHMCE5CCID$AjricbncsCYF&2OL40u~4MJ!pkJ}9^Aq&f~CWQ;U^T)Dj0yYm4g~mV7 z<=aLDho=^8JNUk0XM?zCyDfb0?H%w0cT;VoXy_5%6qqQ#{Q^YhwLxttac9nTHA0{pR?Y3;M@T;dO_E8e>;+HZvDH^0w!1mFTYnRG%W$zm^EA|Rj*AJp6H7R z3TD}d5&9}EC+_+99*b~`txe4luemYQ1fWI~Tm4`BOqU>%7vFJnnqogW+21|knq}!^ zQX^$?Jfl`9;*L9isdi&RP;JK-&fRpEWmd;>7(|m_C{H{r*l!DAaHmERoK#4FMoXlf zdslmZ&#RCV@@{}ZST}XO_Jn6x`v%_(?E0IY(o~}`D9;4QbgQ+L^SVr6%LDnI*Rq>J zOGQKV4f7O{iD$9%?YK}?^NhPs*DoLwY{S)^G((YyRlZ17ZKpHS@ z$0zPM^EPYm_eIvi})QiJ0PS%Sq)> zn`M!;#4n?6Hyg0twc81(gV?+?vu~11_%Hb>XrALB9GX3aGAvIND3^x27scdbmFvcf zfOVGQ875VLbgjt9bct!F^fwj!50(_-+ZR6x!!JH<26MuPcduO@H6tr-MF0i0KP5SK zQXuww&yp-cdUTiuA6|;|pJN_uY7k{$Z9n@g$ z<}nb|JN#7`j7S)+K*~#+^cGLGgn0Mweyyyz335YN4qa{Ov)e3hEI+ULQq<6+KrU|#Y{o$6$-F#yt;Q@ z%tJ|EUrFmTp(RL|h~gnzD0&DusLT!$yzW*!G>JcIZ5Bq}`Z{dy>s+R6C*=?AN^kP} z0GR?wJh~(mCLn2uQVLH{#EIvOBnTrrew?rtpDC(sxma7`zL;)Eyz^Go6g?!#viL&S z9fai{zqONy$x>+V(U-NHAK7{3Cu)e)YUsJk3!bcoH=;eXK{rl6Nxmj+?7Azv-Q!a! z!#8mQ?&D!ixY^J>7#-}^v1r2r$B02{i!|&RG0d}^3NxSeTc~5tj^#O~9)UHqeKwp) z!cIpFSwr73_s!`dEp>EXRPIHV05q5eCVi0sBv z?%@IbWz6R)zTu+?%M1XqbI> z4B7P}H2a={am@v|Q)Q6i#Y5&-p9+E}$iv~qY% zLOCOjLvnuKmvlJFAPOKWIJEHs&jipia^?gTlLdyBaIT=QP$F)^6W2dtw7`n>W(~?~ z%!kHEd!Od2gps5hPuicd1V7_&Kb$yNs&{|px4JW(wC3zL5TT*Hn*d{*>Us)16>;Tr zgEui7V2169KcGCZhFuVTZC&=8YHuZ8pRm3>b)U{Pp*b0(DQk zLNk3Fa<5#wZxQukr}d3xhR2ffl**sJXv+Yc&2ghS(6hf=dG32qxJtbvh?UySmC2-P zFe=TM2#tBv_hrt;gM;|ekCgP&;_Y~XO<7UVovTgo3Ji^F!=}prFH@VVj}dw;WPv`c zr_8x9V_MLKMgTh)OqAi46(OX-t-sDl+sPBM>W?DQ@>msL z2^;$IUk1qHZwt60iXAJVWr4iPKUGN7vC5d7WV&?ZsomZdS!>vYzVsiP0C@AYvaFf# zI$%JgLku6Vqe9iV_VU7$7ynI+4CL z(sGfGPaR5E3``CY368)>I|dl)qvb4v#a<`3XYH!%((_E-Z5L5{rmFRrKN@3_WYM)j z{=-?^@~}x6*O?_o_H{w`V85WkiCgjxPxT!NhAEK?o3oUM_Z(#=jvn&N3Y4K}5}5_m zH(`(4b?@Q@2GC)?QiCbf!-XlmR=K=Nzk&4usrKX%Bd(=~QV=BDmNny$X&k6Ck&b`p zORha1_0f2*rNN}Aa_Gf2_G(JzU6%uSU?^Y!W3QeJBSc>~XeE#qOFfnS-NGyBRChVM z<8EbJeN`m^j@-29Swp`yo}^QEg6_t<|Ea-GPHax1s=TxK3Xbap@$Pp4|+|p10Ng?jigZ0dE}(? ztYukk6z|I<;yI)lUb3t9Y2{xwlI4h$3-ahYoIv^pmk~-#Y)ECEpqj`x;~m~$xP?FU z2T1SP=TZ);ig{GFATVCfXAcJ*QMKsG_#CmhjaG^w5$NEsdbo1M%Mc@t7BgMA~PvwZou$_Cm8HP(82JKb@h)3L3{nt z;>s4W=LOJreqsjNDiePxfAjW*oUkfB!0TopZEvf!ikFCo6i7dklcvfVZGQJA@vQ#? z{ui2(oZY6eb0C9UXX=tf8~}^rrvT5SLjYdNPL=>E_%TS|%-iI{&%VP7A=$KyZk$5% zYxVPDnx=W&=U>Dqy|+-ZJj0=_;zI~dW9$Nm=9(FgN!p*PRG1$u8WsE!UaOL{iFfW@ z_MizQZ`X>Y_6Y2zl{@O;DufnaR7*uv7>y+LD~>+gZwFN@dTU&sAbGs8A;wkioq4i} zf#lrn*jsnzv3fDc+r;^jxnBZ+7NDMvayk*~Z!crKyA__^2( z`ThzlRZRMvbZo7+=W_LaQVw;z{+X3`QenO?~2SlNiO&mX;ksNN_PbvO0DyYSOkLOT z$_jz>i&*lj6|{T99f#jS+qX)2Ua#Z)X{Wix}*SI5@Q1dSt+=y#IZ~&b*iqGmmdLM4!GX=J79=bH7iG zDpb`o-*v%b%<1&_m*x4@4ddgGY1O8T1v%EN%2oXorw2{9CKPg$TN4tWWVndLy#Cp* zbR4@!`iL18Gv9o9ft^$O*j4=FTeYYCWJNR0%x|9F72{h=P6$l$wdSgeep4-Uad5#? zE><5 zA=Mn9BcP9_Sh#=^dnJdCZcD^<$+9CA#%CbT=_%!!6?4$ehrBbiZW!6)*3=|K&Y{R% zaR?(Zt4`%0HxS?<+6h|=rv3YK`W^JW8)f^!{(L`R%gKw_NB5{s7hBdh{6lDERCJW# zVJn$WrX;C7`Ls0pbS0}#0$v`me=Ad1JYv=Fs4RCjtl}km*qNF;h)Q8XtTgOf|9$5g zRR%|#v&Eh_8;A|MwwzY2ce;uZw{Qc88M7JRE!p(^*gv~}MeeF`TrIfM>t=lSNQ7^f zXV-+l4mD2&)>&PWGe@qBiN;KlJ{?o}sESg`3>bdWx_(|uKiel`W!54P3Wh;bgn|*S zlILoVw4DOrW5GQ3r!Z@8KIP=c^gk{v9a>o*YG1M9l_$P39A1%JnOU0FbJ6tI%N!Hqq{^l%0fbwuefWHmGh$`Qwc^y)QPC$ft4~2RvHs6(EYW z#8PWRq>K;_2)!S+^oqH9$?N<52CeHqw5DD?@6c@EoIp5_;tSP`Ng*h&a(Q(ploHC_Zg72JIhhOw84?+E)jHM@2mFh1eDlYBdJIdtFJxca7lb%pHzO86v- zP44`fyI1>3Eo3^NbrFxbdlx>a_*&WbIRt%OFb}dTas#|EhrzGBa`QMmYh;;HP(1~o zE7WXhC4wK^a~(frHeny$HFM_SHiyNHLc*QYhAFX zjCp=7Ms>(9h*=*fn}amSmB+r};|*?jzKhwf5Xw^CQPqB+Dp~xhkG`UM2zuQznLmuc#|k7X$~??ymz4@&L)u2Y9{@pidHDt zWCzb1p~30v*L%HdUU~+5x_h*$VTE%6wCSIS$u|;9sGswLAPVV&5{cG&@&eAr=6|In z4I3#3=>br^D2-Fo@5L?y!2F!aENGu-YYgU%qg3V@`rl&_fDJKq5q}-8>~I=nA(+s$ zX6G18y6r9sA}Dc%k6K)+Fv|TAA-aFCIPPYagqYm=XMlHErO>YJwL4y`6ymsM&`Oi=`7tHQEa|JWAhbr5Pn$-la|*ST|BOM&B>GnoiEZ^mi`d-7}d|m1^YXVbuKYFQl}x@aCtzSI45{HdVWq#@Y%0pVrwN>c-RY&zA>R zJuiA^NV)k#PiG}Jl-RsrcP&$C*^HW}K^Ki%h|`_f+9r+MZDbLU4qYqNN#FWczRHl< zg%^IbsHx*0!xcmTAOtt>$1J-`x0Q`7?=E_uzGdy2|3ZeMbh%o&A-_QQ7a*XZckK0h zBCQzcCw3kuuSwpplp3!;+e5r^49v!cU&fb&SDC`VnX}oDP zf0EqrC( z&JSr+NaNfDBaOJr1EH=q`M1yMp$10S#?}8$kx1StRU`+b69Bo>w>qQJHZYID59nPY zYHLjOt5fUKj^i`l4m{zU@*Gk^OLJLvX8cWSoszEUu&m_&zXFE>nXP4<2=Qnc_(hE&apQ8HkSgyT6 zY#!ees~DcFY{^br#2yKRLk)%5v5Speq{_|}7aTY|W@#kL)+T9@&uf6qDJHfRlZKe$FPNkDbJ)FWOR(PdXUAy{D80r*Irby%sq3u3Ax@sFUGnS6VhFU;H?m;FHuqzwIzJ6(lH3r#$graY{s zVg7)S-E;7qi*IiCfCoy-xD8UhzMW0Va1Hf4)t>lybk!;5CWucO>$P}ls(uGkHMBjoQ3HS2V?*0=Zs`&fCfmnM$3Lm==?R3sk)B^*AjN{rFQ#F|grbwfDm2G+cryV%n zH9LlVB?vc$mF=GwYuNWE3oE&kin5Pt{jauENbd(;rIkcw8^s$R>+INj6rRs-#aenX zx6Lis-!V15cDo;hXUM~F;HulUyPpt~Rg+?FGrhLBY+wDUO{!Sw?BUcq2mP~0wV%qf z`6BFH>aRs~elD+wYCc9zK-cd}(B#Wv`?gXuD1Ky!kc~J#cC&Pl@J*(>`EwEN`Q84J zxi> zAV9jQu!#Rc1SR`c#);t!d#M*Q{h11i2}(Qc|T(;k-Wt^2V`M_`zfg>({ zRfd$f`)Zl(5`Vj#TmW^gXUk?w;I(?(E#E#|yWnTsPK!-`XhH^T6-(U<6JB3IdDe{ir)Y`dll?=R~_ zn{5{acjngBSGjk`pKNOW%o2M~PjL}^v#|2YIKTNg%MP~sa{uv>sJm`UUJOskkX@9@ zusBjVWeq1V=KyAC>?dq<%7xuX6h7kyc^(S9LYhSLau>{>TMJMcMf1VO3M+Gc9P2R-vv@OM|m(D zlEU7+ho@IE+1o0oj*52#QeElQ}Be+jUz^yOGf zo`AUtAOfouVEIPGXXUJP%!v7QBPsK2Rods^^+6;1zb@|O+AevI_C+LdCd4|rzMe3m z{po9ec!K}$!jF%R*_W;b)P3`jmda=4K=;Mcs%>?*Zl+d*E4SXh*j}9kAupBw)zXCw z@qkqX?0zTbyWpw&PtGyV{F10ijjW1s=sT$vg2i`meLCjwo(O&Z}ylX$oD2$WA_dWc*_l8WjSeMm? zxunDLc`WW$zfgZNMY3O5c%}Zy@d8`s4XF_{541Q1T(F2PfoXC~kLVe>Xw6JjxlXS{ zCe&f|mba6WZU|}sa#Rf>@&%7p;4{FZ8{$&(4ZCK<8x6Oa}5zIIThBi zqeDNDF8X=zs}c18;fTt6gCa!LD^5NvIXAONQYY$+?5RZDHYOgXsa;Y_Znou7X6J}U z)2*`{88Kkc_9||n`iLZqZjZa?S|-UMjcAKL7{2DqQkBcySjb0<8ydK+EnOs|$M$=C zeTX(?b|oZJnt)=^J)x)#%%T1Tf#qFtQsN)g7<*?5$W>or~7)tC{T-%rajGd}I-+@^# zbSIu2_-_dU3IJPfR#501lMc{Xz@lP60)}^1AwAjNmz{7!SbYcJuaIsQq5{ZD=(@%i zhjq$#Fe<;4Zr5BuqLt!w4{Oj34is7$uJJIgV>nLZfPv0Qf9=@v}!tp4ALSMlrlI&RM@*&mNSp}L6>S1K*h-nLSHzgkKY=M`Fu6Ss!$rXmGVyPSln*G zlc)D0mx<*(w;}g>>dw8NBqYe-a<2@>r(W^(c$O4dq5;}FuT#@uao;TRc}}urmOBij z!ATkf1dM+mi(K_UrO_M4)K?Mrq?5;d$tbZ$Tnz!n!`$mpDpx0t7Hpa#1G~ho9GG31 zlwEPh_3LzPlK(liEqoa?R0U(*ya6~>D!e%@n22^aTs`#RnYW=P%}j%-h{zU#q2wGp z#r_>@)hl2^qHdyPo)oc!sf@CVhL$sqU4*wMe%WDR4%=2nrZ|bKQL15Y%4N}y1h0~- z4<^RK%aX4i2J@+KuRl5#1kF83JU8b5R44%a){q^o?XyyV+rIpiVs{1u)buQpT-OM|BD(NGp?C9-1RJh!FC%v@E(d{*`e^a$rON7LP3N;9l|6DZ+`WPIAx!AIyHCLdM(vYx|asDU| zp7kzv&+Xmz^k4HFx1WPdNNX)CrdAOH_0@u)kejuf9`}$_5z6-rZv3-@+T)gh(^7#Q zoixeK@Zna@E1KsKNjk<6Y-OmeAGn~~Mz^)2Yd?|V9OvN@>dRwvO`zgbULJ}qh2uH= zn^MZm&CI`M+txMU@T%htU+Qeq&xL{`&QvC!qbO?K(ZS{%C`Du6<>ZI~bYiG?{q|Tr z(|4Q?fSY))!89-`dq=8w)@qzCcy9Llbz{qJ{!tZDe~53gV|nA+UQ1v%U-3*k^AzOH;-eV0vfGVnY9x#bkc*RW~e5mRmlai(J%Hl+dMhtL394viWv zwQOZ~-qLw#RxQ>7(}-&9{Mkeow@_YV2NYyJQpgp5-Mqt#0C0Pp;ZU`Lkkea*I?$HW zmb{q?AFtY-yKNdWRhGI*$WI!`h`-gkb4^@x{)u+pXBGv_)AH zD}9y^`*X2{I5K`gu}(Gc6}&f46PqB{AdPc!n7pB;zdsxqK7!*^BL)2NzMIlmZClXG z*Cv6>^Kt5O^ot*taJ5NKme>7O;Vc^AvlqWAZhS714d4V;T!+raf>4SRy8@Mqo9i_v zE82mMB5{GrD^RrFFoLqH84~bNZ@IhP}Wro2>1j zep{{xrG7;7knK(p7cj5lp+=HSNi2hK)?9w3b$fX%G8(z&*3{(}ce%A(ER<}x^`si$ zK-CRJx)s~VaD2wZ2&LoK$idRF`~0worux|sVUQqoxK^;zeSAE4SxmkU##UjlOi)KV zN7Tr-bm)!1c`&B}fCj1a`@2#EGdI(wC>Cqsm+?Z>gl2s=zdl|4{F%|pdoTSm1Jv~z zCQm@&w)d)wV0YXSEbou^_y)M{tQ07VR41QkBOFxw-jN#;VY|JWlx1$zOeGIVwmXRy zqCsBIYi#5r!UrO#R6%trXPna6$f1TmLPp=(ChKkQG>L3MmJqfFVXxCinqI$Qi7QE6 z6m=9VA>o_?f-9M4z%LJ?uOH zl4Zh^Z?5q>x{lb)sHNEN7*!9{H$0!MZRiRd@`AP=6tbzI+u_SHoxmCTU4-h&_#i20 z4sm5;wOajf#Um_I1=fj#M>lBJ4XNcLJ&DsvFA#8w5#`1SB%ikqiyS6E0iBp1%Vytv zWj^pB@X6X*!EH0ZGP{`c^X4VjC+KVn{G;?<0iOHL902eL3lT+)h>teE*06oxxpW+? z<9m1`)Xx05Kd$kD&z^!BGovrVntd{%Eor3+zm_y>uv&wdX}M7H^%A)XOIHe~QOJU{ zKKcpfCEQh<9s5yl`!nk`z<@>Bk()Dnwj`LFc!OaSaRz97+j=>RsH7e!3ICRy01NIT z=t`FWCz}wEs0I>=Z%U}w20ST4^{I0GnTJu=wD0>`DG5FwvHR(;N79P5a-{<=5Pn(i zC?Ra~6KlkXHkeX!^#7PT7k?)I|NrNdP!u5vuG@7zU(d(maeoBSTy5Hh z{9Ro1%~0-~up zIR3E)rG2CQR*(3M)2ml3yW{CM{=U4x$9aKO+)V6s>GNskZk^0@@hp`0j2VSX$G{1^ zn}?+x*gsXNmg#pI7kcddMBvT7mpaYBMfaV+Q6>l8vcJzx^`{U2b4gfb#fBPb1EC+e@ z2K&R=2k14(WgnkKOf9K44PH^E`bp;H&}48;_QMH3 zD$B3p!<*}Q`GYBMj%dM^ORy<*slgH5;x(`)5bi9BzC}KFl>s`k}#oDGs7C*7zupH5(aunbk>Q z$?iWQJ_-uXn}RRv8o)Fgd{9xznP!)QG3v&S-R98d?*3yZo0JhD#&qh>iQ_%pHy_E7 zSv5cx0+7A^i6HE{X2L;}jX}lbiot0Weq;1Y)EI$V4>6{0n2DK7C~vbp6a4~b>d$c2 z*l_r(;9%wb|1L3;)?VWd0K9pTx!>MXg0C{_%i0Qok4tK!^mR_HQ}%U3zhC`y2zk0` zJf-fkWd&%+yP8r;@9lxa=E0?&tDcS@lwPC9C=+2(Qz^ppU=QRe)r}u2nl;&n3YA)g zmG)GlQs2VtgH|T*g?w;q5BwlXLbBg<^8dl9-f#3us>3c7+QVwEz3GgF0}3xS#W8-N znk&%bVx6fuok;15@r)M(-s`uA6}QiJ6;Eo`kJz4prX?s4WtDyd1i1By!A z&Rl1W4Gc_B&x8_go>^zkI3>HC8IySaWPvpGVcWvDrF;CU#ANe9PjGlr1yxLolb141 z`yZ**=P*ta9L|&d#5Psq;xcNlJs0%sz}XY6{j4@34@^ttVWee?u35hjAG{hWPmdH8 z&ui{?`6oMeP?fJ}#mTE}85REXJO9Bl#y1mNzV)da&831vj#ygt&UU9YNpd8+M{+p( zfmAfQ8^5g4JiVHz72VzgYGM0G$d9dheNEl*gY*qc{7d)yy58uclO>25+<|I1oS+!IjXn=ohlM6v~n(WM#-IV)Gjb zS-xY#sK-a{=z2Pd5L@SjQf3r~GhK;1!ri z*jcwGpO5krpoo|Q{PHOO(LXEfPm}$-0qb##o_98$q;wuL(lD`4xLJvZq{!M!xzWO^ zGh=OAqN&#aMyJmVEvptcS?!T={kT-Jq(Ibf+2;Eu=YFxBzuGC1K2@r`O)tIF_x)Mv zGkPW3%8hz{Tfet3v7+ASIfa?HPFUYzJjWW9E zQj!1E<4E<6Ed~3tCE(b^>grG)m{gZrTN>z&h0Ey51|bY4rf8YDBq)NZkZ)EX3k{wy zIqbd6b{g{9m|Oa~y;j&a%idWi@>$#K_+{Yo)_=b(tv0*NbCtQ8qN>lQ>dkwXrX9oh=tW8905_3GB7X8g zY57z7560h(xP_3r;kFpPM&TT#i64F67l+NHEO=JKR<+_?C5bOB=HUhD8X_b1pCiy~ zUYQ@fZ$*1j1!%@FUZD!%1dLbqZ7(xX;9caZ{Fub{$ee#1>+AgSW9E9!>Tc}QK4pjq zE(8=I2Cfri>cL(}ggD;H642MSpYSsRA^?H|K1qU8jJQ~Xf@~d^ebJt7g9hq=2-|uL zKS~SZ0xL=3&-8_>c^`}A<;qp3ef&L>nmB_fkHa=*`$V%EnPx@MK3kqMPn9lg-&_ic zAk2V~$i|0{?ngDIEbOTW71V}p8R2pxuaTFUAu^^zOzXjVtz$h7J;5OlhP68T=WxOKtCi+{nv=V@A}%w+V7@L-jnW08m~2#xIQ*QD0J15EPi~bQMx3Nni&|Dm%`%#G7X!S4fVj0>#06aRCpOIq$mpJ*Kaz(Ki*hx zol$$F9aIO6xIka}vd7mv3^ugs`Mhz=5AU=*()1FQd6OPLkcwfnf8JTo{cAdI!HSFE zWF96s67y?i5%MLx*)i=11vSbXX#+1Y!k0M&Hl4dXZh_!Ge#30+y8poV;AH(`PKJxJ z|1kdD;@R6gjE?p9_K&vnl5H=HP8;&rbOxOtWo#xFRRj!NeU-)RdlIvCjEe+k7F+9( z73NQGpA&JF)U@9i$ISnG4V0ZblA%7_d;i3hV+9g%2m++X_%Sv5-_+x<#osAoRU^lW zOqp5@{It63thkA;QO2DIbDlnbs68Tg(Ib3=FCLuP<&TK}p8a`M3jZa)_iVNXT5?y- zxsu;6$zk~_yYE^SBI8PP0x9ZoWX0;Nz?#t{R%rL%#%+NPF~`~djg?%p65Zv^bDJ** zqT@6fyQ_bgwY4+`EI9WR+a?>#OoV}*sZQF3xePXEmb!Su!QaD%HCB>oqLJ(tPtVZ@_vbh@_QQ6R zexcG=N|9)pk$L&Eux=_XmkR1D==*`ak}|-x!tHtxYjeNdF|Wu+@B`^BsOf#40!}bw zM=`PEl&5EQ-cmtQ{sg4Rf7I}4_iBucUu(8|cJfDc9dtw0IWxSK2i{44m!>bWIB``g zRjw3BpI*b8%jPbgi%^}@h=1G%v0a#^Y4iPlL#|}n9kp+NV)xwsE=H{4*_am+quxq0 z75T$3|KoEL;V%Mc%-`1%+-x=jJA182RQgx6|C_=dQ~p|e_RW+-z+R_T7{gn@>1Njf zEi6m&sKn)TGwum7K%)a?rA^;$_SdoD+mjc&HD9ZLix6^jF9V-?=Q81^$#nO!@T1lw8Ng9{bwX>z@_Y<44j^){(m+(s&|ZeZ-5 zTqB(wp>WUy%o-;u|6}qJx0_p?{yUHzZ(3a-j~TVr;1c!aHSJ;r_AWJy^puX}7uM|k z%bKV1CLFeB3O&$-yAiLjy<9B6*{k|u+DD-7oE<-*yV>3?yIQC8dplo-{iOOu5xoa< zD#BhP8@PXt`u=kWl|jmxQa_GYp=)+GQAf^1U6;|vKGQ5NdSdA*5Mw3@-|IDX%KjEx zg9%^A!NXIZ6W*rNUa1VdxF(i3*jnK6o9hzu?}?Li`}{QtSE+{#)v~U%{!fnt3M{H1 zllTMK5mE-z#gUoG6fdopm9|pDkw(S?N|&Y2@R&sRW`qYS26PLEZhUvYuNk{9tg-E9 z&s92bXNhYq{I)GdZ#|^hPf4N`-}RbJe7+ul;==^heCtW5l6w{9Zvk@Xm%+nvzzdM;R$x2aR^Y zp{3SH=eXvyn#z~ye{J6?6&T0~a$2Q|5Zj)n*}s{GXmU+4mv?XOhUu7$LYC>HHpI;# z`pF5F>jWy=|W0Gf_RZj@LR=paCbn|^brRz39L)zHzK(zKt z_+=upjNiees|x?}j$OcfQKg3+iy;)<9t?PWkv>1JY;^g}UFSnyWl*eH^cauV2N^1Y z^_I%C`xUsheHX8Q-AbRU)ga{Wrtp&vX|@zzIqlXab7mg4OBaZLr^KPB%_$8fj-UT2e%SqaJED0Jgtz+JX8x8qG?#=WI8S42%#NlFBN0HS zQdjw00{Ul-^NB*Gg0$`8im?ue`1pvETRx5c0EhXJmcFub>JvdRfs!N(7l48=Zj7IT zRX+(<$h-`I{HmyEUR^|6H)P?>S=EdweX5|}Aj5xy<@d|?{p%5us^@mM(y8(zSeApg z6qt6h6i1TFzVz}<`rm=tke8);PwehWUVkFM*|?uAoi{jlZA4X4@tPg-I!%ku)bok# z*r}kZ_Rm=U$4Vy6<5mG;kJfw1-!8}Pr%@pF&_|h&SS^3Xs+dwpX7Wp!Gp{C9bAcw^ znv~g*!mdNv_o9f3VTB2rv-jVX9et90q^k5Rz{kq7OFi`X_~y`lMrC;S+wOcbrCkCR zah55}YtRyJ%A{1Nk5&pngTjod&XX=3Z5;@z-1K7Fx=$>wJCJ&UCx-7!;Y#wjwaqhK zQx6lCDYW#Bp81K|WFV(@2s*JNOXz{3K9yDTkIVTeIZ;1mQ1-fAGwUGFV6V0}3rt`c zPkfst@)eRmW|BUb17V|}-GZA`lI}w=e@+a&`U#vasN-3fgopiaUWw?=yR7pnBIQE> zEOm6vo{#b{Q9DW4{}*63fYuhUA(_xWrAAaUg#ozgL|!TL17i!{58~Rnd(R}k+2tL! zRfB;fKWkr(HYy6b5hF5EDJwbGJ0sLD#iO<{$r$hasG9osbD~?_DTfU*{LZY#sW5Khh}oW!S?5dFY9C*F zLB|GDssR&1aHA&kU?rPcz*ZTMqn9}ZokSSY8Ym$d?VpbfQRI%J_E`Pub)O_&Qsm6W z*frX_NHs;?0~koe^j^Xh-din=_$UEhXqSkn+}}tOfW?78@%iXXgZ&0^e-F{eR+P1wy>c8X2r6>|TVgnZPzqMax>Jm2TNhmosy z&$?R0XZKGLX>Vwt;iE_5!4f)a%hs|H@Wl2+_8l*z3X?r6n|fN_1Z(LPeiICx1#r~+ z$f|^IzgH|M6A`{|TUQh3)!_znPq$J}Rru#IhL;eagG1KVVwblp!LD*Bg-@WemaTx{ zk^9gj>%%l@JP=Vc|lDPvoM6IuE$& zTy~f835qBmMCLzQKW3*<8W}ChoH6h|9APL_F5IA$a&xAh9|c@6le9cd-I+dbelWHU)ST&sKNVgUezfB-=jC6gDu75Dbia%kTDp(oS(_n>q@Ez$>w{E zliw$mCYC_FhQH6Cya8b1eD0Ou6ITgK<=o^H_CPVi8MH(ly|>pvcNwukz^WV=(lO2I z|DEx5eCrq$oRt0b%l)9lBaI@;gGY?z8=lQ%$U<&ePSD%T#RTM;8Fq)rYJ7BK%!)e9 z)(WMn_R4Sj^EdnzJyC7FFuPy&2U)6l=_R?RL_Pvs+p{(`>^}JNwQ@lQN@^09`JCU^ zC`0hI+}TVQky2Myw3L?>83K`Xw77t#NOek`oxLA)p|Q^3Crs*GbIH#fif97J#_E=I@T`qiMTtM?qcbGDIUOg0S()>A)r&f(= zZp82Smj`fo67ztw*AUL99wMF`STj#t2CzNAhOq+07LXQh6?MrM5t77ag`2D$OJrrk z=`WnB9zO5Z_5>cN`a}rwa;o4sJ{?A{vyMF2TeNZT??iY`I(qLDT9*syX?y=0J_Vm1 zD|}l(mo@+3t8?`3-LS-Z<~%L~<~~wKy%=vX=w)gY+Zd?a(#hLX6$u`A->dx1vvFdU z$g63oA!JJO>}E)af3Q8mKX-cWoG-oEC z5~`0Wdaj8W?VBIPR)14!KTdu^+;tf@LM;7+(raJV^4@4|krtc>d`_RQd56#ee55V+ zgx=X+x~-O7B48IFX@$s%PO?tpuliCGpYl1Cd6P_!3a|>al<4<4HiV2DtoiceU1y!@ zkf6*-d0a5#_?49yQ5!bK2azSWA4wHz3cL)Q+}zQ#l!eW4Y9cjmgQ_z6ck(!*g4Ygn zHq0c?aL3Ok*QO}~i&7PLX)%y6d$^Zl=`++G_GMo!R`@BnGAYC9RVwegd3}w?O>R%E zXq%9pvc2oClf2|Rx8%`)tdaK9OgECSNNqM~YT6@dQSR4FvG}9-$pBB#78Sqk$(aeN;MV_^gzrx z&vK-<#c(3M24)g~Ew!&|s(E$&zrFob0z7I(md_9AH-4I4)D7>QWSR~6eG=4|WedwU|huG%0E!vHZ~P^RQ`R}bGu))CqG7XX^p@PIV{1jOaV zj%b{@AGz_(Dw2FY1>3v~5_r2KB5D%1AaTifSzIn^onUVBVA4uLyUb}4VF zO{#iir2WslFe(8Owsd>VO3bF7vel1e8Z!PaF9O*Qz6B9FHVqq1oTe0crLiZ|Kwa{r zlN+niFwEE0xy?D_*tK^B#3fCfevem8?_M5h`<|kyEKWL-b2KYhhZ1`9i`!8nJ?`Tp zdZ1T1ny$c`jr11&1=tdPpdq|RQ~kl--tQ99VUE~$ zHsGDl-=ok#3@eTAKc~+d87DXWjlI&|#@T=N$U#Lmze#I4CvXkjT;!OC7?SR7=*n?#HV;{rJ5$a&gzM*(w})3JA23d1*j6D(u*rk3gm+y z2W^NtVcWas*hw!=F?ipXR;D4j1w(aD;sNKmT{^zv&Q~A16*h_tC}~V>VphdsGqxAY z_IX5I*Sxk0Y9#BREO7{-bs?Vr#?3Luy$oR&S>pwoCb~~UuKTH7T!vOl!csNCsDO`f zu?cAG^?)*z6cKu}B5EYSg{Rtcow{INO`~wDl8hFa{a4@L&3~%r#BZoArqpqp=!X?u zsf*bE4P2LO=Bgo1MSGlnHT8M=qne>${_gn4ydt${aXP3Uc}`Ww>=Xeo{~9X|ILEVF zG6t|YJmc+<*|{Lme#6B60u9prMn-Ps?a}vomuC{LN@kl6r=Ve8c1Dud!>%+oKXa&h zvUSMRy~HxOjz~E@jlxIM%;$?!*y=TDIb43@g;lpMtygb$=LPPo?$w@enux5c5?0_a zL>reK!4>DvJ7~N50vEnJrtIkR${ZjNUh>hkA50xoR(j1#&R6E0HFCEdn6nU*eCUNwji%U~P!aUd0AZBTw z_6~`TFOCui!WAxJU(|kxIs!}rcfVLTCJ&9E9;@sU7M-Q2o|Lj7jR+Q~tO{n-o|JEM z_*=ITOBpcDW3_nuS4}>gdQWNcv|_OU=7_=XW`Jq1!gAC)_n2zM?w=W#2ad5T+Z{c< zE6~cMsdW**F$2_4);Q;Y>qKGVz&TcLx$FN}I}ml7&09G~npX%`VqY~IRAc0;`eCS+ z`l!iX6u)Lo_xr;Ep)=hxQgOXC5A66^s?&d4Jx5+1=Gnn6`r{*$egk%7oQ9^`$&xaz zAdkLUyk4xP`g^YrJ|ZP}&2^UYqf=TiwEgRBoR{K*4g7;4_m(ah!?11{nBTt_A`1Nd z0$cd)8;T=c!H@rrEbVtyiM%W9b#A*3*njBtxC}pz`sO7dmKfEI_FvoDdn<`(B=IB_OaDxM%1m!S|`yJz!SXP zKb3P6UZ};lRCP1?z>P=xt2OmhZIK1kRKejIgEI?@S5j}>1Vo$oY-B^CXd|CM*@x_r z(?bpC6{$s#;Oq!QV|0yu{uAQN)HY_9dT%SeaQLIvquK+8QNC)8E9YIICsnq_6`LO} z9^_#u1$pj!Fq^dsW);+37@~fQcqNyI8gu{2(a;m;tEpXH+SJ_*66$d*Be9M0u|rcx)0f+YqjDi~uiVbw8}?T;hM;|jjJDvf4CJ5~{*^PQiA zjZNic_X&oTGEIu&rNrpfuai`*i<&hkgN(OYJW?K-Gx+!=uk7y3^?;34dTF4qN;RZ# zO@Q=Z$|zt;SA8kNYBqPzinZ#z2vskab?;D2Fi99vCK#_*z#P{PWFkBKzBSTtE>!50r zU#C`FGBb?Qc`N>LXuV;{I-TJ+nmyn@uXPA{e6w+Q=!j{mO*T1M(a2~htM)?&2bzIs zm#hl5#YAus*g)jU>(pucn@T6@?MCezqRQ>I-KzeUhHza0z>KMV}VnwS; zpn%N4O8%!pcl_r^$ero1Isf<_`S!&y_y*P=!doeqt!g$PG1BSPrQ3~-4ZORGQiQ_0 zJ2h(>drLwNZVP?OX+0{-Y?n5Z80^FqW~N!GPlUd>dJ zjeC1#gu?kWjhs*4c|B-ilMJ0d_SWnl!O!IgjqM#B6K6<~PKy)mfuecJYTL1Ml_i#ElPbVOeFMJ=xXfY&rueXD^~x&7~z80d3=hol!>e^}XK^R0WCY7-{fHxDaG{w_D~L zpJ|ZLmguXUic4jjHXB9fS-k(Tqb|jF_IDnOJG&>9--@y)i6 z&_mBOF8dgdS-N4IIO;|u@sRkn%XIgo`NhNC^i3rbs!CsYY;V zdV?&z{WkQYsi^#|5&^~^g< zkMcE4z?`7!M8SsN2&FrZD7O*oM$CJa3mVH>ti}pKk4c!YdMF)3;o@U@#=IaO(EaccC9|Lk zXnE3n8_i)xN;bB+RKI0U2VX*`H4Ff%OdMT3EpEjKe} zjXWY!ly#(`D^As;nzy>uCSnKhJQt1Bd*Q06)zwmjydJlR*lDjk2OdhSYoZ~wh6Zf| zZs8WE?F&y;Rp3NSJnc8y5`sP7lJOS*(bM+$JvLTe-gB{-Vv774c_PL z-sn29VI!+KcK-y1?|u&t5gXg$@eKa_pPNI6<&6!vfkL6M?eEHbVK7){*{X-$24SXv zq4frm=DH9`8?_R9o><;f&{}SukKl0#3=B2T$`V%;E^s@#_NZ26#fHf*Ftgh#M=^-`?;+_OJsRNOts{4EaXSE% zbjIk?k{}JJ2RyJtI{Xq1zn;n&eG#z`1c^x6?U1jzZxo2hbn&NmJ3=X{*yzBF9l3M| zNF{jHef7T&;GohtMZ;agofjtvmWNJ`g{~ij<*`K-uHBx?7Q#wjUeu%dZ)b@?5lixZ(({ zk+{kgE#W?zIsk<4N(XJ^9p@2$m2?i@?PfC1HFvqLCFF!-%0XcT>>1(X({b8GNk1%@ ziJC4ydR{_!;-QE4+Y>_LhhYce=K1tz8sM{wxhd+4y$KQ|{dRVt3AW$tlCg0hnQ>5} zGYC?C;$#C?*nzv5`ZNB6FkI`Mb6;!PYB#Dqd`nzOAxzj2iXJ>=!ea7Iccjr5P2lMP zzEdtxbi(hR!d_qY8FQphRsAT!i}!=b=$@KkH!VYmj~1VoU|3p;xx!r!iQ%liy#?(? z*|*DbwKJjNU}ii}V8KFOzR&s3HDKOgl~sQI67G)RmT~kL*D!KJlm1`-fI#!Ic3eL| zK1hGpuiY(aXXN|US32?(NsBb~yy%UXEj6mQ3MIK5Y?;-N>4?fW3Hl?Heg*HH@4y-? z3-Z_sZL&PbJ|HqLTxyhkFv{!NZfT*qNJO>%;D=OJ zSHI5HFHS|CDC}b;0(-@;YfMbYMF&4=@6Q3*`xaMZdNrCUV4HW%eIY;3z`sy(#bBj@ zVVY(4$2K84@y09BqDWrtbcfR9tV3}j|BC#hB%&u}HVr#BOWkG}XZi@_o5g%GHZCRP zw^l^!nY==WJMte>*J$F4<8c-hIUBYzcPMub&sR?b*mU^T6XYF}=-n1_NT zshzX7mU;|j>`apw@V@WRp*vueJU2FnnzC2&%eQBZi4@uTK70Fmo2c2 zqz(@gfK7}t?JH=*}V?jo+)}d!Q4fACg$-J#plI-hCcjK9pdLdd!nww7Tb;{ zoXnYy?;oxx!FEcahtQ{;`FPD#=(b$_G21!!;1dIdcsVqQ$j8b#(6wko#UunrloiEz zWr^qtbKq`%$pjoG7H&p8q8+=mT50AkQb!I!&^S-q`yr2)pPD}Z3IrS=mIudUUQR9> zn&6Bx=gzsZc3{9BSD!YSa5-J<&bfnb*WO-SNB1}*(0jLroR;C7jXjgIM$;(=!xTE} zS<<=Ai5v1N0QN!>okZrWx61dJfj65Clqe@z{cQnUv8?`4>!--tN0YH?f=RQ0h|LF= zuE?D|Fwu+17BU39XuCC(r^J1>uTyF~AdVgU>RSK4+kR~#wX&C_+WwTyO!F$o_VfgoG zd9a1Dc-Fvxv_f0|vS?1}X}0yd?`a%f2@e$5oVgUxac<~p>w3i2df1s#J_zH*6c)`Y0s>=$T}@C1s41o?$M3TbW;OIp z-6{{KOHOaD+mxV<3$92tqw}VkH{=j;7t9skyWm&yZ7yO5(&l>oi=#qccoT9UsK)e? zm4`mEz>q}p$!+qNbXprQbY&s$%3MqZ+4-#Z#nM$J-Ijf8N}%wJ0hgIT>S98PgMs3t zN-MWvUw&h9x2QkQ2QSfNSgQ33@W{4Xviw<=eWQrVX&s6n#`bN#suc|FFslAd=*Q6D z`a{l&vvSti-X|gWhL1XpFPeU2haG>>Xfxu~{<&53wTz}5Wc~;OriRLQhc#d|FgD^- zkj{x8FRPyzErB@K5i4WuSlW7Lash1@^@BGGsZR6Z;sQd%0+Ie3kvv1(Zeayo(ZEsiMt^HLZB_?FALXd9` zv=|9ZGw{aMev8?TQqb&s$cJHaftFc%!yDZQz&b6(t!l{bVfGQDmhT?dkUG0}H<(s| z;M&KrB2P?mAcsLTO@r~};~(%Z9U`vXN4hO0s#6$0H@eRQn@{e?A5jAu(zdT0I^M-l z>%sp0!SQrtIJekqH(!*|?0o-v<%P>wRMIOkO=E`i7#}LZ?zW`MzT>Qq)TQP#xn|0A z4U1C=1t4FZ)Q6+Y__HDLOL|W$B<9v~r_+8q@H@7evTb<$M(3vl*{JRPRA;aD?>`C@ zN+v6KFi=5Z+n46W_V{rtgG+?4Z?&S7}RSqF?zx{ zJ@R`JA{f(Mrm*^zntGG|Tl-+xio{ys4E(XBaZ|4jf|LsieYH1nvzyv5-~gFsmsAVe zYwvef6TH`y-y%k7|9AM>!t6S;weiOYxjJfs%rU^CRbJEL@8G;($!Dq)i(a~fdsY4k z79JP$>|1S}pql|LM0Y8t%G5vEclGIH!8KpWH#fswELV0^8iDkczz?4F*pkOS6_{FE z)!st&_wKO6TCS3r5l9zH;>Vv6Tf-Zx)-_K`G)hu$*2A0B@MK5}zk*rJzu>BSuRodl zUFb4~cf{|>z~>(Dj|TAHYz8FPM+ic+S!04kGjp(6MzMvwo^TiA$T&-NafDCJ)3>lt zYRnx3Pbk^d`Eh%?vmOZ!kGN*O5u-!C>HIECi(s zOh)U7X*Z!0$#ns>F=mq)@zoOLSDalNW$hg6cJ=h=i-Qlqp}!qra$t7188c>M^KSPJ z(X8&7Tg%>G6PPHz{7$9M+W(9_+6Vq~>eCL)(9=3Dmf08H`v!PM)Z+E$Zg_Wo!ilJQ zW(THy=sjSp@GZSWt-k99S*_B43yBj1dGliJi7XIf@IFskvlg4*CKgcYtQz{ zVm^-H#kTvRcko`eRi!Cx!n?&`uVmsCfBKH5zsS4%?yFTDHV$EfBMHx3krp!h}7t$dudq`E_^#= zDXCri<@$CWfjRTt4cgEHljc%Kzgf}Y@#zLZV~IF_;zeP-;e9Zuj7rdp^p`&SRdMf>_?uY^>F;QuD=FSS1s^b8;}0 z7RhFV+TQ0TbbGij^E)>!6eOxU({4FMJ_{K=wi{x#rI?3cF@zaxLLoIpB zQynQ>9u2Fq%9>Cl-Qou36~(#i3qXO4CO7xkcvCg0`b1N)J%J(3xO#8g!1Cgo4H~b^ zTFlk6^I~XFM7W7g4S+MZh(>M(Xua0pt+e90OG)g$__ar za#R{7Ua^&xv(TaBSDMCZF+NmpAH~AS7RX?ltI{$>-_QR)3u3Sv6lc7r)%?-Y-@U|N zvRA$NL(kHT;PFc{%I5r_kT!^J9Y)i)W=?YC&a-FURe8Sx62bq3t(%=j!N>Y%u86n` z6+EjTKe4=lT1*V!{TVDm@MB&kyMJ!LR}!=Pvu}u*aTXWMqG0`9+^PD_b{l}wO4{Mm z7#YiN72HO%XyxN-izk#a-aWr9lAfxY>xVa0l2M^93Osc5%!yz2Jp-%cNmObL?av9D z&Bu#^20us+E5yz&@_Ov61bBJNY?A1RE>`+F+kYN4)6wOhv@|iY&oC|2z62|g-=Ejr zM={ttlwhHa1r7Or8^3^HL@=f0Ra8i!lxbc24hly2IyRY3^6CmZ;N%i*n8yn+ zO4F>hnk(_wIEHLOK3SZ3gb#4f$Cq`JcgBGdGau%(#al=P9QZOfimU+>QOOIJ>g!n& zQ!P?!3RoKd=-(=t#H*E6e)`7Uk2_tqV|QT%e2mzxnVP9+Tbz297_eC!c=?DPU$(7{ zWZdE~@}O;UK`AcvCA-%t`^n_jyAzD31hvaVnIIeeyp@6s==h8vQfIcNR7hMdB~4b& zKAFr21vIr!9A|pi9Gv-)+BXrq?%{jiKznGTCcO_-IPT_}D>b$v9)L}@uUhLnp+~CR zE$k0kK-P140g%N&s>b5}=SzD&bhw~S^FddMwu=8?z@gHj%cM|#jr(j_`fTXshK7D_ zF%Y`B*`waDJ73LoKC{<5C*ZQD2~SGvP@$6_-kOdbf*h%w{OSc#UJ;htZmDcDxp&{! zG?ZLdcYr&yAkh$LA@e}1a{!?lW-ML7?c+re&Y)AH{6#+OyGY~+g%H#S4V!BFu-wqV zwRWlTr3OL(dZ|{4VVP35dRY%<9Jk@Y6Q6L`plQjnT;;a)4Ko4z4}e$^8%bo=BS1if z&E9zfhAUuI-6O9O>(|LpO^LT}+_DsCdLZn~aM|}69@Ai`|CyvIaaa`<_)A3fs#T=> z{pUsx-YLLxA{xAi38!o9;zWsX!lmf*x^>rYLV4r;@4;7E<13y-zd3!}?!%)6WBq9L za@x*on#Xvj5pE>K#^W1j?n#TMm|V~xG)-lOtQKS7)TVotxOhD>h&rq5cZ}XfVhD{- zdBxr`rrLN@7b`^^GM!ojlkn3OFyZ!Jx3mtXK6q8`2i)&0(=mgJ8UZl=Y>l8K(w(ep zkwZ)qieK?nX-!NZ>yzz zKVj@Ak7O!ex)W{5Md3X*^wGGesdFC0e;CGZ*WCBY7Z7^qNUM4KOPNP}pGGcTx$hw* z_d5sEpSWxX7Q-WNKYSO#!-u51ZEZf>9{nA;Tew*juNby=?Z4$P}QJbUnp8Qc`LF?== zZgFUjXQsJdMh2g$@*;jbd#w1opy2yW0aLYC*ArrWFMlLJ7k`gV5tA1lc&H%erfogwu|tlhP2CUovki|| ze@M_^v@Sl6zY;U3bS59PdCu$jRV7wo!~4Wp2d+19dm7l4@3FLK>aTP-fRm6xpx97$ z0^uh$m5d}yWd0#{_DnPwhH4)?09!caj2}`LrZwK^i`5>TP)SP%Qou_}v$EAP(T${7 z6AtxTw8MsIbNAUnShDuob3cL?T|P*!Ko)q_+AOQ;ExL=&w(Q9Kftb^lYN_z$`8p;a z8Ltvr{b}}x>tE6D)Mc;%+v=y~H~rUG(vj#F%D%cv{>%#8HTS;CRL2PPDunNeZssXl zi>=omRZCQ8%6Gm zryn_bq%2cu7j4R&I6kF0)(v!;ZtIHBh)7!&)d!jY5dv$SL0gWDofM-srlBdX!nBFY zs{;xMhAq5MF^K6riar`UA6AL%C@uP^GB$FVirrB$~4}T z<-o(hnm8}`_I^R}C4rYO(Ks6)O=tc1caL7fVg~}DdsB@*k=-k3)SC2U@0S8UIM2nQ zePfRHc20VoGU39xruk#PjLobb^pxd9ImHi375lB(pI%u#nBPxqIKpeMRBIj78zTP< zuY5KsC2H#e_G-oZ4{I3L7ze9bHQP=lqcY(5BkK;E@84Nz6w~z1oIYnF^44F{j@)Zy zRR}Cay2bpqNwpmg8PD0b?IehZH`-oaBP->pg^Da^OFChukRn)X3T01vXH6wjd+v$J z4plRZ^)qi(^oT)T9Ip5;-TKVW3k3h2{@YJ3%r#N zDv*PqL;pHfZlCUcr32GuE4PsR_om}XwT-Ma9wF&}kDtgDsvMeprk3Z|u5}_{K?pFz zqx&^_BkRviR_=_CInG``P?Wbd^lH-V>#IGV5!}axf1mStg(to}jAnc+)KUQLublxM z)5u-xjGxko5`6Dx2{z5P_FY{Vx^aU8ynVp6M?fJnpvG=sU5p=b!?Wq9?6~j!=QSsQ z6Vj3#DP1{w+CRW1Z3TtX^hLQlx9*~JxKB%tkOTh_ZvZ$He?#~P`zbH!%O`418P(69 zf1~sz%SmS_szo)x!KkNrCUdI$q6vALh^W$?bJef`0l&CF66}}r44;C1 z2kA^a!;5+iPH9CNvxlwcR}C&P1uC3AK(!t4`bAIUccFy#u> zhkDO>w+Okh9=e}B97@UgilJ6kW8$Car)I}Ap+=;E)mH|e$$iNf`u#4Y)YhOOVP)ja zKwBI6V1X5ONY;g~+H#Si(bztqpsL9@?&fElcOXW-Brpj%sm%M>!0jEnUz8$()yfn^M&Fz(+g)*Y1q^pJdPTslQ=#}Tyt zZg#)v2d#@_)K0^xrh8B29}Fc8tav@KDz$f9zR)_lJbnb6W?my#+I)FGwhdfEeDt6B zopNz36~|XKbg}qw6LX>rDR^896z|^Qfb%E?PZ7dXTfdEBKi~b8Ulu@qQgMJeHK@j) z2V}z#k?Sv%#;!`a>BXgQbzY=TvhnxQFNhFkmi6KT*Q|NDY;o7e*S*LIJDJmR&X(Kn z?}4Q}d8*8;bKAH9X2oL!k_C%(wM5qJiMep`qdChJy*c#Aykd41RD^5iWpB8t3TC|vxXHdyVgTB;bjX>cjN@6mw;_7GwKOO8=}p}_kE4~x z_U9kFoFulc`x-kDvbq!Mz|C*n`*$aa zBm_14!u}KY^Pri4-PLKQYB~L5pQbY=8xq>O^ZQ#$A2d!t{Wp2?v#36C_`2Y&twZJZ z-%lu9OI7g?QG1wYJWYP9_a^AgrT2FSKHJ~`dmSmC@%ydcy&PTN#@0~vR%VgrHOQL} zdp}KMTvq&D9Mt93#cB>@IqB9B@kuqzU#cgZipQZlU!UuzC8FG7)Vhzj~+NqbBxNWAhyU@7%R#yo$ z8fEq_pLwnuz^Dz94_n{bX7JX*3)PWb+zO_C(};ewMpI=H_6mMk!Z2|~p0pS_Er{Z# zZ71q0m4F}3^cav8mB?2B{Fl4R>lU$a;j*Oi@rg*9v8!I)yG;6=L?E$9`6e!0NE@zo zyn`5SV9X16@8%%k!M^!}r(S58liIJ;Yzw3I%57k-n%!5JVf5de|HPU;Z^CaefAaCR zzcb!b&+;8b|19Qw_xgcLHM}-viTA({_9Rgym|aEG7ObzSS8E#eiswb_L< z);DSo!I$r@uzDu1N=7%S2^u+{#()xTw0%5rn#Wuw)QYG??CxCT%=su8vd)9#`U^1tdMOFl#c8qLo$zKc8kll{AeOLJR(@wAskb=Ul^jpF%9pL1mYv z582I|UWuRccj*v|^w4kGiBwEjMYS*+q8`bzClvo5Q(qm|1pkGrgfxtt3ZqkECb7{q za+Hd`l!PKLEu&!wjF!fo&jVj* z=-S+bge`Mzw$kJxuWy|=KC5Sjb?o)MG_acy+kRFl-*q+A^nj^ip|Zl(o+mpurQ=jF zAj*+o))-M5z{2HtFyZaHH~ob`;{G}^r(z{{9ct(FVEXRv*Ov=v+#qTABjED5f`en; z%FXjr%j<&!e`~J=cKzewiWcRLdU#glT8xV+uZOcnoU-H|IeneJ7murOa8Rh2N0&*Qs7pp|>pqDELjcOQotTXbR1k){r z#UNfre1XSz&1~Y@R&HKuEg+R^p~&9}mTE?FQ>>r3coN$#6TY@jn{pJyDDE@lyC*}H zFQq>%hxng;YMoC>X3>W8 z#rVpqDV~W6qsPF_Ywg=tn=>)<(>5JhldperJ)j*PR_j16rr9mi_R2IwG$?;(&!{cb(%(Jv9GFs4TFK497!1$Dn5P#XsSx_xy>v<);|=l* zZMJ@1DmshP_9V{_=i+1TK+7X~^hceQ4$nSZDjU5`)H2Eqe-*s;DA0{5*$S%OAYVA+ z?78BQNl^acLT^KO@bGk$0Cly0d(>1@i)9^<=i|sb$1~rJi|c0SFxzcMl5fmZxc@I< zL69SuJ2=H%*0y8F8H#6`dE?y8e5icIrgK&k<9mP=?eH|VrqieyxFz$lZl`b8yOt%G zAx|}>%uq=Y+WVHm@Go1&>PSaMx-+|;>bvQabk&resTmX_Ud_F#^jQYS)V$!m6_+c6 zk4nGCbL-XfeTuf)>EuRI;twa?={CFhWRt^T%ej1c)X)0J)aqoZYOamh$@EvQ445J@ z(ReKcrx2m`egJ*_*8Wr&kQvXhx&cp6_KX-Khrq|SKg`NLaay~4 zZ(P+kYMfJCcZJZe9^6%a$J}xLde{QNb48h=4^-@MEN!~%IoqMsGRkD(y`m|!xI8{# z07mt-ENv$>%X2ExPosUuF>HJx4AMx&*T15a)us0iLsKxuN)>C90!a37!>Y^ zNESp}IR;jK@B2_mQ@bJY6<*&M-1lthr6N*G#3dVC62Eb?C4{kK=t|`Fg6h{r`tq~i zxA%-J!|i?_#mF@t(|?+3+oN$u_tb5u@o$18 zS!gD6Y-*i7{VtW7HpIS&rDdk{X8paopBD_#%ashk5+^iPP$zcd1o~ftJ2BH!OA71L z%!icE@|+Wx&R%Jzf6L$9sg*!pqM3dJn|}?G+XSEEoW_wF`;CTna+`4zpwKp=#;o#A zCF_5$4f%@Oq!D!Kn8)oV8#LM8^|P;|XWC3YE1-&es!S(j-}y%7C88U?5St;|?Q&`O zyvb**A&I7?dgjpUnZq_P9PP$+ zrv%D_q>Q9X&5^Z*r^J_}E2O#XG`!d7&EIbcGjEAa^yS%cPcS3){j9JLNpr`MvK%?_|T5O^%NYBmPV8U*%mzsK=THIX$6flT);?fi``? zHkt3PICkIzZY>?O1`*{5hvSi76O8ybF0w`(%H`@wUlyuE+qaq2>LeB8lV%Eba<-JA zOB-JQzW6EQ^DTTU3Cs14yB|NUwD|wM_$uu2Oo1^4+}|!-#I@mG|8T6{8 z2fkzt4306nK$N>%P_?d+``JjDdj}hFBQucFF1VLB5|$VXRUotjB?9i zi+-RmS5emCEB5WFqKZR{kKza+^QWr)(O%4Su8JUojNy1M4b6+K(~qgHFri=~Bsy z|42o)E0(s<$?XS1GBgWD3Rfj~Vwa|E+l!^6vd=X~vpNScgXMW6oyfFblrg zzy99NymuR=6z1-Gszt9fE72wK0{$S&=(ysbM+YIMwYn9cPpWjBKC1zko3Ha{9R=&u)s#tB1x z1e2&vg2$3yJeRsD%y2^_mP#i~Gha3@{RFdc;ydiMR}fuH&+rm8oDk%(qx8co$7?%y z=j^vvx}@hz#0R`?iyqjOAUo0Inx|O&`gKjzDG}uaj+x0k)B_$Jo_zSNoNdn{+cJ-r z3Y5Azt~5$gbk5E|aB3q)c>McOo;9s^8kK+x?H>xYmKSdw zV{-4H0>$K%D0)b){ZKDVqfJY^#{;@5-F8soZ!H zy|JR^KA6%8erf#`u^8}uhC2`VP&g9k?0gnEQ)QVnmiiq)C>Mh`e0*2gPlkssu$5Y5&hrba(4L zdzjZKmDA7t+)`0Bq*o(AOfi%XaSweTQZW*RChneVRv6^yAqO5WnGkMF4ws&eXb^O) z3KW;`FjE^U)TDU1ec7Q~;~%$KuD|Dr1}`yj{Ao@NI$$N)i>Lb>U5TF+NK+mtIWlPB z??ekML=Jo|pPR3X5T=FW+0VEGV}nEa-x|t$6U$&LH=lx}XA_uX5C?QG?fFj9359re zs+MY~@gbE!!r{TjTb2 zlvzEnulBBRiepM0#=l#Ga4b=OHl;yZs5qD6Cwki5>H5z)DX_E-ucbUsx0fZ#L?A|@ z_F*ifPmq4rR|z=Gt>v|s2}gdi=!5E!praQFaSA!K0f9MG2#?lH|4dxWUd^9gK8@nm z&8x5phDoqD8X+Dq*l%;$ntGM|r?*nRW49Pf8qg54G9U#xWo3)UL#bo7_zgV7Os)6# zaD-3H!x(eY?5r-E3^`NiqOZ*kYvh^n~fO(E1%xmXAb0p^}E6;-TinEj2geL)5jX)hK)PJA39L0#MsR zzW`pBc~D?(+n#XwylCcVYSPN2>$gLjj>^ z$>)3OrtfVoDb5PIJwBokt$3vcMz+$zG0&pAH9uBW{Wl*JJVMEf=dMt%vtlB;i!y|Y zMnE6m`*#0QhmcA&x|vz*)8CQm?4!X0cLOY0hFAV^V)gw=47Jo92zT8E=ze*e?VG-V zwz1>ZzspjH;~LtKHJ?$}cA~+txq)!MiRn~TgQi_`NrL~j{An*}m<1oUd|>0vk=>?K zzeBrvBj$8v%|?7yIe7qhe1_BudvQLm->Un-bm&pOT3L%QZB>j~fAHJLulf)bqh$W! zB?%WbP2uXDS7Q}*PqseS>5QukEy;yyUZAdKyBia9hzS8v*JUAo974)~ll+70-rCjr zydaQJP9RIrLRm*=ZJ!tC$p^27FI`Rb!Fq9SDk;QK1uXaLmNshKQc{*omg2=(skFDS zOvNa-2e+yDP;xnurQnWiqG*X66>TAA<)o_(9r)+rpuRsOM|AiwuF-mO!+yzNqjK`- zZqLzPC--5`Tk8Q43Gf>66|F{r{d;~kn$P?oFcLy1WIfRA695tdvw?D;kJo&yA^#<( zd!-mypO$@>8G0)3Q3?;J(7${5Qj8>Q%igcE!fbkONI{(;7E?ofnynom9ZSi3FCFgt zhXU0jD?Ynb(2B~Z#H|lkrpDwDXXO*%al>p^<2ONxTwR8F`Y<;l(PzRFE zcw>uLFU@ugyDH#i<#ouHfMHQlsNxxH>X7F0S>}RU{Iz%`o-?3(euOWeYg;s!j>Qgm z6mNvpYT1j5`|Brh{^^|PUn^!H+f&PV($hgrij`nOPCPBib;j*oq?KRtp2oMpV@r|k z2}P3L{FNc*VLOzs*NG|=LVWdfdx|#@=L#mEm=iB)t6#0p(`+ep10qr~?r)+YEmlu? zJ&`%%GM5!)?DeoFPu|xD-;&@pL7zVU6)c}TYG#jX%Sm`t-a&Rx#Cy5RnD24vz%NkM z(BaSwPsmKM%f3cjV`2c9S>LVfBW`w0o}|Exm}6;vK1*k z-Fdx#;NRcq-)Ipk@7W4#r%0h0>XXs6gej;Q7hFaN(w5BBcDrpqvRmfgs}=Qx+G>n9 z@JDKAUFya4c>8S@ARhql4>=ikWu4mbolKng@^;xz0(w=Q7Dg5TEfj%t)nCTNatLxo zh2@{!opQyan)_=v6agZb@aWR(J!sWd^6-M_2_43bCDg~KMFOK+*d;+3%-E4!&t8;p ze}UYGb&O{o$1T{5@hXNiA1^-yjk0#IY?$6{h*~7n-N5n}jX3FLDu(dq#Kr#4>;>n~ z`)R=LI)gReFT>El8eLBCJBYnEBLaeS8hfz23yX5Etcd6I^=#5@B{-{`!*5X#Jzft>LZOq6}Jf`L@) zGD}l?2i;eyeiOo@+M^Y=u<-<+2FYjC=o}rT({MK=bp(Qj}g1bc*raS`=Tt892=&c0^r#>82tGcw^L%k2NSjO3Q(E@urtJLh>eg!X7it8l6;^RkZ` zVqm$mXF?!m8$KO?S*2o&RPAsgEm;8!+nXFwX#TYA+aiXrFNa(a0>6C3Zmxr!Xibye zm{1-2>ZDZg4>2fJghWZ*9*^;1yfWeSPBxw_1G62`Epy&iNc{-Nv(K?&g=Yz{8fEo) zt@>mzarK@lkfJpNgYm%VjXO&EI`*(Z;GPL~WJSeN_+abb{XL6h6zt(4`KRFqcj6tf zYd4j^L^OdxN!c7~9GO5bq(s=eJlLGM_1#-?O%7XxW8Ya&oE&1u%F3dcXY&1o*GUUG zxHD@H&4l5sh2-VA`E2{}3d3~&>!!b{uy(e-rd(jbh?NQ1JQcGqrSE{Rh%YYLWI&Nn zYlu+9L2?3T?WmK#HKrm)ObJ9o)zf@fAeucKcDO1hG1>&Z-P__t$F3jsok1BAf>4vU zyDcsn*}}f~kLRivfe!MYvsDizggK3b7>b*}MPFOa=28nY#t7x`SHyUMKC=fPTrEZn z+nx?zq6a}A*PR3P0-HdeRhCe?vnG;Edw8`rI$sRH#q1?(7494p_MdWhK$+k8cW8Y6&Shi+!fc{Tg5|?Ih$>A%Shg5GRoT!q*CJbQ zbzZ}o?jl?h)eoN8SMP=1@IL@xbohEu8Q-F?njhZuq5ZN4_%Qqq2|-0m5v~slPs18f0EXzf>;tlKP3-(UEyRz+SI?9^4gO<+&h5QRRF7T8Y{m(SVqsq8AD*)f& zErmQQadKYE51uXI(Jd4z;t0m8%KM*d(%wEl&Q{-MwJuiYs2Zy`Wufy{y&L#B#3>w3 zwY1-s=%?OJ#SMuFIL$PbUHQOi?I9c-I8__Nem6_SHd>k+ikc*XFRJ8l|J zRkC{WNSy9eS-&8dV#(j-kR#?eT419}Zu-gLN}5*KVuYpwD_@*S0Z3>b+tNc77bt+C z#k$i@)`f-u^KD$6@}k1geJ19s7Hh{RcC3Q8{KtpT)5(TLWzcJkiB7 zR4!AVeIB(%o|d6qfdW7Lrd$XE;rbYmI3Sp zxz$nRZRofPH|$r2a9OWI2(MYD-bHD@Xwr5(nC$3eSn$--D5W-7B;FV)teMl5V4BQs zQN$5HG530kC@;QCo5U2lJUKJ5dJnbe725WQMWzUP+f9Tc^E`$S0bRnDEsr=|8c@zI z-XM8%kS>ho1H7S_3I&>_T;N+1QtD*Joi(QddB2r8^3&cTK}8}z7~U5d?=W)#LiB@X zcLoxyL}_b&VGWYZw9Il4Eug0%!iVSn;zu-|3lyFSg24dUg>c+NI!+nsCah+;(j2-z zJ8L7b&bP(7hC;IQ=LEW>KVAbq?#WWbn2xN*9Zxpph8Bb<>u?{N@~Q9@D^-DWzBv7P zQUJ6lP+VKd?7IFl_0)R7;?UG+`Ev(4wOgqg)LfF2MBWso4tZt0x8pv$o)B)tVb)bkG=tw1(4z*}?qCp`W8J1W-y&kvEzvK~BCbSuR}43l2rhjt=A z7e%;1rPl4E57el1322~6)M)EZ>gCI@K2-7~CSrMu6iUlV!|_JlWO(ko#IR3Z)WJU z!g$of!gBt1qfM;v>$fb^3J{|%yYQK}V$<_^hDBKcUT^~8adCy{!{VR??q!6VMDwS5 zV2ru4-5(^qj6PjbK>5V zoHTJ?V9@P>DcQL>)L9U-7L=0`0=Z6@=ulQmQw*Ac+KLXnJso)9dH2el=nF0%ITFLj zYi^Dx{PW-Jrzce8GH`XYorha|-c`g{Ok+=R!Q?NwBpjVz^?a#HIPM!2y*IXY@571e z_zzi*7E8Wj?~4NqZMl=kOC0I<1o1iVy1Jr&qY+PNuI(@u<62J@0`vJyeky9casx_5^`}+iwKR_j?)mMv@v2 zRHM2Y|LFW^2Po&~9jzl2uX#T|dhm)%>FOD*bvaV6*ZXr?3n7>{5JHV7VcA6esKXaX&G=T-reD z-M&l05Y>dmw#N7*uVS%W6^u}CMp)JQD#hWGClHw`7Ml1;{tGXrOUVN!gQ4|lxvuFx z=1_7o!Wna^-^Jm!lY0dtpB4tGuq2n6WrWEpWsmuMc7udidABwlb(b%u1ZLg!XA4bP*egwW zvD7EHkWNploCf~av?2J!sOBlzv^AgKPn@JCJOgIkbKYEjmWdv4APEC1*(Qx#CqnYwb7&Y>j^xh->V@WvAnEQC%L)$N1 zqZ<&`62C4xw?Y-&XD&T&8WP?kmq!V303wysaNBu0teNvdzCpQnG>3@D9unhG%4eS$ z(75_wDz^|dQhvY3RWhF!!?qV6^`9k;tp<&HW(E7|KWv)9IO^GByb!k(^|i*$K(1(h zWIXS*fn5PO=2UzETjbDQDy{fdlyA?4U$G(a_L^4Mdo5_MH$QE5ZayK1&`(=DX(*G@t%kXA5xL%>4e%xcH33_K#L1Pu9)|8g)Sw2KujO-Xd{br}$=as$J*B7UBols>Ni zh6i>$>zaE$1cT&wsR%V|NA1Mmn&*pi>PwdmmS0$tcX*w0S+`q%qiO?mCDS4B>&R8D zO}GhoqogpgCk~IfLj$2{E249UfO?a1h95;fC8f?f-E;RNyTB2E3V%4-{bTn0ncEnh zr?h8(EOMDY*u0sGu1*VY2-u;C^$H%WKD+<}YrYy%y=#R_9(Zgww zcf*$w=+QlVT$mE%<1PmaBdO3{&gq75vL<2%^9<>oz(rbJDDHfz! z({jqpj*_20ZOzOxmwzy;Kzz-ONga%^ZoJ;Gf*FlZG}#=kzak0~KPV7HHf_sq7DsfF zAhe-SfngHup&+ajdh>dSuvYPSwO@01?&g>PgNZDW(buuadB15aS;qH8aSC~wU_#^j z0%1vyRm!}cd;%(hzDygezf0Wsngw03Yk+d^Ya`P(&?-ZvQZ8$)JR}Z7iq!tCBCk#U z;ee;SuL~q0@J*5OBNO+uwR#EuHdiytx)!L0i>4m8Y`vYY6J-OXtiGTXJDPV>T=vX5 z-^hPXHzOcoUWah;KCz*DIHKTAn6&EzwM61$Zc~_lU6b(rj`lhW$C(JFJt>;7(_4G7 z&VLmlhr3K~<6JcCxG^I$%kz3+16mh|d1&1F`_m?+(5unmYb70Zn>zQ|(#ZREEGITt zGpSb(oKk8YG_@nUq>hO_7n~afdy~goHgPs*)>tcL#!UQ(` zOVi-YU-mLv;)8sSbScl!q+-YKnu(p@P<(Alq%1!-BQfAdoKLhE5Jt8v|tkeO%TBf*N9s z{P$8OUIQwfed$xXf3&t9T9v3O`*OwlmgW^gKLT)Lq2iTLN-O&otVBjlS@Zsx&as`C z$COqj6f(s$65h5=OpR2`)_7>8{h52R6T|cRKShYqek0`{@dwp;QfV5H^<|$#-hRmt zyl&J7x1nxlAn%jqLH7g$y4rYphCFV8pRRfrY4t4JiD%LI&lfV7=_ojN-Bd)XA? zHkk}wSFs7Pl=I=F)AVP0`leL#TwLYQCms8!htGBmM* zH~7!TdcbeXQW7$(rzuWzyjHEn8E1sbQvkXzd%32WgWZhQB?4EJUj^cCzwW>2>xi=x zf(&dN1G7u;D~>PE{ohzM#6Ak`WwgJ}^R0KB(f@~FBE#&da*s|?_Ap(EZu9#}rN?&- zaKO?#M$;wu`wZxRhl0mdf?&da7lEf{F$3Z+QawEZ(inZM9x7u6lMfJ(?K!~t63A$- z_ajTpKnciQmu2Q3HDV=l)K@{B~cz|Yn zv&Gw#f*JoJni!w&yuw`)*w^WYZWg-4TEl2WbDPFrMIP{_U!IH`HtY;lS^b4S`krfi z1SU{!u2kG4U6; zIyrA5AScvM#y{tjvInc>Bs62axj4He8SF=^U@EpD(uQ^m1l`OrZs3GavcCHNs~HYJ?*gMskPn!<*u^noAF;qqoZcpjaZQniU!ns zJD-;k9*X;ux-NfKXuj22(V8gF)qiG?;zFOD6p}O0@~XRzby;f}=_$WkU z`vXoH!^&G}UkkWFbAP*Z`r5K)~0d_4-yVIGpNVVL6`Qyop%j*bq*_x{^vw&fLPJ4)DBr%=@A`o_t&7B!#9KXfpo zux+FRXcx`RF=wChW0o(yLd3(yy1yHA=eg=x2>c3;6OuJ^D;j-5sLv>z9_|P(&JPYl zSg3S37Yp%z-){UfRSzevAR#MODLhrZkZ4~7DC6MR5)@cYgV#6&}H380Zvk}PWhAI;P)Q?cKmG!S`jj`~o= zR59*s8$#qEe!d{#^4N|mi#6r;Z;{PzS=9iDAWOr&pT{Qc!jRWC4C9zq86$#QT!9Jz zBmzsJjB#btfg$_9Jfiy9U1M03(#RAyhu47V%^4<^*;G?w)F>!KW7(DgzJHPdMe%!13qFXn8Fud09Jo=xodPOsoo7PzPkDe(m(_Gw(elLI$-R7O_BI+ zKF!FXu%{-XOP9lzH0$(w!2skVQ6C+rFI9IMuT>|RCBra#JR=jU0@BBy8ch-kqU!4O zFXwd$gz`>o4GNMR=_sX&JWQH@U6Alj&%a*x%)p1CjApm3T%I$V`HbN*4^i(}P(FX} z)y2%zwS9iAa{vBQb2dM!0Pb8WQRm`EG0wjAYY{HVqK`45Js6gyfCpo?N&UQB)&ukj zKnOU%_al7*I#M1&;X4eG+cQS`))XcAeS8VJfw=+mJ-#<~Or-r94yVa-fCi8~ga0$E zMyM|y|3!%Pf@xeKwjdnb1qZc`HRE7Ne#y#ubay+y;e?vlmt+2UP*WG66fB#FeNCsf zUsmXzhA0dVpgt@9Fu?B9Qpgkxir%Lvs@9QXtJhW%QdE6ekup?+82=UU8dgWQ92jj( zGyN_!njk&`$S23l#gg_=h)4}*|1D=(V+;E03VNpOCIe& zj=e{MOUmAQ<6z-w$sr~sQK#fTzRx$&0gaZ^@rUXJg%kEUOD{y5c=&OTTk#Pe`+JD7 z5vY1*FFS?H+H?1qw$#|;kOWhHUt09=dWc>MXapbMQ^P~lN|h=i?3|43xpAE2@}(N!4IJtD&dKYBZ<54*{=R(y&ByMTv^1?Jz8Cb z8wZ4a@Z_D(Al|2wd$1-R{N4{S#ysA=Rd%@gT@;6t9nCT9m*K9$ZVAQIjn8e}l!8=Gu0^_07pY{k0!;E<#K(7rXS-s>YMa-?4;RkVu``JMFo(4 z-A>}HtS~pfK;?N`WKW_Cqbtl$<3-$D=m_@~yN~)qsph}PTa7Af{%V4XfGtj5`34oA zFY#CX8fwJ&T^&9unP?ENB6)zLknwme;~bS!5)4^~1&`c0;nu|Xwd?Gh>#<<(og3yL z{`$M^(oRc?SIbOW!y15CijzPOM>gQ*V6E|onePjPq?VOm1~7$Fp@Gl|-mWs`@urwGs{ zL*K>Z0(Air(=2opzuzAhDaD>PrJ*oLUSV6KI(?#hXOl3k8)K8KW_c?2){kJdu6b2F zYxKLO6QpQ#isp66RjlTD6?6!*pq$^|rPe&q-$q^0vaXSdpHcND_^waB$0ec_Vv*0Q z@@G}rTf4m$J=O?vp*enU#G{Vmnq^(zGk@=J-QWFLsNqy--IJ7Xp?h~j{46{2;SJj! z+Z0u6T=Uk*X9RgmfI$?!?0WI9Bm^s(uP~{z*ra~_XJsoO7yYa>!tWLKw+fsq)~_Y! zMr-QPzSgY3s6u*`JR!oZ1^~AC4iW+}FCpB9e)uPr{;`B@n9?kwKLY(*@q_&ePQs&^ z2X>|G6!C^UBHJ#i)fquWBDqj242+3*Q2u;e?c6(ciJ9|C-$L=^2$xKZo@j%@6P$)tXnP#yxftY}M&}tzYoV z{sC&{Ih#jQaWG)|ZcGr<1IeC;t-5s~vz2BP{-Y-AY8HrCK6{jxE}&>&Qq@~COh>D& z&??NBdMR#qgnNzVQvhdMJm%lR7isI4z3nAe^kKAQRn^1CN2u&E^nYolCg6a6aW zPxxdY`yoD~BC|6^SfKzCpc z0XVcbbQ1CCvJ_sPQ=GA;Dzs&4b#9pm&a!#8Bi30okqxEaad z1@Y@hp>udmaqdP{(r_Q^uzx#vJmlsQZ!6_%%r+#<)QwJk zEvWf%SqD2(>zdqnjhhn#<M-o4K)!qXbszH- z??Qx6Y30MDl2+UwH!)qtVw|=P;CC6UDDEAFB2;HgnS8`lIW9M?fV;5PFK82*x7O74 zyB)rGmbr8$O;UFky4)@kYZhkZJsCZ7Q1PZT0jH=8Nn@jPot6s+uAF7kIz zubO^6t2kPpK6y5Y93J`NJiMa(Je$K^H2)<%W{U*akqZ~km6rOW++n3Ny0>13KmMyG z5_*^{rj3~8@uxd@w-)j9TN@=J5An}h??y%y3Lbu+F!}%@ph}|->6Zk(4Eve6DBjr( zN5}@mwAibMRQFD1OIjs@$%u|Me64D~8jY@)73B7e)qb-*B`V*{7GaHlrGzhO9A@tG zfhqI1@C)(8c(r{N^BzlLo1YJH)UM46)`**5Jh&5-%U(vKWk%( za5;H;J=vrYw%fQRTy%0pH`14^1N50uF6yCSrC;VGb-`7D$s>F@Un2?xAT?8_WE_zk zZQwI={c7mull(-go6eh(Z1-(<8qvr3u33(n(fi#MW#;1xC#lUDyKkea5-xBOCg`^3 zigG4cJEN4`3*B>5@L#t?JD3?V%ELwwgoXNCWTxL(=X9D?)?2AdEq|!XFgsr?rQsCp zj$m9K^x9a+>9Tw-!D8yHiHtDg>vYFWvK>+n*78oPt8;5+-Eeu=qMMtmvZlroayx(0 zY48gIFg{@JY1x{&3f}eEa#@J2hnUHyZ;1)%>R%~iNaSGBp*T8LqVub6X3tw0jZ4e8 zKdJWI@6JU;xySXnuUH}Q5s1#VXwPO@57z&Rs7R}Zkll_3bQR??V=?Y1$)6}Vfrx-r z+K-v!TX=2<>5191nGFxx&os##!yMT`HBjr`8Ml}%K^9Evkf@S}W3dM$m$+&07=hZo zxw#Y1_o8~L2W>wtY1}m{O7ANcD_tyu|Km6OncY<%;<7E?@j0r(wWEut;&2G!5rT^Lk6GEyDaWzc=xK`lHUnWjZdlvZSN?n4 z)xLVTIy0j5Q7nQcNfDP;ZLFvZaM3FgUxq6bytoa@~uGc5e5%=ZD zXTt^iWWT{!>h}er0D>9zIuF;T@Ymp;wDGYGSj-3RYl#`i>pZvaX+50t-?y98SA;NI z|KWw3DuUnoYSN2YCDL5qO8_=PYYWCe+K7iD=qi3Q1fZHC(JxTbA>&}of8_hf8AmW` zG}N?>*jg2L_EtuJGC#c8*yj=By=p@QFB=&TBB6hzo^bG=br3X;NCiTkMa$L5h+Yd?L`7e+KPGcldK2y=w9P z^=POmmv!C5W%qu^P$%>AvuW*!3uSR>*v6>BYkztg+te@3r@VA^`8#Qg=kz@Z!9Pl& zJAWBEX521k_>PaNJ!Vf96k>~2-_FZmwk&j0jez~N!4G6*zFi>dbLaSk*#@#sdv<>` zw^q&3?e0%9WIFc{$V!gOe%3#W2qNX=M5HG16GLCR3N*MoE}@w-;%Ha0OM4L4kqx6+ zLhE4|7Ik1ZZobdfD(&>|Y3as&gZe=pxpe)(RX;f@Trs`t`!7PTm=Pim5$cy@k(G_( z(b2#pzDJO!(SVX4*XS3t2U;Tz!BTU z8joYGXC%MNPA2$lCepg2d=gKUN8oFpAT4vR9T5YxC%BVZ)i?ie#g1^R&+vQBO)2Y) ziz&W$<16Cdtf7gOEosxs5>RU_Xt(vKvl)jTdMp^W>j$2lr9Gyq!E;9+8n&bbnZTve z=~}w3$txk?+ihoEppP6j4sd>^^!TW=9>-jlLMOJSQ^x0^gn$Qh87e9y^ImwvXUtS) zioB?Bj#>1xqW2^Z^Siy-yLb@*n!#`7HBt9*M={1`vS& z?vaRx-f0^stQGhP`W73-1EYY6L?bf`_3hYR;J6_>C@4RvQ&bf) zW4UjSJ=AR1Rsqo;oK)r3l-UDnR1rwqDBW8i_9N4eKkhYWxjvzS=!KNXxX^;a=mfl_ zT-6jkqoIFr`4PVimArd+`%~a&F3{KFJXS^d^TXzV9-*Fq(PA$uJ7**JCiE%)ZNrvS z=M3}pwZnT1o-O>dHoD_<7Sarw#F*hZ?{w6BquX-=SKwta!=`c8slvUb`1UZ-knfil zWGLn|YrfT7JvH){uo{0;uG+p@omVRR7T|ojY3VGQT3x57BfqT?*aK`!!ZO?!#+;CQ z!X!s3D^&lJlYtIa%tb@d!{v7Ez*A-1FeEuc$f+#kz`$0&$UJ){LpIqBcVN-Sr5Km0 zHSHvni>F%%&Ucnlg*%VfSx>p31MRpht~|MR3P`1trY+tzpeJtmu%1;!58S87W79JUQT+T4((Sn+x;D z7JI*BR6!Nc$$l?e#%}6$>(7!RboAxPt`G+MlbF~@0^r`3@R273VjS}?6cuMI4PFao zzg#T8(antV;Cc%v_FxC$Mg%sphx>Tza7pYFlWHzj;LUOW@i_U0P?tg;mUu4ixoR_X z4~Bc>0Yr(mG%8Yo{K;raNl_fUsXDY~L~E+fa8zq1%Yk_NB;uWGZr5x+SFlMs^j|us zq-ZSDJrm^~a$0DjNiH)309;h<-pWU#H^4E+AA$`18BjAIXhD2zunAlT4?Lz?Vt@u$ zirBKa+Ksj;#_FU8z?vR7fX?5VH8?k+k7JG*DL|99M*Kr-*ZqKEwJAJcO&j_9_lc!# zde|GC7tNQKoOF-L(2c#zCq0I?iP|jY83f+MTv?xUT;C`X`RAE3AwF27ZOim!dx;W< z_T5Pf*mTR7Pw8l_I&@jVr4<=qec6(hx8RVlkcxhfXouYsLkP9+&_hpu)g2i9tr7^j z(FJ^W<-PL`9}^iuiP`C>jbGbo+%1ELZk)-RIZ^0mj%b3o!dKQ5o}b!H(`A2I-9gad zKF^9(;g53@8rG57{{~Z*TI|#^&42ChN#{licNovzNy`9JkoMwU>@wJRA^<4Q)nYh7 z&#(38oNbwSsR<1L#0K8yfBh@Y%4vkVhjBz#KB22nqk!k|(|d)q_rb+0v`~W&{HDeK zZTCp%Pflp@fGlGY;CSy8Wpewv%`qODT6mmUQv&q&M@Khx#`R##fD*H*@pwXNN zE8mjZWtYtK%!xo3Lm1$eCnI?uQH?Q31%6y`yJiwB4(4WYAWENrr=#oI&Ht0s_))Jl z$!xAjV#FLMKJIujrFwVz9?lL(@cg^=D^+X3d;X?31k1_Q`9ux@El^xDcfU2f`U z8*Gv zNfSk6v}~s`U4L9`K!)-3f#MOPnIlb&luwb6NBfUKtOQ7b5JY)hOA+(JktRlZJDL-2 z`IGW7qK;{t1^{n2tv?c^FGboir~>_CXqe9Otp8*27X0a|K9|ZoqsxBuXW+?RJts7_ z@lkr?7Q}^4eW^w`lt1j|G=(Q7mI3^T%o7+1ZX|ZM8xOwce>^!AVtO zfa6lS z=V45hKxm2OI=2HesArIQ`_7g|mWho^X-?7J701AetYrN`_SxCPrW_3rhzoQw7Zed;A9`6Tm2gCYehQx91(b)S7cNZ1=Qr-JNS3DU$@ zFH^XSm?&Gl^LRM3u)Z`JLH1^A+DRdMsh2yvc;21sute!DWB9JR5`79ZExD+*^N7NS zzzO;fjgVpngC2rit1jZ3_-5mEW;m?!CqdGntWEl`-Z$e(w=SIioe^u<9eEYru*4tVH5z_RRlTW&w?3WcL@pH!I@V3rk_TMDn$ZupBz&9dui9O?y9dD<7+_Xz8-9rv}x zp8p&W?HQvKR^-+pl66dfy7!Na)g|oL>;6zOQ|`QH5iVk*?K#hVpL6cd`JB(89+dOJMhFVGSUgRDcO$m^ zZKyl^rsVv7#yce3hDe__Iah0_bTE|Is;N8^??abW3Npez7az4ItQTOR;(VU3SKTK2 z+yrpG4RUZRA6?NbH08p8h}C1G!h|Cy@i``{W_%G1;)eMrM$_%j%&3v*vziR?95oN9 zWH#x}6Q06FS2l5foi>&sG9iV|kZqy)=VLLdcQ$OsJ6Q8gC&@+`R%l2$1Smf&h7$!iy@nnDgq-(X65CoSD++CDZ&HPPl8*3*#c5jrlqm`cuWx_YPgNOcA}VsU2Lz(oA6 zf{DC>xS!h@)c~4#rnJ#vItf44KXW~}w`qLh+_MAr8s-g7ZVpDXJf~P)xEevS zmOIrhX)7LtMv?~~X~s3!KaD7bmf*c)b}q)lejMXAr4t&MVQk&J_Qbap=+t#t zGC{Nq1$uF#x+;HuN2>zcss;B6dq6S$Q`;EIar681<8mdb7a&!rw`wOs{7R)+BE~wE zV&{Hd{jQan3*(_hyVqVIY_^|ZVBh)tVhTx_QRWjMua`nc{E7MXb7pV@$EU5YLXtl9 zWnLU`=t09@ud`u2z+fu6n)xv5j7u6_vI6Lfp+iXtTx8EG!+7-GO~1cwG7{S+I7WKe z@U#!e7qbtH%nAv~CM^L`oM?qXh#C2{muPJ(4hYxw=@RMP;pH7os?Uot(wG!eh= zB-St~uUnibg-@KOYnzsq&FuiPd#5#+nojzgCE8a!Zd>&!!SiYh9Jp8)6INMMm;Aqp z#LF>@B2*x4QhyNO)*)G3F+ahmnZy@uR;rBd0hDY>a96)F ztU5wUj~;CCTHS&YGy}{|b5ehM)m!u$ z{lT;f>wJUIOw0a6ikmm&lAJ{bNvy1_in!PGnMDZx4QYQ4Pg`3V*>y^{G2^X2)Cl3g z044RW2dtJ4G;y{%9?reBb{@_IjT?vxWI?`I*0G2xX}VNl86Cp<#28G*QkDEWyg4;GcqnP zsI`^7`H1+(y(akt#foCXBkfC`+`)U_x?K?l;!@YQOdTtA5r>GoZlR00j@>gD1l6y1 z)Uk=*Uz37|Gs+aQtl81Z9prYhe|EdT;oDpy$3xhIf3}A@23(76FzWLy=2TfhY(C zO!R6@AfvszEWzIdD_0KyherVdkBD#`Zs^Dn$ePH&wS>>MNgbUvs&AEgXS(F2f+;B- z$bGToS|8$3IBmzYy^E%k?&e*e2C<&itIp*%*0XE}%FYuwq<v%rj{;=_EI-}Z2egWXYT#dr4xhapW$3E({WyMaQ|9L) zPV{!ZWisLfV)#1yg}B<%N&lja?h=gB;qeK2E#Xs4`egOb3)qpTu=)eY#5Q|i6%@pdEf053K}WW<3*f9KQbHrTX_1 zHsFzH{KFZp)K0EtZlh~#_3WZ6{ie)O2t(o3Qhw@hjbn3V2Zaa5KUWp9cRyDNL?4=0 zM}Eava%mjQ$e4jUd%v|mVE)qXt*hg|Ph5*Q$7{QHn2}GFB_$=I)Ot&Slo~|r_Gu5x zvl_eIDB-@}KIOa)D%H1t(}!j<6&%Daz+e8?{}Wq8?1Ma^R&^*|P`i`9UVODtzIqT| zb-%IK?nAS55l^0sq&^Co>=O*s;UvDR;D-I_8(sON{)qb@1)S>(RWl=uz{>BX#pPk? z`474%C4<8(E_BsO8Zfdw)A%A4e4{<3Ze3-fx_B!SVLKZYPv-h%SidHD($AyG|wx_NHX`=h4fPlsoxD1QQ((6Wu#u z3{-O0oiA0F0&pf{wDVu(%F@Q^LXufl#Z`y&q9J`Cj~u&LA;KMr2_>|4cK_sSRY82|;8X&uIcMH4Fym3VzCKSSMTVU3wKJxmo z^5)>`ygDt2^y%mH@}rMSa5b4*V}y>QTi~0zW|;%@x$1nR_8_29uu7sUbu!dTuQ^j z;&F$;ip4>_{ipl>e32jfns;BIXuanQa+)X3Qo zGFiTF;J1Vpfo1YN?4>6u4(b)1Bgg;#P5(}M*x)kju)YWWUKJR~6L{RGxDUIF-f}%k zfd;~)h!~eDA>$)5-+81;HLk>GVe9b(rSF=rHvwD$O{<1e2mkH8KCP=Q)`1BHybo)G z0XxqY@7w=Ki`tO?JQ*DN3Kr;bSmYuq$LzS065l;*oa?Tkp@}MV%2iQukb}-m|HH9M zN_N^&uz7ti=Oz*kZT~9#(9@^5POp}wrF^5QuY4F_623d@(aI-vyK&$+F~UHp2Xs$~ zPm)lg57%n6EdE$S7@a-F+GXFxk(@+i#umx!BeS|UtgblS9*~gmc7k`xu8mFDRHr2| z?@*e|Xk|u%{#cJ^6mLS2H@W~h^C++48e`M{K3WO)RPruV9r$y7j!z|waBkfwkS=O( zX^`3UlnrDu&v}+KT9Mm4uq7c4OBi5d`7m`ERm25(r>{xpIP|+^%9>`{D{3EC09r8n z$DUIEUA}mrsvKwxA9{HIM$F*GtfndFyCqckAaH#{=;icj^--)L%>?GmqCO6SFgOl6 z#iV-2B_1}Zh=a7ZsaCo#<6Q5mSK->!ONDD~IvNj#j=P=F0~>T!v$-x0GjT6)2t)DE zEeNTUpp_14GAc$`4ofw8=l}Iqjid|UARoUs9YL8Ao=~)+z@_x%e*^eEB&ShPgrN7{ z->xmLmJhm|Uz@93y2Ok{Z+l(v)?GQ7-9FI4g;3oz(_f|R#$_D$*j@RF>b4%Y zumCe%l8gv>YWEJ=3vd0CTFQ{$=PIcN{LF81>(f0Ug*lN*?|9NLzAyO5uu;}_Bw`HQ zu5{xn9MbzS>bljdP|DJOburRh>T089;&+yGezPTO*OeZHsj9#_@My^;Z6Qq&XS}M} z%PWR|u)U(gw@yj9=+K^9|LQbtXGZ3>eUF53oKNRL0nO-4y>u;?Qn%nE_qs*#nophj zp>Po08R6J_+}oZ`y>!a-*R=3c`G=~>`Fg_o!_TT4zvraC3cIw(@4!JVM}K=of)ua7 zk~JI3oE}FC(7=YvQ$y(&YhomjirH-GQ`@~P*Y-!Kvi~h|%qkzh*!l5st`W8xU`6uD zr}$#3LWet2k*XmMFZv@tUiK_VFOXX?nO@|19Mi&KrF2R|(m{fF>w@^co9etk|S6fYx9?|Ab4SJE24&bOWSTtb#- zTmTc&#r_>zf^*0#hNf;*H7oSRU)|c$SI}vNh8#kg+lBe>3EH_oOx;1>dl*pEEDo{L@fC3OP5q@ zHs^L6b!=P44!a8~f=VSE_wmzEKo$I(cDL@!ZMYM;8bWt&{-mUTB@I+w6ohG-AGW&H zb{R6kvw@J{H(ripV%lGVy`KHd&$r=x`x1Igw-@L&4wY_NzC*xn0eiQVHfniH!M-ui zaM}+=6noAy4x9E$Atx0e*zVl{{_AO089&d_KfI?nL~HJwCsRq*%ai78!C4J4nuPhE z*=U&O7&OYj2J&Pt-QGHoj8kkUQ*IJFME8Gf=dX{PlBBU8l{Z!3Z&h(SLVq{$mYK{C)PFj?z2i+1N{5hnjC?pp8=B_^<#4`!=MT z$D7G2jaW31;2o_pFgodcm;ol@d>_Hv3$I)ydI||V5ssE5t2GdoL-$jk6fl1nat3V{ zGG)e-T1jYPVI`ePw9c)gSzOgFH0g`0^#~>e7P8js547@?Ubt?= zuo%q$p9?7prhB+WoHR&q#)~*_#3`;2~%bGhT@ z>%NL?N;npk%m#|9a?B{sw)h(tF8D~lL#*J-plw+Lef&`$LDL_G-no23&h2gAcE1w$ z!U%bA(_95y#=yU9@%pY$=y-yX{J3@s@rM6RS}NH}G;%ZVo5r6q)L+cF6}~ zFhJ4Gml%%;pdv5*tm|BTQs7hU7GO1ffC2H*Qj7RkNQYO9qNVR&UQ&S5&(j-u0TU_W z%-z94Kyn^`H1P@?Kse6$QSd>tI{gW=*WwV@g1zIFR6^Ez61_mgbP9*Q9eEg@E~_5` zy!h^vsti*C@_EbdO-V*7Il-zgl47~&URA|Ojf_$mTR$2lP`Y!emxT7+gZzdQ_O;6z zRU0nffvtAk?_ZoY-mgGV;6U>YeE_8Pr-E=vh47Mh+eY%7%=i4$v5UjoTwNMFXf`~o z{}r(a$$!R@pSEolZRi*IocW{@FQl$u79SLCysQ>aY+=1Ce?ABQV0e1cO-t4#1qjgU zD{omK$KF`neptAYEUisdEhO*K7VADb(xSF#0tH?__-pt-Af9|Trq8i|*ZsS%OJ0?E{b);ACJQ~FBQ*#7Wq?fz zpGeC8zDlcfiG?0UWn%qjdn9utK=(L?!jGsbi)oerX5VQRGUt_99qkm}IO2Id>jXuv z`8h2k6PD~k-(ND;hxW+IW0I7B*LJFSYU4CgPT4b0dT^?5Qu$S529%3=@!?VI0>AT9 zuI?%lijM*=59@b+bGM$HMmGjp^X2s?_EQ#m>^ZooVRDY#t;ee`AvXd#U@8;Is}0gx zcI4%*XTpDrzGs!gzoh&MY@z4oN2d~;QArYq5~~%hyPNfEo<16g3#~bGbfz=NCR6t4 zdjGOj{YAYJX{JNd!HXh`FBoahbl5K2%ugw2DyHy&Ib2miuREaw{5I2u0ZWP>2MH1* z-{-2dDl)qtQ*qiI!vFI*(PB6`4#GVTw|(tL3tr7doZaa(K43fn=zye2MZ(M99=8B9 zD&3f=Sm?qZy-RqDze;(x@vuSh7Z-DgsIeOha)A+* z13LjO{2|k~=awm~oj_h_yAr$3)+o^0$Ld6qh%Ls!(tnqT%^S8Pc7WOYeYIuXU zQw&HxW9yGsR!>j#(Xr9a+1R0q)?J2!N>FP+PPpxPle}cF4P$;fY;*E_b#2a>xaY~y z{CBfTZ@;E}$)i!b!S4DQ`pPF{)dii|O7!>ShxoK?4t}DI6w~8hrL&vUYre5mk-Y2W zr`M$rRfW$sns(|zgo$|vFDhG%9z_(U=4KtH1(vRXftKN9aO(RrZKK=Ol`j8{PsQ8?xpzh|C(F-QKeki){$5P zr#6&H4AF`9-r}FDc|WUCgnLd&CHUF9B}z$p0d4kv4{_wA;xmN>^wKHt7W=~Mi z37?<`(&CSeo?^aaWKMN~Tz$@)j>=~dsFWKC@#FF^2E01UeqRZsmESVcP=1)s$Jc7HVrjjo+awr?k*;X6PyOgJ$;1%AlZ^b%#%*F#Ah4gc! z%x2cNAAK<19XeZuf=>IyV6?s(UlRfuo$pE$zPAC6&TyE8^U)5uXc1xC+H{NG|NPEk z(A#ICk)dJpa{Ha~RmO{uzcfZb0+a+5V@StEmIxf_c3he$}teV|SDJS}c2 zxkHa-d+o&$6OMbR-OH=^Yq|%tScH9TJ!(xvw8Kla#6hDlZe`Ul>mBcQ=(PYl_)^$T zPr2@`La$svcMG-R7?ci4x^8URL`yfMJlh`+d-&uu;K{)$7@fVx*m}N>E7qYpH45JM zkXYLYyRPZV^p{mQPu_>nhI^{K$T)i29s=$6Oh{_cM1n9C|M@@oFfYo_qhK}L4w z*BJQfPxW&PI8BTtf>S8~GtZfB`HW^2l+yjEcuQ+Cw;Q+72>v z+pv)`Rotc{?His-=$rL5;&I}O2GtNrBKpyf5v^*MmSVC|cPk4g4ih1UIy$|SYk)-F!m>=WkI=RbT9ZJN*pgBesx=kF?0)yN*_6_!yR2^Jxm9$ zz4~xvkpT5vf!lK{J!2#X+4ZgwWIk)!s?oetd`!56>O-&#D@2A*dwTiACs&|DY3hg~CvskIiiqHU^i055oy*n{5p73i)0OFrOYfr8w_o3X zIVE}Cb~T_m%~<}~x%|atx^W@m;OhlHZM;f@r}p|yf_b_(W=gvxqUv?b>3b{5a-b_A z_u2V=k&{mmoz-+^1KJC%KHS{I?kjgDpOD{U#pUR5>H}V4i?sW)V)J*r$IXA1yD#M7 zS#b8y;)P65``&=0tTas{5Qh+D<`iY+?qd5Afwz9-OBd7&V2i)f%;v+sb4aK#dwTM* zk{i3+1gHN~JZ$Uqu?2?w-^n6d7_y=+tgpOk@FB84L59Zr%3ycv4ETL({KvbVt9PmK zRc_I4G1&)q$>IU`!wu+*p~5QTDTIc|8S$A7iK;Q1vZO7D7!*WkSxlGPQk;482(`uy z0+PiC*gUa*KS3`?SV;6f-K$Q9ejOlCw=ppVW#u+2mbJ@7Y^`Ya;E{ttip702{LD+3 z@L#MYVo}PaX}YXR|Cs6}WR5?(Y)!d{=+u#e7Vv@vVJVrG8)d9SuXG)~HpI0(>)Fx2 z1YWeu;%nWU6l!jn>mE+jk6dy@Xo*;@rTxmvXxwgI{k(VLem?VM4u1wUqlx~>ZW*m7 zocDWAl}2H>I%Nea{P~^b=9kQNIkQoVer`tHs8b!cBSc=E`rse!F@^A z;D}eyL)aT@ClQ4?t0T(gELX}H-tZs!`T48n3W*ZYbSB%cJTd9~;s@GScqIGSJkUXu z;D^vHM2sY)@={XXclHwR1zmQ?ikxb!lrG6_WM8?lHA}ctrsyWGL+h9gAL98Lf1xj~ zqf~;fpB7y6@aG|iYG?biMOF*MX;74c;>kIkyIqWVjAG4|#ij73%!g{?>yLEXox*vq zn33!m&u&V5H8~e02Jzqz{d$GG2-erxQty>S6o(ZJRQ^6+ErtcauZW(iR!Y(zdinn) zK$OH{CSA!^qYKQZei45>uC(bOpUrNaeiR6li!uc}4}RezS>v`eEsuLp)HV~0GLPT; z27L$MhMNV8=r}vth!->uom==wX6F*5b>}}N^kZvkPM>2iqPTpsb;AIQLTW5cS(Vdw z#u_MJU>RkTj$-N-k-^k)X&sjmj=PPpi=XtJ6AO>N`(trag%)I_Zehi8IJIwZLEEoS z!>7a7jtOgLJr|-XwauixY}t$=;lz&C@pSZ@ct=@L&y@Z!K1CyXv329KX0m46}m2B?nbVNXtF z$AdMVM=~dr{w9iD(=tBQ01x>-v_vCGsA~TeYRHSR0@Jg;@ZR67NaxR#41>I);u4NmxBV5Gf5NL=rSs`~JcG(1XM3|m=LSZ!Tg>Nq%t-uAD+T~NH!e`clvblT z;obus&Y&;@zhzHuh2Glk)aq=4)AsMW2{doqiRC|>t)_oW?MS2Efb8R&V9Hp>F^C!h7#C&SBmv8rvX}*ec^m+C<6P;(9q-{WHn+kFy;s`j+)RQD z5%$B;ESk0FSS zbX3f@=b=9cG-$tr8=ehx959zfDCkTw#J#zmH&woDjhH%9_3Pz}431#l(Lg7oqZepf z4yc|0^Xh$V1uCFZ=Qqiw{F6yX9Vz7Br? z%7WvxO?X|Z?0pi5mVeVomqTTrb878B#BZg?-=znpVK$2Z*Z+)llsSFU@j&Z}P{KX! zQy$yqm5b||VS#=dLRlyN-&= zNG`7!{W?3;#;X&(VxcjqO!rxAl9aW_OH!y7*l6Y-DNJFHbaU9f0}12H0v%M!19{ zjA}V}b5f`oefotP$1H|0J(bC3y2~?m&b&S8cDZBG1?MNad#Vun&}%^|xG5ukk`i-_ zQglJ`al7q|`t0aG-A_t) zviNvH;`=1{&r$y`m^pUE9kamBJ!#JZ-=H}d-Zb$xJ_pF(M>0UM3OKjVf8;)uli<6! z@h4=9)(G<`JOAF$gdA&dalASf!om*cdP)&XCYy+{EZFpb=$U>|!PS`cM?S9_4s39$ zajC${gEbVL+PPR76?AlX-oE#!Rd>C8X%;T+ws2d)4(v$vwti)yQj@H$=*!NHBes-e z-b>L3VwlqIerhE|?aDWS5(({iV z`3m~8^ZYc;V*9bwb9&&Uo_t_Q+=PITCZsUE+LzhE{mmY}9j94mVh3+aJ`ekk`zAi# z$V?iV_feJSpQAc5UCY)KLWf}z_O+Rc`-Zf znw3Pzd}JFNIBLEnLjd-Bx?ye)dU(gh4^bU#mSgHt<>!=SrVXv-AM7Q_cU&p+h1mn8 zfLf}bx|)Y{{$uieCoSA};zoJi06~s$nUOJdB@DkDzCpRhue`Bmr@LUZ@-a@Raxiph z0Pi{PGlz|R$-$zVp3;}z0xJwG6wP}(Q4|OeVWd4&pY=Rmo&Hc`BRcn?S%WEw z<*XltsuIn0RwGk%>VA-F#okF}M1sc?whTm?6daPU%tD?%+H{iWsGn2BBJ#BkrKhf< zrxN>6C(7jhrz4TZf|4(vf=s&T1Vl&5Umf@h%(u+Q)_Ij>ZEnarRTm z&Gp=su?Ys~+M+*Uqkzw+1D7a3Fbl`-G!c7w0@KjY$?pg)s_Gw~oIQLp$> z-9#-#t&-^s#q`4GAKP-p<^5@x4o!}O>~jdlt+%uSY$nNL4%FJXCh*7tYwKDx8sz!X z0LU2Q)8q5&AP0{nbh@_|p&ZF|Rt;ri|4o?y9Xu`ou6*sV0xhe@ z_aVzjPNqowl^Q|jq;PgE&2eObFG~vRDwDK$$Qa`^t2GBz6(hd9q`#8x$>tA(Dqll3 zUy~iJc<6Df8g=+*>(@m(7Kt-=nJqH{9*+kUsKpE5Ko-2@KC%{Iv4?1(CN;hhFOEu% zlgP<>*!u=UnO^34T)cXfOWqWI+UzL*G-LH=H{VAbJM4%2OGBfAps7viPipPwhKK7f z>fa3+>+%34zcAPm8tpEYIH}q+7&_Ld_@C(OFPS^c7SFrV@VQy313mA16mz_{sB=@C zo4s*0(zz849a?9E3|~Tqv)&WC|0`{aMgeoZiFqz>e3!WygR6p%UnNfbDoyU3U`?RZ zU}$pqF4(7mtBBV+LS&sN6b-!Xj`>zYkWG}+uLwPKn>~#g5$_92YYU5%KnF;F1plp% z;$bf)Ejo!AkXO#w`!Xch)Fr{1`O&9%hDxD+A+!JG`Alzss487gBxFH*q0EDyKC|~- z(kB;5%Nqv$$ORw^05QlR>E!9@%S1Gx=k+y9YTvV;@}ieHdbpv52i{VQ>~{@PG%Goj zBZL+j2&qeQK=1dJUejs5rPMJEZi??OZ%ZZFxh#~HO!yF{*$d$b|8mJ{me`!RqtMjW z24=Lkj@t#S{Cx?O26oTSQum7p4 zjx@ySW*Lw+7VG(>iD1iWUNf{UaJLGjII~?>=XjI?h*2$JHg@w_dhB%`csQnIlIpU> za5NAenr9&%6fJ*pb0THJs(EB`H46DPd*S&0n~WC2S9Jq}H4qEPSs#n@gBk-Qd_@=f z%?@A}FXou2mm(H%wH>|Aq)k`zeAO7QE27hZF+lDQ{~#)kU_?>9OP9Mo8s$5tixk&3 zW{U^P3x6~sOs`AiB+TCJF<0J21ayg|^h!=8@FV=5`my-AkURcqI!g@pSO`jG!lvag z>xsu_pB^t}cAlP_Dn8FVUK!uhn`2r~ry$lZDKn{l-7G|5w(4&^$V&g~mip2_=&Hu- zplgBO`Um~JYZcFPuhIM0_weyHZ>;sC)cmj~TqyFh---uU}_> zG~qdk+pGIr%n(?~6vD)_X;PvHw|e-z7yCQblT4)w$7rkL>LYmT$u}k%kX69NFS^!h z6uYb2SC-Sx#o$F8<@a>()bEw&$45)KSJE3|9g{K9nJlq#TliYz z$tJhtdfb*P;@&C+hdN!@#n#@}B}(c_GHueTX7NU)9le_05W-nZN%@4T?(xrTqt`;B zn_SD(jbOB?*s)g?-r?O-TD+>1`J8?6wg(DV6a0BGM1mg-J2Du>=Ju9p2|xb+Nma+8 zMY}Xz&li!6+Ev=FmyIDYuGs9+Y5nr((1;SX8A9=AD}2I_+An5%Yyc+x-MO&pfX9b7 z5X8gH#`y&7h=Pr+e|_D~zwhSBs6!j_2W*Mhabqxz4f2g96u0%gBEpQx*cZQ|m;}~v zt>YdDVtq^HnigCE|5Ae;7IP}adWI{TKpEA-uZ*b0^3mw!!a~pntu1YXVfp2 zZ|CC2>BZ$H{)^0V`G%)QH&m_#`$LOA$|;WCO|S9i%o20kE&SJ5Q2Vk;^lz93=mm|Z z|KO1KMzlHaj;`q9B>Y&CYF!FAFrj{Xd7wei-XasYmggFSwU~SC?8x=AHk&YXSpefJIRD87Y*2TjI*m;qBQJK`2N z{lfflOhF?T?u3kSGP1+icsZU}OrgZA!E)$^yJ0ZOmuniu-eE4m-;h9df$^{2 z**j671Je{7SLZG&R(g5oIZu1USFMO}e(oyn3pve$q#?^T&7flvu8L^F?pmJnw|q=X zbI$!lk+RW@VfHTxSvV<*a3YhKG%T4uspyEuq?7K|@-Fzjr(I>H0Y+vp!74*feKlTe zgjMU65Ak*H3^Vjt7V8}_R;(kwjjFRD|2lhQo*|VhJrm{b304FL3P~K;>l4+tMh=ws zX2VKuV=gs;Udk;T&h-`qA_2IU^US9{YXn}u;gOyoU#q%wSA=)WXpy#7s@f_10H}og zpG(Lh^dP9gC2o_Rq?L_cpjJ7pd~x>6CNUzpL%S6^he)gIwMA$o6rO_uU3!~h-_lk; zNlFoe`nQe{WZmK<#bbC@4xujtOsgVX+XQ@gq6z6`sW@pv{^ZZtDTyob`=|42tgYE* z@@Lu)7opK;$n~*x#7OLRHNV}YO~qi!R`G@3^GDqU{3N9C{~|f?7G@+K5!sZa)TSxm zG}#4NmruSLP^^N<4|c~CsoZ>1;*n-bM!9)9PHKcVy^GHyEZvRq?R7Ct#YPREohUB9 z7IDSXGUI~L(>J93=fUQ>9adH2)|>V#-bN(u-8Y9s!DxnN0~*&74^E0RYEMyYI~Dd` z`+NQ5?h*nKtP2-bm&n@GQz@2xRH*qD5NThVff=ATuYYEiqiI1{}ixYn4>M0?WXz^5<5HYjM{(S5}hCgyn^)nK8N%QraRzilS-3>OE(djIz^? zZB9+d^ml19-0lvF`jks$;*XdP41KM1*z_(><>a+SkN4$HU+WMi)!R^9+9l)5QJB6K z3)oh`o($QVNZ;-zoXbDwX|Ac*P6}%M?*?J1le5M;vY|VrneQ3&i=b#Csd)yk_?$@Z zhT&s1;FK7f@|xYD_E_Z?hSUe=Yu^ZCyZuLoYu-U*ESp7Qd)y=cbQlip-|0)9=A;EyUD=^ zz;n2`y&{ZFn^Q|$yYS@19sD9QI~(b)?;V{Qvt3O7Ml6$&#bGl$?XX)YzWVDa@v!BXW)H10 zS3gFVduQ4HayQBNCjZ1y>}x`aq-K_y-QpCmEmL-D)2K4r|H=CmnTaZLrkx*tyY;~zi({^@{@>N(> z!}3T0VZRt_QUY@`WJN$~Nq1}8gLbEgryaJB<57cb(vRTO5>(OKs(lhiLiheim`0gm zJ#Wd|l80P0`5&ThqlP8WXo9^$7dFgm)cMEtVOOmKm*hmk@YGnZ21vW}g~>y16=KIk z=!vZr+w6~o-{xv}r(YDN!&||DTXjA~BcNERurg#N6t0=&+QH-nK60(Mm#4H-E7T1C z7H4e~{fXK)HJpt`3WLWM7&L&y0(`}v$lpxf>%OqOt^705@y79~}gjnIV5W@AY}~`HI+UVE89D$h-bS_}vD1id3|)r4WS-xx((xaDIWO z+Rw$zO*JxWwR)GWd+42p?Db@NP}$rM+yA+t@A;MBaJ znSQR5oZJqXgTPfe*h6{*p$KWSYEF%Da2giyq@2#a~@)4=-^CA)3!6xOOyzZw*PG| zLV-o3h+jopSjrzqlB5_Z2+%(^iXNUc*JrEM9k@PkcY$y3{*bWCn%6ai{`H#4{P%9Ex=(IpBR9ojr{-_P38O4M3Zd~K*g$PE`H zq}1Sq#v0As&!__vIv8Ncfeu%4< z0vdRkQ_NYqi%c`h`m<$cvjuWg4uA(FL_9`Z@pbtHsraoKY_BCc;$ubhglm70?75<^ zKRZHL{46=xBp;Sxdabhtid3~f_bOF9soeh5{o~hTt`j5XJE`X&(v~AXNsEB)r~@Wj zK8EnbHZ)J8Rj%;TZ@8P^Pge{Ll$P|@0E*R(P&HvWtyH{Ggai33*1?{WWR#`U8L@<- zUYA-cS9Gt6JN}%&pcxj1>-hJ-ygxGhx`i@NIGo(v4Bs?)$c9H+o0HKb35EAE`~H2_ zaA5JJqkpB!gWpQ8=kvMWGh)oomLREF9qBP^B9So^!r0epzLI@xEuzF2vdgPQb6`AG|n-?6G&h`>N+0FI&X3RC&T4+q4LAJVM4Twi*)w3Eb zHhSf0T*ptoB&aTYn!T=$4|q~&%j!45_HPh~2El%s4;MKNFI1+(ATe(7Y#_TMf0bA9 z9sM1#Er(*EL+@KmM9qYwQm7@Vt66KKJ_Vo_g-+9Mk^Pq{$bPAWq9Ga2vJOJ>o`LXR z>mk;brLUhA7R}g1#Mmv6c*=i>=V4r)aNDYY7LTUJ9H=yZnVAsbC)}Y4S4PqNy0oSY zjpXOqU(r_0!~s0vieEC z=%s4#Y1(%+|9C{dPAc$otG)Os{(x}{Ct_44NE+Z^ApL%@;vZc0X4<1Wf#;2EV&@D2 znvvJ&6Q+IQ3)c3Gptyu%bYx{?fK9=TH+-DO2_=6$+2Pl=f#L0N`)WJZ3I(fj#6<}* z>B9*KX5;}qs`7pyDJLn9#pXClKAaHaN{1#X*pP8Kzu!w#=+n&ibl7;TRl-KTC&PZ% zMqSik&FfkmItQuqq5lkB*KAxF*7=Nt6lznAA5Jnw4OHenMS-V=JP8d+##`ht4Alxj$@8VUprtp}>E(2)kM00c3g3jM=_Oa6_!HjBU_0EtGN}rk7ax5(dJL z7RYdlJ$Eke|DxK98FVo9y0%6f97zgG*7AU1)ZA$u%}ZGhRE??EKg#$20~>*uNIRa` zNfBHX3JH2~`1&LS5b!NfT>sDO?1KJJ!RY77j83!OUYI@;Iq2XCCZsPc@;8g!l)`H>xt2`o5h7i zMsW(s%-_=$Nc%H&|7g;5bYnIr_M-UuSY9`qRTL@1r7-`x!ZMR;VzW!qi-5318j0aT z8tVui%X&%Is;Thto3thkKX!mt>~Nbzh2+lnn`Br2D}sU_6G>(TU*AuwGz|5jGEp4#&Ay=s{eHyC@N#;uQ@j4-aVwaQWhS;346PtxL|-DKI){jJleeB`=+ZKNwSpa2E4maXK0O5vc!9G&1g_ zc}0(fF03y4tYm)bN(x-ttiPoW4xOAB`aQM4mKzopRtj|s8`K?$WeD_Z?24SHhwp9u z9V1aVLy7KYHr6{1{C1hy6rV6!$Yhk4XDXuiZZLk8BG(!rL~6}es-5D>tt&9~s)~iJ zm87j~KO-xXy5_0J=MbFSt~Ge@KChvfXGLKFV&wa0k+}JtZ0qF}jWcRF42yga@o7{9 zc4u3AAKgCoFG{;tzOAvs5}ToxV}w1d7oT1Q%lYCcs2{75tsl9fM>-r)=?|w?Uos*> z&ezLWj^}@qeb;>}zzE{MF`r{e)DFTx=O)|RnGd~+Fq5(EcvCQro3uFeXDmHs3b(e| z#3~y|5b20R(OuXx_mRUUV(yb?nwa~0@rq~91js_>ef2`e0tO0#t6Y|uvff4;u7h2P zJ>B2@xXiELrK6?_WTua|6Lwc~#Ta@;+LBS+ zXG0xL=@l$WFD{fe>GYN(T|A!`rc8RvXtG9O;Q02v;V!+&)(ukuG*?3b)cle<;qt`o z=dK#v8MW>^=f;m}8}GGCSxxJ4g=L>=^*;@Fzzl&9m^{HUlbX`oD$KO4bLmCYigw$T zCn`7mweGjr3QuHb@HCxv{MPTamJ)1SJnoPdb(+rGXeZnOt77@VoU=8mHFvuDm`oYz@@)hsv?6 z%zy)Be5UEbab!(u`!uP6_zgzmO){5YWY<`A$5VM>DY$cMh`7AMW~wlt> zXTZjopLB9wI%GYRM?Tj7rcj?nZN*R8YBbKlEx?B7NmYyJld7e{H>%XD@}6mzM}gA3 zRyD{)@xhH}w>S8zVlRcikZNgt?*_J9WVJnc;#KtgxW8tmUah86eKCLyvT^h@F81p; zxmuI?aT(E6e}d6;&ibVOu^~h;I*mJ(5779vRnLQ|-bLV3!RChoNxFnD6R^alWN_3b zMZ-78OFSOyN#+F}H|v?lit%YZGc;w{=4V#h^#uAo@x!si3p1*JG3?0?qykM>YNzzU zdTgFx-1)M~MP|QNm3+dQTuyi~x#hnc$y;R~|EI|+_0%^{aHsY^Q^vE(kx+;IM@LhY8Bu8=p7}`hKzRzsKf~Hkf(Il)m?d}~=3I7~ z5G@lto&YB8KQpM>VzSV8$ibv~-$xohr6L3W4^3AY7xniwr9rxpSXe+(X(?H{yFt1U zr5kqX2I+2Tk?vX==@O*7rD5rQ*5Cj0yx&)6@4er1X6DSW5>;;hteT$8iPJ7J>6AzW zE!aN*s}rmMc~kDBuzfI^+|dD@DM`p1Hc?fr$y}Z5O$6UQuw-Z|U-ixYLp#%tk*&YW zI>eP4A{zB$zlZ)OROLUrx@_xo+EZH_#8z)g+lnz3>>Q#FcX$EdGDepD;3u{yMl``h zzz-i=s(jz;V_ov%#A~1p+3Y-#BJot{NEGwx8wG^ObpB%i? zHNru5uxl`GH3PUTsxtpLAO*@#8$*aG-k`!1IPCU=+Jhy%>zO@sskD zLY92zE!FVZ&5oUwE3*YVo#nP4MAQ41tVQCf-}s?~9Q9o$*nYeI9T~X~8>6Vmv6~6n zgS1hFi#BQ-9#wMA&wqCuYUqRo<7<OHch{InakB z9T@Pco0!J8?g#`wnfV3v!IlY#lfVFzxPCC^b@6^FXNefD@kyE`u4v+qau0WR1|`LB zCBXPj%b-JpVvtrv`sMwqp;0sb0niug&Kt?v(kftxJT$ZP`%%FBFTQ|<{t#0eYr8%6 zFwfsIFY?K_Y9p4SgV5%JCY>uaLG~Q{J8?AnxK>))?sq-@x~)(|S^qL{ zc?745_6n16j%*Kc!@g$O!Fd~P2wwkxpM3#szwZk#>~PK->m13T?qzLuZOw1{m(ipg zV!Yp@bt>R(-;4v2PL`ZSV;5brp?H6aN;P|Kj!Jy%SksEh=H7b|8dft*ecuy;U!^WO z1QSo(K}p}=e|={{4dOM60ZQk1O^MT|9HNUc(IcE@DVn6L@)%$G4~E-{N~9sp7r+=8 zOJJ(+?}bKxV6~U`80J_NOA}8V=yzaE$@}=w7SZZ?{jy8_*^|mwD^pDhv;JAYMRa!0 z%I-bAuQ<`FSeW)ug2?5Sgm)qsr(}T>OJOH^yWOWrPHs@dO?!SFAuEteF?%J7Ymwv? zvQi%~NVj%kIhf+;$AD{RYo@eNPbZ}p787SXb+AkUV)aoueVx7MgAH^Vb+SNlVdI`_ z*LNycJn1%C>WXuyOy%3!UEaZG`)EkcWKAhaBsbl{bz7U;J}3jTUA+!J zJoF=J4!ox&@gR%W!6^6xAA-NB3`eNK4`WdPg7zYdk(1}_E)MlnuF>66rz%Bbzy>R@ zvAf5ylJ?wP?hHCMHp=wzI&v_Ae}mAR&CH&u=PetlzP`;Dv*m}Ukkmxjn_>VZnWj*v zyzN)}q?8_X!$#Y5&=+J0O5BNX0}UyEK=#B~jryC~lwR5WT1#yDIIQ;z5}YzZ*+0>Y z2GkqFI^7~uIZsJ-Wz$_7T*u$TtTzg|!8}7AJgpDfOuFu(Y%LRr6Fq}exW#)X%_7m& zxHM}~?nVn65AvU=TJ!Dy&6^J9StrtyjJ*)J(%h*r&I?=Bx*cBS)kBA1WkbXZ^wY`N ziRHz?vlOEu@H@Lj_lnzBQAj5E*)*}51Z)Jqq$gz=kPF}c%79`p@W~b0Fi{EfWP5|< zkh**HiyvwAw`lds=8VegIWRWk6*+eMuOm4)Erxkn(xGA*-j>p&-spr2a8OoNukJd( zyw|&R`YSsqH|6T8SRg!~Ye7K=`q_nhnhU#%u}@Zz#(9^Y_4OTvLYdHMEEhL^(0b|2 zWp20*(8eNzxSKm7q|1ve)tgy*j`HDlJ7<`0KPWl9;ovhs%CH;XC%t7S&zMkh?v0Ac z>Hz~8DPW*H=Bu)7?_D`8vYays3*SB**m?mp;o9d?|F`3=csoy;V7&n|hK>=mp10Nl zd4l6m=!7&PteMs}|K01NAS7esEcAbVlGG~y4^OIre2E=Uxd?-BeAFBN661e!b&09(kKQ!P% z9J92}}+-k$;x-?8I!0VK6z8tRGKBe(KB{15Y-kXMoYYye1Doivy3 zPpkvop{78qol4ACA&(X@w~C8|0mHJ+_dOF!~-?1<=5+QI};k0C}Iw{sfqXKFBymaGJXGbm5m zov(k(IuNlmAbg()W-8wEMF*d2D2pt!R5wZrvsg`PGJax@!T8#pO+rFKK9!Vb{c1Bc zIZO&@x)4Kmrxi0eb+moRMranahoip;p^Tyb9EXiZO;T5GG=LcQDpSuKah7xR^&9)4 zn9@IMBI{nU?;hcl*|X^GUoYkSZ-Z__dPhJlAxdWmL`X?dX*Yy>YA<?Z)QCbbL6)r}M9%9uhXfmvTmExook@8Uc`@;;{#*Zz&Esy;qH9SgG{v~wG> z<16}&5A|8O?_EEsztbKx)*)}%3aG#0=KHs*k-&`ze$iAW(uX&aHIlF5KN3eIcj2{Y z#LGXkq#!ysVNEkx*iTo?-RgHD?I;lt>i&^8>Hxv{Tn6i@}J!CiVK$~*3hUk*2sdaVb!$oqLz5g5b8(!bBRh5P=o6C60xb0bIvL(#&Z1YTy&>Q}=O z{iAYyb$dx;l%Q#OstZsMviAe!zGP~3>!ZKRmzYRd(Dpi9ujd z4jsGAC-r;JRrf(JcM5o=ch85F9|0tt{)I#d_}gN8c-af$lPTCaRel08J%@#o*GBtT zFxvcSx`c4P@iLtVyojuexKdaH4Y!Q!# zEj%Dr0>+P4Ry1EmB}Xk{fyUt7v(?|iv??q>H{9}MTh*WT@s+}<-3OMTu?zGRPYZ42 z!9UCJphuSVq#P{U_gp14c)JYmHU)~_H>(A2SSHyYGRnP~@^HZy$#h@2^3-cDVy)=V zB#Epxf}LjytvOgczzrh*{F+em47X`xVlDd*U*ifVZjVZ~LOFX7agui~C+>n@SKb&^ z5qxyN#*x2Vzk|>NSC*(R`1~?WjR7mLTl>2|SCQ5zpMl!1`QBXOH^foZ1yN4K6@da)l&?Vyh;KrZbbVBiiz_Cn(1C{=1)Dk*mL6qZ9BybWs=nid2&``9ISaOvJRN zFr)xZAP{@hpHMBfvP0-k*~O6$VRt}gI6hSl#L$MDll!c{G)}RR#yPG!$!cC%iarh! z$0m!tAnGxFt$mnB16ST>t^cP-aHFko;p`4jjl zZtqb^r7pm2BucdySIO&y`a1o41C3J1Bf`RWz2cy)L>tEyOM!2%nJe}fGSa9@t4ODr zzFl9&RUL?bVptY5tA~fwZM)PZP^&@9lqv9(1(q3)knogdhPP`mhS8vv^Xxrd8(K}b zs}7Bfx`L7IaiYbz93TA8=1G`xO8>)jYAmYnk)jBJvg@V!zyQowYv|I$mQugopwu%U zZK}eH3U@rQK-@bPY7|MiZAI&=j>;Qa-JuPcE6mPqH-^%CyT@bTZ3h;FzrI+$6)KN{ zJ%VTl=>oFSg13R&H1?da;{Z_JxviKo);``w@bX<`0o=aKZDZrB7}Ldvr3TN3)Y?Td z@$Edb?8K%fKK4nI`309tCjD!w3?j}$>(6}Fy~09?3zWkruzXxURHYYA#SztaU)gCs zw;SCCFmhI1h6wGxcfn&;TMm}pp6rr0l$dw+sR2sZ(e}(cGNKnO{`6RICKSn7HAc2! z2DwMM?mWH0ANIdp&=s3YQhHN~r6(sN6xgKZMo0ZA+t^=US%o=bvC!eu;++hx=c+ND z@0wrfwwLJ=>9;H4_@o^Xa^%4;|EV6SLXbPsZ!6ZIza^^;*j}p$-&qKJ{O~wj^(Ze; z#!rf`C_&}Hdb+?c8Q=ijVF|-Fp2`koKbEKS03D437S2QV`p|Z#`t5WIt%YB{Us%+} zpuCA&jcG3XwgmqDJ7>FHIw66`sdXAV!n14x}{j6CQqsQLti8P&$h@i803*LgU zOT0Ah!sb3mYPRp!^c=l$eJqC!0X&t!f??@@Ws_p_W@jl~u5}^5?RRm!%`G)qM<3+` zHl%|uK&Fl9Ktl)i?Rc*!`QC;>mDq4+hO|F46AqFK^Yh(2?v6as)R6bsl}j0|p{qrT zEPc|P>}rhH-^33jp@yWdXF6>Q=+xn!hRzMIBn80BXNptGg=l~2jqSIvHi5qA=T3f1 zS%|f>(4J3cMLlk7oljt6ISPka7vO_CWaCq6gmsz-UMTxv!*^f!2eb%3#Iz~5lqT_C z%d&?@@_-U&?t@=QxN)9&diTI6a&?rcjHr?-r2WnZnbnbTOxQnXtK@c#+klj>)c z*&gst8Lq)i<;MopL^<54rk@#y@?M2oy&e!C_2Qpa%>SAmgBQo7eeT(&SPOG32029z zl%>#Ff;%*ER@Ep_rfJfffS2D5lDLDpm9u#H7 z+n~LG$I@uNZ+(*6?(_!@{5?~irJW`$5IUu+{UG8XQ9%EWYya01bD#jP@P^7vbaZr4 zo?GVhN1hjPTcTx7ARKm2f5v2B*Ov<#=w!+_!v241 zh~RJFKYZ35f9(~jQ#-6!NTrn!PbeG!=u}2v^?f{j7l~YH*v9K~k-4VmcPZm%@u{3K z_HtWdV6OA|%Ck);%bb7eeWvWT3S?s^VA&43A1O3=_=OK7W>8BM>4wc*ZgAz54w)o`Zfrxp_MPS==?v z{0Ac0&mD@^oOrY?mK-stIy!4y2@bU~(i4)6_oLg6m`W3=e_YNJ(jCOWcK6#2Zl#5@ zY)fz^hZ)tv!kPm^G&QBxf%qkn5Q{n=uqV8uR3c0XcFVK!r|&a4^~b+w8}O)tLpB8o zL2ouX|15-qDAlm@(?eOZi}OFiqq&;ixMR{QK)Y5f+_%ScUtO~rA`8%2>}=U->?$A~ zne1t?VaeTHsVwt0W0qmWR&Ju_c!uP*p*>q`D-umtFwhFhq z+dW*XWaaYut&8IhoIKsLUSBuwm0akcbsU}yOwFpSY*q!pIiF}bDehFIzPBBSu)#fQ z<@NyK+Eork#o{1eoXPuxww1TdvV)igb#9Cl(ZFJ(Jzkb6FBFp2S{plgSTly zd!DZ1jd-`T+qf^sdvIJ`H(K5*$yXhLIon~QnEMpCeyl6ExDN6UIli?2mZMx(*w<{; zNhc(}?pBW!RJNV^3fbNLiC(>l6(MUSyAGxNX$L39Wc6OU8)iYUEBN{`-1EwJhtHQ5 z1e*thJuQ-+lyEt86@SQ#ydox+sXn|i9Do?@njfB?R!yk``hrvh{lZm@SI6RcqfFIaEdw;l7&9gcrIHJEfL z-b5ozfA#zgeI1RjSz_(sauwdEe1*$9s8uo@#=y@JY8DPL;iwmFM=b=s{>atu!y_Wc zsqfp{lwR=}c$v5E<-QDD627G7I$zHmvg4}-{Jn#rDnQE9w8Roe`0c z$xX<0{VN#Q&hE?J-uTt{zF%TZk?(QwYJaB+l-+SPDA{_=%_m!RxuiUto_uz^4W9uz zsy>bFE>-CW9dqo>Y`r5g^_)jm-JmmG$vnB84k7FcaldAim}%h8!fd7SA2>KtLD8Dq zJaeCJAVVbUi+yjh`IY9>;AZ;x7hdP?JwNi)d=svC~ax6&fU?BBvOb zB&-u1JZhY&rGA#21UoH6xaMlnhGKacGikPgp2ahjwuT}rmXE{(-|_e4yniW!?GCIf9zLN zfV7V^EZ-WblAPEvc%(n74V+lg?vLmcMlW3&EV~x92O0@fa^0CJX7YwY5XsyH{>>tu zZ3r;ZOi>SeB*JYRGc*!!pK<|zypQOsBF}XND4R`W|0GYUM5wB&B#M(8r?;CfC(V3z zZF+OGaxsV3$bJrLM`H=+uOqrHEhhBN(EQW_U1WnIg5o}<#l&HMN5(4{RaTs(c^@aT zd&+4LrNWjt^zQDVXe+|3k!DDQj&JnTGqX`-QfPD)?nh0wpIQY1|c`v`b74U zmfxpd7k{tkJv6|N5BB=KsQc8rgH9BYfC(@9EcY1!xSr17tr>seMGaC_6nac?5E@Q6 zWk*WCjGNE8mV<0`HX&^U8ZJN)jcOb!v+wpPDImM``n4^(MGLRopY}d?dRwI7**Zv3 zzT4>n;tz+A@dwC?RT0mG#N8u6qDzQz;&JFENlmaN&+4yW$-;jminRYN%H4(Zq{xNB zKHPL=d{@6KfW{q;wo>EbL6w=iOC+Do;AUo6Kdia}FL!J>xoWZYe0a*h624AB}M2H;_a~*ml8}4dsbvgZc@|$|&@e z%p}+gsnUkjEmTeW3Xe(@cJX^PLVRkTj3@R_6tEd6}+4g{}ZRWhS;3w zlT~84f0s((%8f|2^rg@dEFUkN*IT{y$l4d(O-2;-U!g-kLw(Cc;@qEyPCEl0-=k(r zbYT7}jq4b-yNJ4{B+j8!=&kBBlJ2X5!+U1 zz2y)`ZY8phxh3a{=&_=$KpbQSAGTyNzh zlGV-h*YzE@xuc|EO7e1G*rPS>gMf*iKRL**AsX=dUnE=wYPI2acQMV=(zPJ zqW&(WjEclh7fyX?zGJ4my^knD^8C-%X;=9#4<5ZE(895U(UH?1dDhFHm{Hsml|b+d zhD$q*C)`-`=Li7&G{5cvU*x%8+$T3n{Wm#9iiB(S&9aNT7=;h0bzRJ`Zgx*r$|c8e z(le`1V{ar@hhVxMejofT-_a-r4^E>%eKQ=zEh(-Q*C2ZGT5KexMg|~>#$BkMRno2S~j z=(3XNoV-@Y$O`JkjZm_ z4I!{9phGU2#=8+fnGSBr zPT6d+WzZge)<`Isn{!f&1i=D2A&jGx*3CA$^Ya@YOBHe*fhSLVsYz5+?Vss1inNlo z*s*VeiB3!R9`n|&rUP14iw{TN9+$W*qTx}phNpjJNoHn*g(P=i4sOGwK~Zi z%Cfco>=`VA+% zSSUK0SvFn5T#{+GwsUY+Crf1it!>A#x%}@fY~S{wQzdFOV|H4gx=YPCi29qCzaVWK zN^WMCZb%FfLO~ZHGBAiK!a%fEQTj|MLOIQKlqGXhQkaa(5X3{%8penlOU$ZPH;?%l zs4{}dLlgBBBw_Qmw{qNi~Zrk4$_wDgM#*;!<(CRh%ZnpHHA56)4)u9g=Jm#YhH7~a*Bkqh7v~1+YB4l ztvt4oay1IgRSGmu=KciDn0rs7mK8zbHDVxieV^z9^4TUO!Lde{hOAVxK)wJ)si;uO&o&$eBpE=E~5n&_G)gS~|GGnuK zz>j6Y=C}EcUX_10g_*jyKODWkyT-{WCnZFy79EW0{aM$Y+j_DfiQ#+CaIGcgHP^Q& zpN(vS8nq&#e%iz!8*i9sMh>|OHzE7SPj&M1QSY={vQ{lwlS!!v-s2yXhSAM6*Bz+xAt^>)yY>C*c^wDDt+8+EJn zjzzYBA-r%>zaY!c&#Usttg8g!%semTABDjx&zl2dUJt2?2}Ho!KvHZ|l)shWoJ#|C z2fX*rQz!_|g6OEo2zG*z-*=+pq_{@IU03js1`)SWV>nm?x*DJ$%p7iErT(~TIjSMM zaeQ;2+3Meg-$csfY?6+JVYfWhz@ldlmTz7@aa76J`<%bOL~Xa{z~Rf{Uh@Yx%8N;7 z26f1}xxAzc7dBQUOy1PBfH2*XT5<`!({jhT5p-%eNW%T+eHD*85?Z{3;&M{Fi-~jU zv4^LBtD}v64FxC+B)OVn3V(ajuM)JK(uYzZS*+hSmU4(o9TuD|Cvx;U%9a^G?$zH~ z>-UdFZ3X?jmlvQate+= z$!IkF8{8xB{E?rlTYvtmoZ@y4b>*v11h-fQwzs3C47a=Ni7$5q$W%Vhw=dBo^W9cZ z;q-v*7wg{zIBPx~&jH`k6!Dk?>P25WO?cIy*jQ;OY`;4VC}Gl1CJn&rS3Ov!bUCC+ z^*R-H-HHx^RuiOUfu=Al3*YTNxZxV5jku~ZF9GE6o6dFGi?9kS=vCwEj4Zn4S4xLC zs|t@#jZL}sw3_D`K|6nr<8_W>z^9s&q z{#Rg43q-2wBPwr?MW2a8;o9sljkM!Jp4z8)9*B*O_x{iW5pKMO#YOloj|_Sg zPIj5iu^Sy{Um!#fSDgMWgBGnh`YJ2>AaazDV@*0pRHt?7wy#j}_}bUgHau{<{gp&X z_~`J3hR%yObMP4khy3XGxYAPvqa-^Jt(Wa@E-S==#`o`NKXpJt+9RI^*|-($H@LA@+0x)svy%KqJI6 z?UY)S6Teqg0D6udnhkKLw;!Af;sg5+jZ@==q;V-i_f|_FOYB#z^Cu z6T|&;k*DcP?w0|y_afIf9)$|eZ8rknniDrdsP;-c>{izBx+5d&qL&L~jBy3<&Q>|T zVeqbayBQH5DFYHV)-SUs>{T+Ge3H6^zLEWX^y1gu4ni!?4Aa{Ls&-ohpXG!;VwW2L ziD~I*)392+9iZo&_zmYg47I$?UpTICaTdA5b;ff9c1)1=a{@dHJG4lxY6(pg$iY-^ z3D=q`F;2VreveXFh;$LNY8U%vOE_Cuk;P~mCH~|}RkfTa8Tx|X{(;uxtK=is(7WVo z1objP^!QEfij_2NL&x~RbxRxD%MbC3bCUDd2hAc%(2iIvls`dG9`d~qp7bQS+yjBi z%dLW{!U!aGx$6kzi~VM;FGfWXu5lokI2XdPxm$gJvSKJ52tUM?WjynU2Ow9=6tmra zjuL0&cX9L4&#@%q?V+_W)6JLdoB>!3&bzIrQ~mSMYI7jeKTdQz#$mje{n&FO*b<&v!DfQ+29Kl;&@Ae)=rW
3Ho?# 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 iVG#a@9)EMo)&mu^ Date: Mon, 1 Apr 2024 07:11:39 -0400 Subject: [PATCH 25/39] Fix Fish!! --- Almanac/Fish/FishInfo.cs | 6 +- Almanac/Fish/FishManager.cs | 85 ++++----- Almanac/Fish/FishingPage.cs | 19 +- Almanac/Fish/VanillaProvider.cs | 76 +++++--- Almanac/FishHelper.cs | 292 ++++++++++++++++++----------- Almanac/ModEntry.cs | 10 +- Almanac/Models/LocationOverride.cs | 2 +- Almanac/Models/SubLocation.cs | 4 +- 8 files changed, 290 insertions(+), 204 deletions(-) diff --git a/Almanac/Fish/FishInfo.cs b/Almanac/Fish/FishInfo.cs index f83cd67..090bb5a 100644 --- a/Almanac/Fish/FishInfo.cs +++ b/Almanac/Fish/FishInfo.cs @@ -23,9 +23,9 @@ int End [Flags] public enum WaterType { - None = 0, - Freshwater = 1, - Ocean = 2 + None, + Freshwater, + Ocean }; public enum FishWeather { diff --git a/Almanac/Fish/FishManager.cs b/Almanac/Fish/FishManager.cs index 4d21b35..8c88f3f 100644 --- a/Almanac/Fish/FishManager.cs +++ b/Almanac/Fish/FishManager.cs @@ -189,66 +189,63 @@ public void LoadOverrides() { #region Locations private static void AddFish(SubLocation loc, int[] seasons, string fish, Dictionary>> result ) { - if ( ! result.TryGetValue(fish, out var entry)) { + if (!result.TryGetValue(fish, out var entry)) { result[fish] = new() { [loc] = seasons.ToList(), }; - - return; - } - - if (!entry.TryGetValue(loc, out var slist)) { - entry[loc] = seasons.ToList(); - return; - } - - foreach(int season in seasons) { - if (!slist.Contains(season)) - slist.Add(season); + return; + } + if (!entry.TryGetValue(loc, out var slist)) { + entry[loc] = seasons.ToList(); + return; + } + foreach(int season in seasons) { + if (!slist.Contains(season)) + slist.Add(season); + } } - } private static void RemoveFish(SubLocation loc, int[] seasons, string fish, Dictionary>> result) { if (!result.TryGetValue(fish, out var entry)) return; - if (!entry.TryGetValue(loc, out var slist)) - return; + if (!entry.TryGetValue(loc, out var slist)) + return; - for (int i = slist.Count - 1; i >= 0; i--) { - if (seasons.Contains(slist[i])) - slist.RemoveAt(i); - } + for (int i = slist.Count - 1; i >= 0; i--) { + if (seasons.Contains(slist[i])) + slist.RemoveAt(i); + } - if (slist.Count > 0) - return; + if (slist.Count > 0) + return; - entry.Remove(loc); + entry.Remove(loc); - if (entry.Count > 0) - return; + if (entry.Count > 0) + return; - result.Remove(fish); - } + result.Remove(fish); + } public Dictionary>> GetFishLocations() { if (!OverridesLoaded) LoadOverrides(); - var result = FishHelper.GetFishLocations(); + var result = FishHelper.GetFishLocations(); - WithOverrides(() => { - foreach(var ovr in Overrides.SelectMany(x => x.Value)) { - if (string.IsNullOrEmpty(ovr.Map)) - continue; + WithOverrides(() => { + foreach(var ovr in Overrides.SelectMany(x => x.Value)) { + if (string.IsNullOrEmpty(ovr.Map)) + continue; - int[] seasons; - if (ovr.Season == Common.Enums.Season.All) - seasons = new int[] { 0, 1, 2, 3 }; - else - seasons = new int[] { (int) ovr.Season }; + int[] seasons; + if (ovr.Season == Common.Enums.Season.All) + seasons = new int[] { 0, 1, 2, 3 }; + else + seasons = new int[] { (int) ovr.Season }; - SubLocation sl = new(ovr.Map, ovr.Zone); + SubLocation sl = new(ovr.Map, ovr.Zone); if (ovr.AddFish != null) foreach(string fish in ovr.AddFish) { @@ -259,12 +256,10 @@ public Dictionary>> GetFishLocations() foreach(string fish in ovr.RemoveFish) { RemoveFish(sl, seasons, fish, result); } - - } - }); - - return result; - } + } + }); + return result; + } #endregion @@ -289,7 +284,7 @@ public IReadOnlyCollection GetFish(bool filter = true) { public List GetSeasonFish(int season, bool filter = true) { if (!Loaded) RefreshFish(); - + ModEntry.Instance.Log("Fish Loaded."); var overrides = filter ? Game1.content.Load>(AssetManager.FishOverridesPath) : null; diff --git a/Almanac/Fish/FishingPage.cs b/Almanac/Fish/FishingPage.cs index 5817b97..c6d5f75 100644 --- a/Almanac/Fish/FishingPage.cs +++ b/Almanac/Fish/FishingPage.cs @@ -562,21 +562,21 @@ bool OnHover(IFlowNodeSlice slice, int x, int y) { List> sorted = new(); foreach (var pair in caught.Locations) { - string? subloc = pair.Key.Area == -1 ? null + string? subloc = pair.Key.Area == "No zone" ? null : Mod.GetSubLocationName(pair.Key); string name = Mod.GetLocationName(pair.Key.Key, pair.Key.Location); string key = subloc == null ? name : $"{name} ({subloc})"; bool matches = pair.Value.Contains(Menu.Season); - bool all_seasons = true; + bool all_seasons = pair.Value.Count==4? true:false; - for (int i = 0; i < WorldDate.MonthsPerYear; i++) { + /*for (int i = 0; i < WorldDate.MonthsPerYear; i++) { if (!pair.Value.Contains(i)) { all_seasons = false; break; } - } + }*/ Color? color = matches ? null : Game1.textColor * .5f; Color? shadow = matches ? null : Game1.textShadowColor * .5f; @@ -673,7 +673,7 @@ public override void Update() { var selected = CurrentFish; - var sorted = Mod.Fish.GetSeasonFish(((int)Menu.Date.Season)); + var sorted = Mod.Fish.GetSeasonFish((Menu.Date.SeasonIndex)); sorted.Sort((a, b) => { return a.Name.CompareTo(b.Name); }); @@ -688,12 +688,15 @@ public override void Update() { continue; } - if (FType == FishType.Trap && !fish.TrapInfo.HasValue) + if (FType == FishType.Trap && !fish.TrapInfo.HasValue) { + ModEntry.Instance.Log("Fish has trap info: " + fish.TrapInfo.HasValue); continue; + } - if (FType == FishType.Catch && !fish.CatchInfo.HasValue) + if (FType == FishType.Catch && !fish.CatchInfo.HasValue) { + ModEntry.Instance.Log("Fish has catch info: "+fish.CatchInfo.HasValue); continue; - + } if (CStatus != CaughtStatus.None) { int caught = fish.NumberCaught(Game1.player); if (CStatus == CaughtStatus.Caught && caught == 0) diff --git a/Almanac/Fish/VanillaProvider.cs b/Almanac/Fish/VanillaProvider.cs index 2fc7ffd..7a85cd7 100644 --- a/Almanac/Fish/VanillaProvider.cs +++ b/Almanac/Fish/VanillaProvider.cs @@ -14,6 +14,7 @@ using Leclair.Stardew.Almanac.Models; using StardewValley.ItemTypeDefinitions; +using StardewValley.GameData.Locations; namespace Leclair.Stardew.Almanac.Fish; @@ -30,38 +31,35 @@ public VanillaProvider(ModEntry mod) { public string Name => nameof(VanillaProvider); public int Priority => 0; - public IEnumerable GetFish() { - Dictionary data = Game1.content.Load>(@"Data\Fish"); - List result = new(); - - Dictionary>> locations = Mod.Fish.GetFishLocations(); - + public IEnumerable GetFish() { + Dictionary data = Game1.content.Load>(@"Data\Fish"); + List result = new(); + Dictionary>> locations = Mod.Fish.GetFishLocations(); List pondData = Game1.content.Load>(@"Data\FishPondData"); foreach (var entry in data) { - locations.TryGetValue(entry.Key, out Dictionary>? locs); + locations.TryGetValue("(O)"+entry.Key, out Dictionary>? locs); try { - FishInfo? info = GetFishInfo(entry.Key, entry.Value, locs, pondData); + FishInfo? info = GetFishInfo("(O)"+entry.Key, entry.Value, locs, pondData); if (info.HasValue) result.Add(info.Value); } catch(Exception ex) { ModEntry.Instance.Log($"Unable to process fish: {entry.Key}", LogLevel.Warn, ex); } } - return result; } - private static FishInfo? GetFishInfo(string id, string data, Dictionary> locations, List pondData) { - if (string.IsNullOrEmpty(data)) - return null; + private static FishInfo? GetFishInfo(string id, string data, Dictionary> locations, List pondData) { + if (string.IsNullOrEmpty(data)) + return null; if (FishHelper.SkipFish(Game1.player, id)) return null; - string[] bits = data.Split('/'); - SObject obj = new SObject(id, 1); + string[] bits = data.Split('/'); + SObject obj = new SObject(id.Substring(3), 1); if (bits.Length < 7 || obj is null) return null; @@ -133,10 +131,29 @@ public IEnumerable GetFish() { Minlevel: minLevel ); - if (locations != null) + string[] seasonIds = bits[6].Split(' '); + seasons = new int[seasonIds.Length]; + for(int i = 0;i < seasonIds.Length;i++) { + string seasonId = seasonIds[i]; + switch (seasonId) { + case "spring": + seasons[i] = 0; + break; + case "summer": + seasons[i] = 1; + break; + case "fall": + seasons[i] = 2; + break; + case "winter": + seasons[i] = 3; + break; + } + } + /*if (locations != null) seasons = locations.Values.SelectMany(x => x).Distinct().ToArray(); else - seasons = null; + seasons = null;*/ } string desc = obj.getDescription(); @@ -155,7 +172,6 @@ public IEnumerable GetFish() { break; } } - if (!matched) continue; @@ -170,33 +186,31 @@ public IEnumerable GetFish() { int price = obj.Price; pond.SpawnTime = price > 30 ? (price > 80 ? (price > 120 ? (price > 250 ? 5 : 4) : 3) : 2) : 1; } - int initial = 10; - if (pond.PopulationGates != null) { foreach (int key in pond.PopulationGates.Keys) if (key >= initial) initial = key - 1; } - - pondInfo = new( - Initial: initial, - SpawnTime: pond.SpawnTime, - ProducedItems: pond.ProducedItems.Select(x => x.ItemId).Distinct().Select(x => (x == "812" ? FishHelper.GetRoeForFish(obj) : ItemRegistry.Create(x, 1))).ToList() - ); + pondInfo = new( + Initial: initial, + SpawnTime: pond.SpawnTime, + ProducedItems: pond.ProducedItems.Select(x => x.ItemId).Distinct().Select(x => (x == "812" ? FishHelper.GetRoeForFish(obj) : ItemRegistry.Create(x, 1))).ToList() + ); } - //Hardcoded the ids of Legendary fish in since FishingRod.isFishBossFish no longer exists. - bool legend = false; - - if(id == "(O)159"||id =="(O)160"||id=="(O)163"||id=="(O)775"||id=="(O)902"||id=="(O)900"||id=="(O)682"||id=="(O)899"||id=="(O)901"||id=="(O)898") - legend = true; + bool legend = false; + legend = obj.HasContextTag("fish_legendary"); + SpriteInfo? sprite = null; + //Hardcode in Goby sprite + if(id=="(O)Goby") + sprite = new SpriteInfo(Game1.objectSpriteSheet_2, new Microsoft.Xna.Framework.Rectangle(112, 256, 16, 16)); return new FishInfo( Id: id, // bits[0], Item: obj, Name: obj.DisplayName, Description: desc, - Sprite: SpriteHelper.GetSprite(obj), + Sprite: sprite==null? SpriteHelper.GetSprite(obj): sprite, Legendary: legend, MinSize: minSize, MaxSize: maxSize, diff --git a/Almanac/FishHelper.cs b/Almanac/FishHelper.cs index b5f6717..a489a3a 100644 --- a/Almanac/FishHelper.cs +++ b/Almanac/FishHelper.cs @@ -15,6 +15,10 @@ using Leclair.Stardew.Almanac.Fish; using Leclair.Stardew.Almanac.Models; using StardewModdingAPI.Utilities; +using StardewValley.GameData.LocationContexts; +using StardewValley.GameData.Locations; +using Leclair.Stardew.Common.Enums; +using System.Security.Policy; namespace Leclair.Stardew.Almanac; @@ -33,17 +37,17 @@ public static bool SkipLocation(string key) { return false; } - public static bool SkipFish(Farmer who, string id) { - who ??= Game1.player; + public static bool SkipFish(Farmer who, string id) { + who ??= Game1.player; - switch(id) { - case "898": - case "899": - case "900": - case "901": - case "902": - return ! who.team.SpecialOrderRuleActive("LEGENDARY_FAMILY"); - } + switch(id) { + case "(O)898": + case "(O)899": + case "(O)900": + case "(O)901": + case "(O)902": + return ! who.team.SpecialOrderRuleActive("LEGENDARY_FAMILY"); + } return false; } @@ -101,19 +105,23 @@ public static SObject GetRoeForFish(SObject fish) { if (fish.ParentSheetIndex == 698) color = new Color(61, 55, 42); - ColoredObject result = new ColoredObject("812", 1, color); - result.name = fish.Name + " Roe"; - result.preserve.Value = SObject.PreserveType.Roe; - result.preservedParentSheetIndex.Value = fish.QualifiedItemId; - result.Price += fish.sellToStorePrice() / 2; + ColoredObject result = new ColoredObject("812", 1, color); + result.name = fish.Name + " Roe"; + result.preserve.Value = SObject.PreserveType.Roe; + result.preservedParentSheetIndex.Value = fish.QualifiedItemId; + result.Price += fish.sellToStorePrice() / 2; - return result; - } + return result; + } - public static Dictionary>> GetFishLocations() { - Dictionary>> result = new(); + /// + /// Returns locations a fish can be caught + /// + /// Dictionary>> + public static Dictionary>> GetFishLocations() { + Dictionary>> result = new(); - var locations = Game1.content.Load>(@"Data\Locations"); + var locations = Game1.content.Load>("Data\\Locations"); foreach (var lp in locations) { if (SkipLocation(lp.Key)) @@ -121,18 +129,22 @@ public static Dictionary>> GetFishLoca for (int season = 0; season < WorldDate.MonthsPerYear; season++) { try { - var data = GetLocationFish(lp.Key, season, locations); + //Dictionary> + Dictionary> data = GetLocationFish(lp.Key, season, locations); if (data == null) continue; foreach (var pair in data) { - int zone = pair.Key; + string zone = pair.Key; + //lp.Key = location name SubLocation sl = new(lp.Key, zone); - foreach (string fish in pair.Value) { - if (!result.TryGetValue(fish, out var locs)) - result[fish] = locs = new(); - + foreach (string fish in pair.Value) { + //If result doesn't have this fish ID, add a new Dictionary + if (!result.TryGetValue(fish, out var locs)) + result[fish] = locs = new(); + //If locs has this Sublocation, add the current season to it's List + //Else add a new List for this Sublocation with current season if (locs.TryGetValue(sl, out var seasons)) seasons.Add(season); else @@ -140,7 +152,25 @@ public static Dictionary>> GetFishLoca } } } catch { - ModEntry.Instance.Log($"Uh oh: {lp.Key}"); + Dictionary> data = GetLocationFish(lp.Key, season, locations); + if (data == null) + continue; + + foreach (var pair in data) { + string zone = pair.Key; + SubLocation sl = new(lp.Key, zone); + + foreach (string fish in pair.Value) { + if (!result.TryGetValue(fish, out var locs)) + result[fish] = locs = new(); + + if (locs.TryGetValue(sl, out var seasons)) + seasons.Add(season); + else + locs[sl] = new List() { season }; + } + } + ModEntry.Instance.Log($"Error at FishHelper line 169",LogLevel.Warn); } } } @@ -148,10 +178,10 @@ public static Dictionary>> GetFishLoca return result; } - public static Dictionary> GetFishLocations(int season) { - Dictionary> result = new(); + public static Dictionary> GetFishLocations(int season) { + Dictionary> result = new(); - var locations = Game1.content.Load>(@"Data\Locations"); + var locations = Game1.content.Load>("Data\\Locations"); foreach (var lp in locations) { if (SkipLocation(lp.Key)) continue; @@ -161,46 +191,57 @@ public static Dictionary> GetFishLocations(int season) continue; foreach (var pair in data) { - int zone = pair.Key; + string zone = pair.Key; SubLocation sl = new(lp.Key, zone); - foreach(string fish in pair.Value) { - if (result.TryGetValue(fish, out var subs)) - subs.Add(sl); - else - result.Add(fish, new List() { sl }); - } + foreach(string fish in pair.Value) { + if (result.TryGetValue(fish, out var subs)) + subs.Add(sl); + else + result.Add(fish, new List() { sl }); } } + } return result; } - public static Dictionary> GetLocationFish(GameLocation location, int season) { - return GetLocationFish(location.Name, season); - } - - public static Dictionary> GetLocationFish(string key, int season, Dictionary locations = null) { - if (key == "BeachNightMarket") - key = "Beach"; - - locations ??= Game1.content.Load>("Data\\Locations"); - Dictionary> result = null; + public static Dictionary> GetLocationFish(GameLocation location, int season) { + return GetLocationFish(location.Name, season); + } - if (locations.ContainsKey(key)) - result = GetLocationFish(season, locations[key]); + public static Dictionary> GetLocationFish(string key, int season, Dictionary? locations = null) { + if (key == "BeachNightMarket") + key = "Beach"; + + locations ??= Game1.content.Load>("Data\\Locations"); + Dictionary> result; + GameLocation loc; + if (locations.ContainsKey(key) && ContainsFish(locations[key])) + try { + result = GetLocationFish(season, locations[key]); + } catch { + result = GetLocationFish(season, locations[key]); + ModEntry.Instance.Log($"Error at FishHelper line 225", LogLevel.Warn); + } else result = new(); Farm farm; try { - var loc = Game1.getLocationFromName(key); - if (loc is not Farm) + loc = Game1.getLocationFromName(key); + if (!loc.IsFarm) return result; farm = (Farm) loc; } catch { - return result; + loc = Game1.getLocationFromName(key); + if (loc == null) + return result; + else if (loc is not Farm) + return result; + farm = (Farm) loc; + ModEntry.Instance.Log($"Error at {loc.DisplayName}, farm = loc section", LogLevel.Warn); } // This line will forever live in shame as a stupid mistake. @@ -208,12 +249,12 @@ public static Dictionary> GetLocationFish(string key, int seas // If we've got whichFarm 1 then we want to reset the list, as it never // falls back to the default getFish behavior. - switch(Game1.whichFarm) { + switch (Game1.whichFarm) { case 1: result.Clear(); break; } - + ModEntry.Instance.Log("Calling override..."); string ovr = farm.getMapProperty("FarmFishLocationOverride"); if (!string.IsNullOrEmpty(ovr)) { string[] bits = ovr.Split(' '); @@ -229,70 +270,103 @@ public static Dictionary> GetLocationFish(string key, int seas if (value >= 1) result.Clear(); - GetLocationFish(season, locations[bits[0]], result); - } - } + try { + GetLocationFish(season, locations[bits[0]], result); + } catch { + GetLocationFish(season, locations[bits[0]], result); + ModEntry.Instance.Log($"Error at {getLocName(locations[bits[0]])}, bits section.", LogLevel.Warn); + } - switch(Game1.whichFarm) { - case 1: - GetLocationFish(season, locations?["Forest"], result, true, 1); - GetLocationFish(season, locations?["Town"], result, true, 1); - break; - case 2: - AddFish("734", -1, result); - GetLocationFish(season, locations?["Forest"], result, true, 1); - break; - case 3: - GetLocationFish(season, locations?["Forest"], result, true, 0); - break; - case 4: - GetLocationFish(season, locations?["Mountain"], result, true); - break; - case 5: - GetLocationFish(season, locations?["Forest"], result, true, 1); - break; - case 6: - AddFish("152", -1, result); - AddFish("723", -1, result); - AddFish("393", -1, result); - AddFish("719", -1, result); - AddFish("718", -1, result); - GetLocationFish(season, locations?["Beach"], result, true); - break; } - - return result; - } - - private static void AddFish(string fish, int zone, Dictionary> existing) { - if (existing.TryGetValue(zone, out List result)) { - if (!result.Contains(fish)) - result.Add(fish); - } else - existing.Add(zone, new() { fish }); } - - public static Dictionary> GetLocationFish(int season, string data) { - return GetLocationFish(season, data, new()); + try{ + GetLocationFish(season, locations[Game1.GetFarmTypeKey()], result); + } catch { + GetLocationFish(season, locations[Game1.GetFarmTypeKey()], result); + ModEntry.Instance.Log($"Error at {getLocName(locations[Game1.GetFarmTypeKey()])}, farm key section.", LogLevel.Warn); } - public static Dictionary> GetLocationFish(int season, string data, Dictionary> existing, bool no_zones = false, int limit_zone = -1) { - if (string.IsNullOrEmpty(data)) - return existing; + return result; + } - string[] entries = data.Split('/')[4 + season].Split(' ', StringSplitOptions.RemoveEmptyEntries); + private static void AddFish(string fishName, string fish, string zone, Dictionary> existing) { + if (existing.TryGetValue(zone, out List? result)) { + if (!result.Contains(fish)) { + result.Add(fish); + ModEntry.Instance.Log($"Added fish data entry for {fishName} (ID:{fish}, Zone:{zone})"); + } + } else + existing.Add(zone, new() { fish }); + } - for (int i = 0; (i + 1) < entries.Length; i += 2) { - string fish = entries[i]; - if (int.TryParse(entries[i + 1], out int zone)) { - if (limit_zone != -1 && zone != -1 && limit_zone != zone) - continue; + public static Dictionary> GetLocationFish(int season, LocationData data) { + return GetLocationFish(season, data, new()); + } - AddFish(fish, no_zones ? -1 : zone, existing); - } else - ModEntry.Instance.Log($"Invalid fish data entry for season {season} (Fish ID:{entries[i]}, Zone:{entries[i + 1]})", LogLevel.Warn); + public static Dictionary> GetLocationFish(int season, LocationData data, Dictionary> existing) { + if (data.Equals(null)) + return existing; + + string name = getLocName(data); + List entries = data.Fish; + ModEntry.Instance.Log($"Fish in {name} list: {entries.Count}", LogLevel.Debug); + + if (!data.Equals(Game1.content.Load>("Data\\Locations")["Default"])) { + LocationData Default = Game1.content.Load>("Data\\Locations")["Default"]; + foreach (SpawnFishData f in Default.Fish) + if (!entries.Contains(f)) + entries.Add(f); + ModEntry.Instance.Log($"Fish in {name}+Default list: {entries.Count}"); + } else ModEntry.Instance.Log("This is Default Location Data"); + + entries.Sort((a,b)=> { return a.Precedence.CompareTo(b.Precedence); }); + + for (int i = 0; i < entries.Count; i++) { + string fish = string.IsNullOrEmpty(entries[i].ItemId) ? "(O)213" : entries[i].ItemId; + string fName = string.IsNullOrEmpty(ItemRegistry.Create(fish).DisplayName)? "Fish" : ItemRegistry.Create(fish).DisplayName; + //if(fish=="(O)213") { fName = "Fish Taco"; } + string zone = string.IsNullOrEmpty(entries[i].FishAreaId) ? "No zone" : entries[i].FishAreaId; + if(fish.StartsWith("(O)")) + AddFish(fName, fish, zone, existing); } - + ModEntry.Instance.Log($"Added fish data for {name}, season {season}"); return existing; } + public static bool ContainsFish(LocationData loc) { + List catchables = loc.Fish; + foreach (SpawnFishData f in catchables) if (f.ItemId.StartsWith("(O)")) return true; + return false; + } + private static string getLocName(LocationData data) { + string name = data.DisplayName == null ? "No DisplayName" : data.DisplayName; + string endCheck = name.Substring(name.Length - 7); + switch (endCheck) { + case "Name]]]": + name = "Farm"; + break; + case ".11190]": + name = "Town"; + break; + case ".11174]": + name = "Beach"; + break; + case ".11176]": + name = "Mountain"; + break; + case ".11186]": + name = "Forest"; + break; + case ".11089]": + name = "Sewer"; + break; + case ".11062]": + name = "Desert"; + break; + case ".11114]": + name = "Woods"; + break; + } + if (name.Length > 25) name = endCheck; + return name; + } } diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index 4e9f5bc..3868c39 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -876,7 +876,7 @@ public bool DoesTranslationExist(string key) { } public string GetSubLocationName(Models.SubLocation sub) { - if (sub.Area == -1) + if (sub.Area == "") return I18n.Location_SubAny(); string name = sub.Key; @@ -889,16 +889,16 @@ public string GetSubLocationName(Models.SubLocation sub) { return I18n.Location_SubFloor(sub.Area); case "Forest": - if (sub.Area == 0) + if (sub.Area == "River") return I18n.Location_Forest_River(); - if (sub.Area == 1) + if (sub.Area == "Pond") return I18n.Location_Forest_Pond(); break; case "IslandWest": - if (sub.Area == 1) + if (sub.Area == "Ocean") return I18n.Location_Island_Ocean(); - if (sub.Area == 2) + if (sub.Area == "Freshwater") return I18n.Location_Island_Freshwater(); break; } diff --git a/Almanac/Models/LocationOverride.cs b/Almanac/Models/LocationOverride.cs index 7009a07..6cefcb9 100644 --- a/Almanac/Models/LocationOverride.cs +++ b/Almanac/Models/LocationOverride.cs @@ -6,7 +6,7 @@ namespace Leclair.Stardew.Almanac.Models; public class LocationOverride { public string? Map { get; set; } - public int Zone { get; set; } = -1; + public string Zone { get; set; } = ""; public Season Season { get; set; } = Season.All; public string[]? AddFish { get; set; } = null; diff --git a/Almanac/Models/SubLocation.cs b/Almanac/Models/SubLocation.cs index ab6aeec..f883e94 100644 --- a/Almanac/Models/SubLocation.cs +++ b/Almanac/Models/SubLocation.cs @@ -9,9 +9,9 @@ namespace Leclair.Stardew.Almanac.Models; public struct SubLocation { public string Key { get; } - public int Area { get; } + public string Area { get; } - public SubLocation(string key, int area) { + public SubLocation(string key, string area) { Key = key; Area = area; } From 12aeb53f04b20cd53b7b9d24353e4e7ec0f27ea8 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Mon, 1 Apr 2024 09:08:08 -0400 Subject: [PATCH 26/39] Fix new crop sprites --- Almanac/Crops/VanillaProvider.cs | 41 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/Almanac/Crops/VanillaProvider.cs b/Almanac/Crops/VanillaProvider.cs index c0cbefa..9a4198e 100644 --- a/Almanac/Crops/VanillaProvider.cs +++ b/Almanac/Crops/VanillaProvider.cs @@ -48,7 +48,7 @@ public IEnumerable GetCrops() { private CropInfo? GetCropInfo(string id, CropData data) { // TODO: Create Crop instances so we can // lean more on vanilla logic. - int sprite = data.SpriteIndex; + int spriteRow = data.SpriteIndex; string harvestID = data.HarvestItemId; int regrow = data.RegrowDays; bool isTrellisCrop = data.IsRaised; @@ -87,7 +87,7 @@ public IEnumerable GetCrops() { // If the sprite is 23, it's a seasonal multi-seed // so we want to show that rather than the seed. Item item; - if (sprite == 23) + if (spriteRow == 23) item = ItemRegistry.Create(id, 1); else item = ItemRegistry.Create(harvestID, 1); @@ -114,13 +114,13 @@ public IEnumerable GetCrops() { sprites[i] = new( Game1.cropSpriteSheet, new Rectangle( - Math.Min(240, (i + 1) * 16 + (sprite % 2 != 0 ? 128 : 0)), - sprite / 2 * 16 * 2, + Math.Min(240, (i + 1) * 16 + (spriteRow % 2 != 0 ? 128 : 0)), + spriteRow / 2 * 16 * 2, 16, 32 ), overlaySource: final && color.HasValue ? new Rectangle( - Math.Min(240, (i + 2) * 16 + (sprite % 2 != 0 ? 128 : 0)), - sprite / 2 * 16 * 2, + Math.Min(240, (i + 2) * 16 + (spriteRow % 2 != 0 ? 128 : 0)), + spriteRow / 2 * 16 * 2, 16, 32 ) : null, overlayColor: final ? color : null @@ -141,11 +141,38 @@ public IEnumerable GetCrops() { ) ); } + SpriteInfo sprite = null; + int offset; + switch (harvestID) { + case "Carrot": { + offset = 0; + break; + } + case "SummerSquash": { + offset = 1; + break; + } + case "Broccoli": { + offset = 2; + break; + } + case "Powdermelon": { + offset = 3; + break; + } + default: { + offset = -1; + break; + } + } + if (offset!=-1) { + sprite = new SpriteInfo(Game1.objectSpriteSheet_2, new Rectangle(offset * 16, 160, 16, 16)); + } return new( id, item, item.DisplayName, - SpriteHelper.GetSprite(item), + sprite==null? SpriteHelper.GetSprite(item): sprite, isTrellisCrop, isGiantCrop, giantSprite, From 5bb2401e6203f57d3ac029be96aa525f430fd484 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:03:17 -0400 Subject: [PATCH 27/39] Revert SeeMeRollin --- SeeMeRollin/ModConfig.cs | 2 +- SeeMeRollin/Patches/BuffPatches.cs | 12 ++++++++++++ SeeMeRollin/RollinBuff.cs | 13 +++++-------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/SeeMeRollin/ModConfig.cs b/SeeMeRollin/ModConfig.cs index e37f453..f178966 100644 --- a/SeeMeRollin/ModConfig.cs +++ b/SeeMeRollin/ModConfig.cs @@ -5,7 +5,7 @@ public class ModConfig { public KeybindList UseKey { get; set; } = KeybindList.Parse("Space, LeftStick"); - //public bool ShowBuff { get; set; } = true; + public bool ShowBuff { get; set; } = true; public int SpeedModifier { get; set; } = 6; diff --git a/SeeMeRollin/Patches/BuffPatches.cs b/SeeMeRollin/Patches/BuffPatches.cs index 84897c1..1b584ad 100644 --- a/SeeMeRollin/Patches/BuffPatches.cs +++ b/SeeMeRollin/Patches/BuffPatches.cs @@ -38,5 +38,17 @@ static bool Prefix(Buff __instance, ref string __result) { } } + [HarmonyPatch(typeof(Buff), nameof(Buff.getClickableComponents))] + public static class Buff_Components { + static bool Prefix(Buff __instance, ref List __result) { + if (__instance is RollinBuff && !ModEntry.instance.Config.ShowBuff) { + __result = new(); + return false; + } + + return true; + } + } + } } diff --git a/SeeMeRollin/RollinBuff.cs b/SeeMeRollin/RollinBuff.cs index 4dbcd54..0857c42 100644 --- a/SeeMeRollin/RollinBuff.cs +++ b/SeeMeRollin/RollinBuff.cs @@ -6,15 +6,12 @@ public class RollinBuff : Buff { public int Speed; - public RollinBuff(int speed) : base( - ModEntry.BUFF, - duration: 1, - icon_texture: Game1.buffsIcons, - icon_sheet_index: 9, - display_name: I18n.Buff_Name() - ) { - effects.speed.Value = speed; + public RollinBuff(int speed) : base(I18n.Buff_Name(), 1000, null, 9) { + which = ModEntry.BUFF; + Speed = speed; + buffAttributes[Buff.speed] = Speed; } + } } From 617a49add40ee083a97d11aa751cc8d6f5fc9fff Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Mon, 1 Apr 2024 09:59:04 -0400 Subject: [PATCH 28/39] Revert SeeMeRollin --- SeeMeRollin/Patches/BuffPatches.cs | 11 +++++++++++ SeeMeRollin/RollinBuff.cs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SeeMeRollin/Patches/BuffPatches.cs b/SeeMeRollin/Patches/BuffPatches.cs index 1b584ad..2624676 100644 --- a/SeeMeRollin/Patches/BuffPatches.cs +++ b/SeeMeRollin/Patches/BuffPatches.cs @@ -37,6 +37,17 @@ static bool Prefix(Buff __instance, ref string __result) { return true; } } + [HarmonyPatch(typeof(Buff), nameof(Buff.getClickableComponents))] + public static class Buff_Components { + static bool Prefix(Buff __instance, ref List __result) { + if (__instance is RollinBuff&&!ModEntry.instance.Config.ShowBuff) { + __result = new(); + return false; + } + + return true; + } + } [HarmonyPatch(typeof(Buff), nameof(Buff.getClickableComponents))] public static class Buff_Components { diff --git a/SeeMeRollin/RollinBuff.cs b/SeeMeRollin/RollinBuff.cs index 0857c42..b11a85b 100644 --- a/SeeMeRollin/RollinBuff.cs +++ b/SeeMeRollin/RollinBuff.cs @@ -7,7 +7,7 @@ public class RollinBuff : Buff { public int Speed; public RollinBuff(int speed) : base(I18n.Buff_Name(), 1000, null, 9) { - which = ModEntry.BUFF; + which - ModEntry.BUFF; Speed = speed; buffAttributes[Buff.speed] = Speed; From 9f5dfc093c7291a6e06f44a958ea77afec39411c Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:16:01 -0400 Subject: [PATCH 29/39] Add Bookseller and passive festivals --- Almanac/Managers/NoticesManager.cs | 159 +++++++++++++++--- Almanac/ModConfig.cs | 1 + Almanac/ModEntry.cs | 11 ++ Almanac/i18n/default.json | 10 +- Common/SpriteHelper.cs | 249 ++++++++++++++++++++++++++--- 5 files changed, 387 insertions(+), 43 deletions(-) diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index 2fc9a0d..3155b33 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -24,6 +24,7 @@ using StardewValley.GameData.Movies; using StardewValley.GameData.Shops; using StardewValley.Internal; +using StardewValley.GameData; namespace Leclair.Stardew.Almanac.Managers; @@ -361,23 +362,23 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { if (gathering && bush == null) bush = new(); - if (gathering && IsBlooming(date.Season, date.DayOfMonth)) { - Item? berry = null; - if (date.SeasonIndex == 0) - berry = ItemRegistry.Create("(O)296", 1); // Salmonberry + if (gathering && IsBlooming(bush!, date.Season, date.DayOfMonth)) { + Item? berry = null; + if (date.SeasonIndex == 0) + berry = ItemRegistry.Create("(O)296", 1); // Salmonberry - else if (date.SeasonIndex == 2) - berry = ItemRegistry.Create("(O)410", 1); // Blackberry + else if (date.SeasonIndex == 2) + berry = ItemRegistry.Create("(O)410", 1); // Blackberry if (berry != null) { - bool first_day = date.DayOfMonth == 1 || !IsBlooming(date.Season, date.DayOfMonth - 1); + bool first_day = date.DayOfMonth == 1 || !IsBlooming(bush!, date.Season, date.DayOfMonth - 1); int last = date.DayOfMonth; // If it's the first day, then we also need the last day // so we can display a nice string to the user. if (first_day) for (int d = date.DayOfMonth + 1; d <= ModEntry.DaysPerMonth; d++) { - if (IsBlooming(date.Season, d)) + if (IsBlooming(bush!, date.Season, d)) last = d; else break; @@ -451,6 +452,48 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { } } + if (Mod.Config.NoticesShowFestivals && Utility.TryGetPassiveFestivalDataForDay(date.DayOfMonth, date.Season, null, out string id, out PassiveFestivalData passFestData)) { + string displayDate = Utility.getDateStringFor(date.DayOfMonth, date.SeasonIndex, date.Year); + if (passFestData.StartDay <= date.DayOfMonth && passFestData.EndDay >= date.DayOfMonth) { + string festName = id; + try { + Dictionary content = Game1.content.Load>("Strings\\1_6_Strings"); + if (content.ContainsKey(id)) + festName = content[id]; + //These festivals aren't in 1_6_Strings + switch (id) { + case "NightMarket": { + festName = "Night Market"; + break; + } + case "DesertFestival": { + festName = "Desert Festival"; + break; + } + } + } catch { + ModEntry.Instance.Log("Cannot get festival name.", LogLevel.Warn); + } + yield return new RichEvent( + null, + passFestData.StartDay == date.DayOfMonth? FlowHelper.Translate( + Mod.Helper.Translation.Get("page.notices.passive-festival"), + new { + name = festName, + startTime = Mod.FormatTime(passFestData.StartTime), + startDay = Utility.getDateStringFor(passFestData.StartDay, Utility.getSeasonNumber(Utility.getSeasonKey(passFestData.Season)), 1), + endDay = Utility.getDateStringFor(passFestData.EndDay, Utility.getSeasonNumber(Utility.getSeasonKey(passFestData.Season)), 1) + }, + align: Alignment.VCenter + ) : null, + new SpriteInfo( + Game1.mouseCursors, + new Rectangle(346, 392, 8, 8) + ) + ); + } else ModEntry.Instance.Log("Adding festival failed.", LogLevel.Warn); + } + // Weddings / Anniversaries / Children foreach (var who in Game1.getAllFarmers()) { // Children @@ -599,24 +642,13 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { // Winter else if (date.SeasonIndex == 3) { - // Night Market - if (date.DayOfMonth >= 15 && date.DayOfMonth <= 17) { - yield return new RichEvent( - date.DayOfMonth == 15 ? - I18n.Page_Notices_Market() : null, - null, - new SpriteInfo( - Game1.mouseCursors, - new Rectangle(346, 392, 8, 8) - ) - ); - } } // Traveling Merchant if (Mod.Config.NoticesShowMerchant != MerchantMode.Disabled && date.DayOfMonth % 7 % 5 == 0) { + var sprite = new SpriteInfo( Game1.mouseCursors, new Rectangle(193, 1412, 18, 18) @@ -659,6 +691,58 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { } + yield return new RichEvent( + null, + builder.Build(), + sprite: sprite + ); + } + } + } + //Bookseller + if (Mod.Config.NoticesShowBookseller != MerchantMode.Disabled && IsBooksellerVisiting(date)) { + var sprite = new SpriteInfo( + Game1.mouseCursors_1_6, + new Rectangle(177, 488, 18, 24) + ); + + if (Mod.Config.NoticesShowBookseller == MerchantMode.Visit) + yield return new RichEvent( + null, + FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Bookseller(), align: Alignment.VCenter) + .Build(), + sprite + ); + + else { + var stock = ShopBuilder.GetShopStock(Game1.shop_bookseller); + if (stock.Count > 0) { + var builder = FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Bookseller_Stock(), align: Alignment.VCenter) + .Text("\n "); + + bool first = true; + + foreach (var pair in stock) { + var item = pair.Key; + if (item.Stack < 1 && !item.IsInfiniteStock()) + continue; + + if (first) + first = false; + else + builder.Text(", ", shadow: false); + + if (item is SObject sobj) + builder + .Sprite(SpriteHelper.GetSprite(sobj,Game1.objectSpriteSheet_2), scale: 2, align: Alignment.VCenter) + .Text(" "); + + builder.Text(item.DisplayName, shadow: false); + } + + yield return new RichEvent( null, builder.Build(), @@ -668,8 +752,11 @@ public IEnumerable GetVanillaEventsForDate(WorldDate date) { } } } + #endregion + + #region Utility methods //Copied IsBloom from Bush.cs and switched calls for current season & day to passed values - public bool IsBlooming(StardewValley.Season season, int dayOfMonth) { + public bool IsBlooming(Bush bush, StardewValley.Season season, int dayOfMonth) { if (bush.size.Value == 4) { return bush.tileSheetOffset.Value == 1; } @@ -697,7 +784,35 @@ public bool IsBlooming(StardewValley.Season season, int dayOfMonth) { return false; } } - + //Copied from Utility.cs and switched calls for current date info to passed value + public static List GetDaysOfBooksellerThisSeason(WorldDate date) { + Random r = Utility.CreateRandom(date.Year * 11, Game1.uniqueIDForThisGame, date.SeasonIndex); + int[]? possible_days = null; + List days = new List(); + switch (date.Season) { + case StardewValley.Season.Spring: + possible_days = new int[5] { 11, 12, 21, 22, 25 }; + break; + case StardewValley.Season.Summer: + possible_days = new int[5] { 9, 12, 18, 25, 27 }; + break; + case StardewValley.Season.Fall: + possible_days = new int[8] { 4, 7, 8, 9, 12, 19, 22, 25 }; + break; + case StardewValley.Season.Winter: + possible_days = new int[6] { 5, 11, 12, 19, 22, 24 }; + break; + } + int index1 = r.Next(possible_days!.Length); + days.Add(possible_days[index1]); + days.Add(possible_days[(index1 + possible_days.Length / 2) % possible_days.Length]); + return days; + } + public static bool IsBooksellerVisiting(WorldDate date) { + List visitDays = GetDaysOfBooksellerThisSeason(date); + foreach(int day in visitDays) if(day==date.DayOfMonth) return true; + return false; + } #endregion } diff --git a/Almanac/ModConfig.cs b/Almanac/ModConfig.cs index e047de2..1bf7e18 100644 --- a/Almanac/ModConfig.cs +++ b/Almanac/ModConfig.cs @@ -118,6 +118,7 @@ public class ModConfig { public bool NoticesShowFestivals { get; set; } = true; public bool NoticesShowGathering { get; set; } = true; public MerchantMode NoticesShowMerchant { get; set; } = MerchantMode.Visit; + public MerchantMode NoticesShowBookseller { get; set; } = MerchantMode.Visit; public bool NoticesShowTrains { get; set; } = false; } diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index 3868c39..c2ab690 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -793,6 +793,17 @@ private void RegisterConfig() { [MerchantMode.Stock] = I18n.Settings_Notices_Merchant_Stock } ) + .AddChoice( + I18n.Settings_Notices_Bookseller, + I18n.Settings_Notices_BooksellerDesc, + c => c.NoticesShowBookseller, + (c, v) => c.NoticesShowBookseller = v, + new Dictionary> { + [MerchantMode.Disabled] = I18n.Settings_Notices_Bookseller_Disabled, + [MerchantMode.Visit] = I18n.Settings_Notices_Bookseller_Visit, + [MerchantMode.Stock] = I18n.Settings_Notices_Bookseller_Stock + } + ) .Add( I18n.Settings_Notices_Trains, I18n.Settings_Notices_TrainsDesc, diff --git a/Almanac/i18n/default.json b/Almanac/i18n/default.json index 312b9a2..8887ac0 100644 --- a/Almanac/i18n/default.json +++ b/Almanac/i18n/default.json @@ -112,7 +112,7 @@ "page.notices.season": "{{item}} season goes from {{start}} through {{end}}.", "page.notices.summer": "The summer ocean currents are expected to hit Pelican Beach with extra debris from the 12th to the 14th.", - "page.notices.market": "The Night Market is open late from the 15th to the 17th.", + "page.notices.passive-festival": "The {{name}} is open starting at {{startTime}} on the {{startDay}} to the {{endDay}}.", "page.notices.anniversary.no-s": "It's {{name}} and {{spouse}}' anniversary!", "page.notices.anniversary.s": "It's {{name}} and {{spouse}}'s anniversary!", @@ -127,6 +127,8 @@ "page.notices.merchant": "The Traveling Merchant will be in town.", "page.notices.merchant.stock": "The Traveling Merchant will be selling:", + "page.notices.bookseller": "The Bookseller will be in town.", + "page.notices.bookseller.stock": "The Bookseller will be selling:", "page.notices.train": "A train is scheduled to pass the station at {{time}}.", @@ -284,6 +286,12 @@ "settings.notices.merchant.Visit": "Show Visits Only", "settings.notices.merchant.Stock": "Show Visits and Stock", + "settings.notices.bookseller": "Show Bookseller", + "settings.notices.bookseller-desc": "When enabled, visits from the Bookseller will be listed in Local Notices. If you wish, we can show the items they'll have as well.", + "settings.notices.bookseller.Disabled": "Disabled", + "settings.notices.bookseller.Visit": "Show Visits Only", + "settings.notices.bookseller.Stock": "Show Visits and Stock", + "settings.restore-state": "Restore Previous State", "settings.restore-state.desc": "When enabled, the Almanac will remember what state it was in when you close and re-open it.", diff --git a/Common/SpriteHelper.cs b/Common/SpriteHelper.cs index 96220e1..eb7ddeb 100644 --- a/Common/SpriteHelper.cs +++ b/Common/SpriteHelper.cs @@ -312,18 +312,15 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch return null;*/ } - public static Rectangle? GetSourceRectangle(Item? item) { + public static Rectangle GetSourceRectangle(Item? item) { if (item is null) - return null; - - // TODO: Not half-ass this - var data = ItemRegistry.GetData(item.QualifiedItemId); - if (data is null) - return null; - - return data.GetSourceRect(); + return new Rectangle(0, 0, 0, 0); + else return GetSourceRectangle(item, GetTexture(item)); + } - /* + public static Rectangle GetSourceRectangle(Item? item, Texture2D texture) { + if (item is null) + return new Rectangle(0, 0, 0, 0); int tileSize = SObject.spriteSheetTileSize; Type type = item.GetType(); @@ -346,7 +343,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch else if (gate && drawSum == 1500) source = new Rectangle(112, 512, 16, 64); else - source = Game1.getArbitrarySourceRect(fence.fenceTexture.Value, 64, 128, tile); + source = Game1.getArbitrarySourceRect(texture, 64, 128, tile); return source; } @@ -360,21 +357,21 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch if (obj.bigCraftable.Value) return SObject.getSourceRectForBigCraftable(obj.ParentSheetIndex); - return Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, obj.ParentSheetIndex, tileSize, tileSize); + return Game1.getSourceRectForStandardTileSheet(texture, obj.ParentSheetIndex, tileSize, tileSize); } // Weapons if (item is MeleeWeapon weapon) - return Game1.getSquareSourceRectForNonStandardTileSheet(Tool.weaponsTexture, 16, 16, weapon.IndexOfMenuItemView); + return Game1.getSquareSourceRectForNonStandardTileSheet(texture, 16, 16, weapon.IndexOfMenuItemView); // Tools if (item is Tool tool) - return Game1.getSquareSourceRectForNonStandardTileSheet(Game1.toolSpriteSheet, 16, 16, tool.IndexOfMenuItemView); + return Game1.getSquareSourceRectForNonStandardTileSheet(texture, 16, 16, tool.IndexOfMenuItemView); // Combined Ring if (item is CombinedRing ring) { // CombinedRings are crazy. - return null; + return new Rectangle(0, 0, 0, 0); } // Boots and Rings @@ -385,7 +382,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch else idx = ((Ring) item).ParentSheetIndex; - return Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, idx, tileSize, tileSize); + return Game1.getSourceRectForStandardTileSheet(texture, idx, tileSize, tileSize); } // Clothing @@ -425,7 +422,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch } // Unknown - return null;*/ + return new Rectangle(0, 0, 0, 0); } public static SpriteInfo? GetSprite(Item? item) { @@ -521,7 +518,7 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch if (moddata != null) { try { texture = Game1.content.Load(moddata.Name); - } catch (Exception) { /* no-op */ } + } catch (Exception) { *//* no-op *//* } } if (texture == null) @@ -595,6 +592,100 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch ); } + // Boots and Rings + if (item is Boots || item is Ring) { + int idx; + if (item is Boots boots) + idx = boots.ParentSheetIndex; + else + idx = ((Ring) item).ParentSheetIndex; + + return new SpriteInfo( + Game1.objectSpriteSheet, + Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, idx, tileSize, tileSize) + ); + } + + // Clothing + if (item is Clothing clothing) { + int idx; + + switch (clothing.clothesType.Value) { + case Clothing.ClothesType.SHIRT: + idx = clothing.ParentSheetIndex; + Rectangle source = new( + idx * 8 % 128, + idx * 8 / 128 * 32, + 8, 8 + ); + + return new SpriteInfo( + FarmerRenderer.shirtsTexture, + source, + overlaySource: new Rectangle( + source.X + 128, source.Y, + source.Width, source.Height + ), + overlayColor: clothing.clothesColor.Value, + isPrismatic: clothing.isPrismatic.Value + ); + + case Clothing.ClothesType.PANTS: + int tiles = FarmerRenderer.pantsTexture.Width / 192; + idx = clothing.ParentSheetIndex; + + return new SpriteInfo( + FarmerRenderer.pantsTexture, + new Rectangle( + 192 * (idx % tiles), + 688 * (idx / tiles) + 672, + 16, 16 + ), + baseColor: clothing.clothesColor.Value, + isPrismatic: clothing.isPrismatic.Value + ); + } + } + + // Hat + if (item is Hat hat) { + int idx = hat.ParentSheetIndex; + + return new SpriteInfo( + FarmerRenderer.hatsTexture, + new Rectangle( + idx * 20 % FarmerRenderer.hatsTexture.Width, + idx * 20 / FarmerRenderer.hatsTexture.Width * 20 * 4, + 20, 20 + ) + ); + } + + // Unknown + return null; */ + } + + public static SpriteInfo? GetSprite(Item? item, Texture2D texture) { + if (item is null) + return null; + + Type type = item.GetType(); + string ts = type.ToString(); + + // DynamicGameAssets Compatibility + if (ts.Equals("DynamicGameAssets.Game.CustomObject")) { + SpriteInfo? sprite = GetDGACustomObjectSprite(item); + if (sprite is not null) + return sprite; + } + + // RaisedGardenBed Compatibility + if (ts.Equals("RaisedGardenBeds.OutdoorPot")) { + SpriteInfo? sprite = GetGardenPotSprite(item); + if (sprite is not null) + return sprite; + } + // Combined Ring if (item is CombinedRing ring) { // CombinedRings are crazy. @@ -602,6 +693,126 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch return new CombinedRingSpriteInfo(ring); } + return new SpriteInfo(texture, GetSourceRectangle(item, texture)); + + /*// Furniture + if (item is Furniture furniture) { + Texture2D texture = ItemRegistry.GetData(furniture.QualifiedItemId).GetTexture(); + + if (ts.Equals("CustomFurniture.CustomFurniture")) { + // TODO: More advanced furniture with layers. + if (Helper != null) + texture = Helper.Reflection.GetField(item, "texture", required: false)?.GetValue() ?? texture; + } + + return new SpriteInfo( + texture, + furniture.defaultSourceRect.Value + ); + } + + // Fence + if (item is Fence fence) { + Rectangle source; + int drawSum = fence.getDrawSum(); + int tile = Fence.fenceDrawGuide[drawSum]; + bool gate = fence.isGate.Value; + + if (gate && drawSum == 110) + source = new Rectangle(0, 512, 88, 24); + else if (gate && drawSum == 1500) + source = new Rectangle(112, 512, 16, 64); + else + source = Game1.getArbitrarySourceRect(fence.fenceTexture.Value, 64, 128, tile); + + return new SpriteInfo( + fence.fenceTexture.Value, + source + ); + } + + // Wallpaper + if (item is Wallpaper wallpaper) { + // Logic taken from the Wallpaper drawInMenu method. + var moddata = wallpaper.modData; + Texture2D? texture = null; + if (moddata != null) { + try { + texture = Game1.content.Load(moddata.Name); + } catch (Exception) { *//* no-op *//* } + } + + if (texture == null) + texture = Game1.content.Load("Maps\\walls_and_floors"); + + Rectangle source; + + if (Helper == null) + source = Rectangle.Empty; + else if (wallpaper.isFloor.Value) + source = Helper.Reflection.GetField(typeof(Wallpaper), "floorContainerRect").GetValue(); + else + source = Helper.Reflection.GetField(typeof(Wallpaper), "wallpaperContainerRect").GetValue(); + + return new SpriteInfo( + texture: Game1.mouseCursors2, + baseSource: source, + overlayTexture: texture, + overlaySource: wallpaper.sourceRect.Value, + overlayScale: 0.5f + ); + } + + // Colored Object + if (item is ColoredObject colored) { + int idx = colored.ParentSheetIndex; + Color color = colored.color.Value; + + if (colored.bigCraftable.Value) + return new SpriteInfo( + texture: Game1.bigCraftableSpriteSheet, + baseSource: SObject.getSourceRectForBigCraftable(idx), + overlaySource: SObject.getSourceRectForBigCraftable(idx + 1), + overlayColor: color + ); + + int offset = colored.ColorSameIndexAsParentSheetIndex ? 0 : 1; + + return new SpriteInfo( + texture: Game1.objectSpriteSheet, + baseSource: Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, idx, tileSize, tileSize), + overlaySource: Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, idx + offset, tileSize, tileSize), + overlayColor: color + ); + } + + // Objects + if (item is SObject obj) { + if (obj.bigCraftable.Value) + return new SpriteInfo(Game1.bigCraftableSpriteSheet, SObject.getSourceRectForBigCraftable(obj.ParentSheetIndex)); + + return new SpriteInfo( + Game1.objectSpriteSheet, + Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, obj.ParentSheetIndex, tileSize, tileSize) + ); + } + + // Weapons + if (item is MeleeWeapon weapon) { + return new SpriteInfo( + Tool.weaponsTexture, + Game1.getSquareSourceRectForNonStandardTileSheet(Tool.weaponsTexture, 16, 16, weapon.IndexOfMenuItemView) + ); + } + + // Tools + if (item is Tool tool) { + return new SpriteInfo( + Game1.toolSpriteSheet, + Game1.getSquareSourceRectForNonStandardTileSheet(Game1.toolSpriteSheet, 16, 16, tool.IndexOfMenuItemView) + ); + } + // Boots and Rings if (item is Boots || item is Ring) { int idx; @@ -674,6 +885,4 @@ internal static void DrawBounded(this ClickableTextureComponent cmp, SpriteBatch // Unknown return null;*/ } - - } From 5d1d67a782845506110cd8b97353f61be74933cc Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:30:19 -0400 Subject: [PATCH 30/39] Fix All Seasons bug Fixed a bug where fish were displaying as catchable in all seasons on their details page, despite being only available in certain seasons. --- Almanac/Fish/FishingPage.cs | 6 ++-- Almanac/FishHelper.cs | 60 +++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/Almanac/Fish/FishingPage.cs b/Almanac/Fish/FishingPage.cs index c6d5f75..22b2d20 100644 --- a/Almanac/Fish/FishingPage.cs +++ b/Almanac/Fish/FishingPage.cs @@ -569,14 +569,14 @@ bool OnHover(IFlowNodeSlice slice, int x, int y) { string key = subloc == null ? name : $"{name} ({subloc})"; bool matches = pair.Value.Contains(Menu.Season); - bool all_seasons = pair.Value.Count==4? true:false; + bool all_seasons = true; - /*for (int i = 0; i < WorldDate.MonthsPerYear; i++) { + for (int i = 0; i < WorldDate.MonthsPerYear; i++) { if (!pair.Value.Contains(i)) { all_seasons = false; break; } - }*/ + } Color? color = matches ? null : Game1.textColor * .5f; Color? shadow = matches ? null : Game1.textShadowColor * .5f; diff --git a/Almanac/FishHelper.cs b/Almanac/FishHelper.cs index a489a3a..89546f7 100644 --- a/Almanac/FishHelper.cs +++ b/Almanac/FishHelper.cs @@ -119,8 +119,9 @@ public static SObject GetRoeForFish(SObject fish) { /// /// Dictionary>> public static Dictionary>> GetFishLocations() { + //Dictionary Dictionary>> result = new(); - + //Dictionary var locations = Game1.content.Load>("Data\\Locations"); foreach (var lp in locations) { @@ -152,25 +153,7 @@ public static Dictionary>> GetFishLoca } } } catch { - Dictionary> data = GetLocationFish(lp.Key, season, locations); - if (data == null) - continue; - - foreach (var pair in data) { - string zone = pair.Key; - SubLocation sl = new(lp.Key, zone); - - foreach (string fish in pair.Value) { - if (!result.TryGetValue(fish, out var locs)) - result[fish] = locs = new(); - - if (locs.TryGetValue(sl, out var seasons)) - seasons.Add(season); - else - locs[sl] = new List() { season }; - } - } - ModEntry.Instance.Log($"Error at FishHelper line 169",LogLevel.Warn); + ModEntry.Instance.Log($"Error at FishHelper line 156",LogLevel.Warn); } } } @@ -221,8 +204,8 @@ public static Dictionary> GetLocationFish(string key, int s try { result = GetLocationFish(season, locations[key]); } catch { - result = GetLocationFish(season, locations[key]); - ModEntry.Instance.Log($"Error at FishHelper line 225", LogLevel.Warn); + result = new(); + ModEntry.Instance.Log($"Error at FishHelper line 208", LogLevel.Warn); } else result = new(); @@ -273,7 +256,6 @@ public static Dictionary> GetLocationFish(string key, int s try { GetLocationFish(season, locations[bits[0]], result); } catch { - GetLocationFish(season, locations[bits[0]], result); ModEntry.Instance.Log($"Error at {getLocName(locations[bits[0]])}, bits section.", LogLevel.Warn); } @@ -282,7 +264,6 @@ public static Dictionary> GetLocationFish(string key, int s try{ GetLocationFish(season, locations[Game1.GetFarmTypeKey()], result); } catch { - GetLocationFish(season, locations[Game1.GetFarmTypeKey()], result); ModEntry.Instance.Log($"Error at {getLocName(locations[Game1.GetFarmTypeKey()])}, farm key section.", LogLevel.Warn); } @@ -309,15 +290,12 @@ public static Dictionary> GetLocationFish(int season, Locat string name = getLocName(data); List entries = data.Fish; - ModEntry.Instance.Log($"Fish in {name} list: {entries.Count}", LogLevel.Debug); - if (!data.Equals(Game1.content.Load>("Data\\Locations")["Default"])) { LocationData Default = Game1.content.Load>("Data\\Locations")["Default"]; foreach (SpawnFishData f in Default.Fish) if (!entries.Contains(f)) entries.Add(f); - ModEntry.Instance.Log($"Fish in {name}+Default list: {entries.Count}"); - } else ModEntry.Instance.Log("This is Default Location Data"); + } entries.Sort((a,b)=> { return a.Precedence.CompareTo(b.Precedence); }); @@ -326,7 +304,7 @@ public static Dictionary> GetLocationFish(int season, Locat string fName = string.IsNullOrEmpty(ItemRegistry.Create(fish).DisplayName)? "Fish" : ItemRegistry.Create(fish).DisplayName; //if(fish=="(O)213") { fName = "Fish Taco"; } string zone = string.IsNullOrEmpty(entries[i].FishAreaId) ? "No zone" : entries[i].FishAreaId; - if(fish.StartsWith("(O)")) + if (fish.StartsWith("(O)") && canAddFish(entries[i], season, data)) AddFish(fName, fish, zone, existing); } ModEntry.Instance.Log($"Added fish data for {name}, season {season}"); @@ -369,4 +347,28 @@ private static string getLocName(LocationData data) { if (name.Length > 25) name = endCheck; return name; } + private static bool canAddFish(SpawnFishData fish, int season, LocationData data) { + if (fish.Season == null && fish.Condition == null) return true; + if (fish.Season != null) { + ModEntry.Instance.Log($"Season int: {season}, Season value {fish.Season}"); + switch (season) { + case 0: if (fish.Season == StardewValley.Season.Spring) return true; break; + case 1: if (fish.Season == StardewValley.Season.Summer) return true; break; + case 2: if (fish.Season == StardewValley.Season.Fall) return true; break; + case 3: if (fish.Season == StardewValley.Season.Winter) return true; break; + } + return false; + } + string[] conditionBits = fish.Condition.Split(' '); + if (conditionBits[0] == "LOCATION_SEASON") + for(int i = 2; i Date: Tue, 5 Nov 2024 20:38:19 +0800 Subject: [PATCH 31/39] Edit the path for my path --- _build/common.targets | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_build/common.targets b/_build/common.targets index 18f0d85..c131382 100644 --- a/_build/common.targets +++ b/_build/common.targets @@ -30,9 +30,9 @@ - - - C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley + + + D:\SteamLibrary\steamapps\common\Stardew Valley C:\ $(GamePath)\$(ModSet) From de9febbd4ecfc32822ef2eacf45f40d134ea9379 Mon Sep 17 00:00:00 2001 From: xysztm Date: Tue, 5 Nov 2024 20:39:37 +0800 Subject: [PATCH 32/39] Make Almanac can be used without the Random features in 1.6.9 --- Almanac/Almanac.csproj | 2 + Almanac/Crops/CropPage.cs | 4 +- Almanac/FishHelper.cs | 29 +++++++++----- Almanac/Managers/LuckManager.cs | 9 +++-- Almanac/Managers/NoticesManager.cs | 3 +- Almanac/Menus/BookCollectionMenu.cs | 4 +- Almanac/ModAPI.cs | 17 ++++---- Almanac/ModEntry.cs | 61 ++++++++++++++++++----------- Almanac/Pages/FortunePage.cs | 4 +- Almanac/Pages/MinesPage.cs | 8 ++-- Almanac/Pages/NoticesPage.cs | 2 + Almanac/Pages/WeatherPage.cs | 4 +- Almanac/Patches/Game1_Patches.cs | 7 ++-- Almanac/i18n/default.json | 16 +++++--- Almanac/i18n/zh.json | 60 ++++++++++++++++++++-------- Almanac/manifest.json | 5 ++- 16 files changed, 153 insertions(+), 82 deletions(-) diff --git a/Almanac/Almanac.csproj b/Almanac/Almanac.csproj index 46c5492..62cfaed 100644 --- a/Almanac/Almanac.csproj +++ b/Almanac/Almanac.csproj @@ -7,6 +7,8 @@ true true + + Crafting;BCInventory;Inventory;GMCM;Mutex;Overlay;UI;SimpleLayout;Flow;SpookyAction;ThemeManager diff --git a/Almanac/Crops/CropPage.cs b/Almanac/Crops/CropPage.cs index 5efa34b..e9edbd7 100644 --- a/Almanac/Crops/CropPage.cs +++ b/Almanac/Crops/CropPage.cs @@ -263,7 +263,7 @@ public CropPage(AlmanacMenu menu, ModEntry mod) : base(menu, mod) { rightNeighborID = ClickableComponent.SNAP_AUTOMATIC }; tabSeedsSprite = Game1.random.Next(2 * AlmanacMenu.TABS.Length); - spriteSeeds = SpriteHelper.GetSprite(InventoryHelper.CreateItemById("(O)495", 1)); + spriteSeeds = SpriteHelper.GetSprite(ItemRegistry.Create("(O)495")); // Cache Agriculturist status. Agriculturist = Game1.player.professions.Contains(Farmer.agriculturist); @@ -350,7 +350,7 @@ public override void Update() { break; } - spriteSeeds = SpriteHelper.GetSprite(InventoryHelper.CreateItemById(seasonSeeds, 1)); + spriteSeeds = SpriteHelper.GetSprite(ItemRegistry.Create(seasonSeeds)); LastDays = new List[ModEntry.DaysPerMonth]; diff --git a/Almanac/FishHelper.cs b/Almanac/FishHelper.cs index 89546f7..044fc2e 100644 --- a/Almanac/FishHelper.cs +++ b/Almanac/FishHelper.cs @@ -122,7 +122,7 @@ public static Dictionary>> GetFishLoca //Dictionary Dictionary>> result = new(); //Dictionary - var locations = Game1.content.Load>("Data\\Locations"); + var locations = Game1.content.Load>(@"Data\Locations"); foreach (var lp in locations) { if (SkipLocation(lp.Key)) @@ -164,7 +164,7 @@ public static Dictionary>> GetFishLoca public static Dictionary> GetFishLocations(int season) { Dictionary> result = new(); - var locations = Game1.content.Load>("Data\\Locations"); + var locations = Game1.content.Load>(@"Data\Locations"); foreach (var lp in locations) { if (SkipLocation(lp.Key)) continue; @@ -197,7 +197,7 @@ public static Dictionary> GetLocationFish(string key, int s if (key == "BeachNightMarket") key = "Beach"; - locations ??= Game1.content.Load>("Data\\Locations"); + locations ??= Game1.content.Load>(@"Data\Locations"); Dictionary> result; GameLocation loc; if (locations.ContainsKey(key) && ContainsFish(locations[key])) @@ -262,7 +262,7 @@ public static Dictionary> GetLocationFish(string key, int s } } try{ - GetLocationFish(season, locations[Game1.GetFarmTypeKey()], result); + GetLocationFish(season, locations[Game1.GetFarmTypeID()], result); } catch { ModEntry.Instance.Log($"Error at {getLocName(locations[Game1.GetFarmTypeKey()])}, farm key section.", LogLevel.Warn); } @@ -288,10 +288,10 @@ public static Dictionary> GetLocationFish(int season, Locat if (data.Equals(null)) return existing; - string name = getLocName(data); + string name = data.DisplayName; List entries = data.Fish; - if (!data.Equals(Game1.content.Load>("Data\\Locations")["Default"])) { - LocationData Default = Game1.content.Load>("Data\\Locations")["Default"]; + if (!data.Equals(DataLoader.Locations(Game1.content)["Default"])) { + LocationData Default = DataLoader.Locations(Game1.content)["Default"]; foreach (SpawnFishData f in Default.Fish) if (!entries.Contains(f)) entries.Add(f); @@ -316,8 +316,19 @@ public static bool ContainsFish(LocationData loc) { return false; } private static string getLocName(LocationData data) { - string name = data.DisplayName == null ? "No DisplayName" : data.DisplayName; + string name = data.DisplayName ?? "No DisplayName"; string endCheck = name.Substring(name.Length - 7); + switch (name) { + case "Farm_Standard": + case "Farm_Forest": + case "Farm_FourCorners": + case "Farm_Hilltop": + case "Farm_Riverland": + case "Farm_Wilderness": + case "Farm_Beach": + name = "Farm"; + break; + } switch (endCheck) { case "Name]]]": name = "Farm"; @@ -344,7 +355,7 @@ private static string getLocName(LocationData data) { name = "Woods"; break; } - if (name.Length > 25) name = endCheck; + //if (name.Length > 25) name = endCheck; return name; } private static bool canAddFish(SpawnFishData fish, int season, LocationData data) { diff --git a/Almanac/Managers/LuckManager.cs b/Almanac/Managers/LuckManager.cs index a2c67d4..9b3a43c 100644 --- a/Almanac/Managers/LuckManager.cs +++ b/Almanac/Managers/LuckManager.cs @@ -1,5 +1,5 @@ #nullable enable - +/* using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -229,7 +229,7 @@ public double GetModifiedLuckForDate(ulong seed, WorldDate date) { #region Events - public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, Common.GameStateQuery.GameState state, string? key = null) { + public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, GameStateQuery state, string? key = null) { if (notice == null) return null; @@ -280,7 +280,7 @@ public double GetModifiedLuckForDate(ulong seed, WorldDate date) { } // Condition Validation - if (!string.IsNullOrEmpty(notice.Condition) && !Common.GameStateQuery.CheckConditions(notice.Condition, state)) + if (!string.IsNullOrEmpty(notice.Condition) && !GameStateQuery.CheckConditions(notice.Condition)) return null; // Get icon @@ -535,7 +535,7 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { I18n.Page_Fortune_Event_Ufo(), null, SpriteHelper.GetSprite(new SObject(Vector2.Zero, 96)) - );*/ + ); return null; } @@ -605,3 +605,4 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { #endregion } +*/ diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index d2c9b8e..c306c73 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -1,4 +1,4 @@ -#nullable enable +/*#nullable enable using System; using System.Collections.Generic; @@ -805,3 +805,4 @@ public static bool IsBooksellerVisiting(WorldDate date) { #endregion } +*/ diff --git a/Almanac/Menus/BookCollectionMenu.cs b/Almanac/Menus/BookCollectionMenu.cs index 3ee08f0..b57ccb8 100644 --- a/Almanac/Menus/BookCollectionMenu.cs +++ b/Almanac/Menus/BookCollectionMenu.cs @@ -101,7 +101,7 @@ private void DiscoverBooks() { if (value == null || !value.Enable) continue; - bool locked = !string.IsNullOrEmpty(value.Condition) && !Common.GameStateQuery.CheckConditions(value.Condition); + bool locked = !string.IsNullOrEmpty(value.Condition) && !GameStateQuery.CheckConditions(value.Condition); if (value.Secret && locked) continue; @@ -520,7 +520,7 @@ public override void draw(SpriteBatch b) { if (LockedBooks[book]) continue; - var sprite = SpriteHelper.GetSprite(InventoryHelper.CreateItemById($"(O){idx}", 1)); + var sprite = SpriteHelper.GetSprite(ItemRegistry.Create($"(O){idx}")); idx++; float scale = cmp.scale - 1; diff --git a/Almanac/ModAPI.cs b/Almanac/ModAPI.cs index 0c552bf..29d198b 100644 --- a/Almanac/ModAPI.cs +++ b/Almanac/ModAPI.cs @@ -25,10 +25,10 @@ public interface IAlmanacAPI { /// itself doesn't have the ability. /// int DaysPerMonth { get; } - + /* #region Custom Pages - /*void RegisterPage( + void RegisterPage( IManifest manifest, string id, // State @@ -74,9 +74,10 @@ public interface IAlmanacAPI { Action, Action> onCellHover = null ); - void UnregisterPage(IManifest manifest, string id);*/ + void UnregisterPage(IManifest manifest, string id); #endregion + */ #region Crops Page @@ -174,6 +175,7 @@ IReadOnlyCollection phaseSprites #endregion + /* #region Fortunes Page /// @@ -289,6 +291,7 @@ IReadOnlyCollection phaseSprites string GetWeatherForDate(WorldDate date, string context = "Default"); #endregion + */ } public class ModAPI : IAlmanacAPI { @@ -514,7 +517,7 @@ public List GetSeasonCrops(string season) { } #endregion - + /* #region Fortune Telling public void SetFortuneHook(IManifest manifest, Func>> hook) { @@ -530,7 +533,7 @@ public void ClearFortuneHook(IManifest manifest) { } #endregion - + #region Local Notices public void SetNoticesHook(IManifest manifest, Func>> hook) { @@ -546,7 +549,7 @@ public void ClearNoticesHook(IManifest manifest) { } #endregion - + #region Weather public string GetWeatherForDate(WorldDate date, GameLocation location) { @@ -587,6 +590,6 @@ public string GetWeatherForDate(WorldDate date, string context = "Default") { } #endregion - + */ } } diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index c2ab690..e0c147f 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -21,6 +21,7 @@ using StardewValley; using StardewValley.Locations; using StardewValley.Menus; +using StardewValley.BellsAndWhistles; using Leclair.Stardew.Almanac.Crops; using Leclair.Stardew.Almanac.Fish; @@ -62,9 +63,9 @@ public class ModEntry : ModSubscriber { #nullable disable public ModConfig Config; - public WeatherManager Weather; - public LuckManager Luck; - public NoticesManager Notices; + //public WeatherManager Weather; + //public LuckManager Luck; + //public NoticesManager Notices; internal CropManager Crops; internal FishManager Fish; internal BookManager Books; @@ -100,8 +101,8 @@ public override void Entry(IModHelper helper) { Harmony = new Harmony(ModManifest.UniqueID); // Patches - // Patches.GameMenu_Patches.Patch(this); - Common_SpriteText_Patches.Patch(Harmony, Monitor); + Patches.GameMenu_Patches.Patch(this); + //Patches.Workbench_Patches.Patch(this); Assets = new(this); @@ -112,9 +113,9 @@ public override void Entry(IModHelper helper) { Crops = new(this); Fish = new(this); - Weather = new(this); - Luck = new(this); - Notices = new(this); + //Weather = new(this); + //Luck = new(this); + //Notices = new(this); Books = new(this); ThemeManager = new(this, Config.Theme); @@ -124,14 +125,14 @@ public override void Entry(IModHelper helper) { // Init RegisterBuilder(CoverPage.GetPage); RegisterBuilder(CropPage.GetPage); - RegisterBuilder(WeatherPage.GetPage); - RegisterBuilder(WeatherPage.GetIslandPage); + //RegisterBuilder(WeatherPage.GetPage); + //RegisterBuilder(WeatherPage.GetIslandPage); RegisterBuilder(TrainPage.GetPage); - RegisterBuilder(FortunePage.GetPage); + //RegisterBuilder(FortunePage.GetPage); RegisterBuilder(MinesPage.GetPage); - RegisterBuilder(NoticesPage.GetPage); + //RegisterBuilder(NoticesPage.GetPage); RegisterBuilder(FishingPage.GetPage); - RegisterBuilder(DebugItemsPage.GetPage); + //RegisterBuilder(DebugItemsPage.GetPage); } public override object GetApi() { @@ -264,7 +265,7 @@ private void OnButton(object? sender, ButtonPressedEventArgs e) { [Subscriber] [EventPriority(EventPriority.High)] - private void OnDayStarted(object? sender, DayStartedEventArgs e) { + /*private void OnDayStarted(object? sender, DayStartedEventArgs e) { ulong seed = GetBaseWorldSeed(); if (Config.EnableDeterministicLuck && Game1.IsMasterGame) { @@ -289,15 +290,15 @@ private void OnDayStarted(object? sender, DayStartedEventArgs e) { .GetWeatherForDate(seed, tomorrow, ctx, "Island"); } } - } + }*/ public void Invalidate() { Assets.Invalidate(); Crops.Invalidate(); Fish.Invalidate(); - Luck.Invalidate(); - Notices.Invalidate(); - Weather.Invalidate(); + //Luck.Invalidate(); + //Notices.Invalidate(); + //Weather.Invalidate(); } [Subscriber] @@ -323,7 +324,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { string input = string.Join(' ', args); Log($" Input: {input}"); - Log($"Result: {StringTokenizer.ParseString(input, item: Game1.player?.CurrentItem, monitor: Monitor, trace: true)}"); + // Log($"Result: {StringTokenizer.ParseString(input, item: Game1.player?.CurrentItem, monitor: Monitor, trace: true)}"); }); Helper.ConsoleCommands.Add("al_gsq", "Run a GameStateQuery", (name, args) => { @@ -343,7 +344,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { Log($" Query: {query}"); if (seed != -1) Log($" Seed: {seed}"); - Log($"Result: {Common.GameStateQuery.CheckConditions(query, rnd: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); + //Log($"Result: {GameStateQuery.CheckConditions(query, rnd: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); }); Helper.ConsoleCommands.Add("al_update", "Invalidate cached data.", (name, args) => { @@ -380,7 +381,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { Log($"DaysPlayed: {Game1.stats.DaysPlayed}", LogLevel.Info); }); - Helper.ConsoleCommands.Add("al_forecast", "Get the forecast for the loaded save.", (name, args) => { + /*Helper.ConsoleCommands.Add("al_forecast", "Get the forecast for the loaded save.", (name, args) => { ulong seed = GetBaseWorldSeed(); WorldDate date = new(Game1.Date); for (int i = 0; i < 4 * 28; i++) { @@ -388,7 +389,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { Log($"Date: {date.Localize()} -- Weather: {weather}"); date.TotalDays++; } - }); + });*/ } [Subscriber] @@ -883,7 +884,7 @@ public ulong GetBaseWorldSeed() { } public bool DoesTranslationExist(string key) { - return Helper.Translation.ContainsKey(key); + return Helper.Translation.Get(key).HasValue(); } public string GetSubLocationName(Models.SubLocation sub) { @@ -904,6 +905,8 @@ public string GetSubLocationName(Models.SubLocation sub) { return I18n.Location_Forest_River(); if (sub.Area == "Pond") return I18n.Location_Forest_Pond(); + if (sub.Area == "Lake") + return I18n.Location_Forest_Lake(); break; case "IslandWest": @@ -912,6 +915,11 @@ public string GetSubLocationName(Models.SubLocation sub) { if (sub.Area == "Freshwater") return I18n.Location_Island_Freshwater(); break; + + case "Desert": + if (sub.Area == "TopPond") + return I18n.Location_Desert_TopPond(); + break; } return sub.Area.ToString(); @@ -1023,6 +1031,13 @@ public string GetSubLocationName(Models.SubLocation sub) { case "Deluxe Barn": case "Deluxe Coop": case "Farm": + case "Farm_Standard": + case "Farm_Forest": + case "Farm_FourCorners": + case "Farm_Hilltop": + case "Farm_Riverland": + case "Farm_Wilderness": + case "Farm_Beach": case "FarmCave": case "FarmHouse": case "Greenhouse": diff --git a/Almanac/Pages/FortunePage.cs b/Almanac/Pages/FortunePage.cs index 12f3c5c..5032602 100644 --- a/Almanac/Pages/FortunePage.cs +++ b/Almanac/Pages/FortunePage.cs @@ -1,3 +1,4 @@ +/* #nullable enable using System; @@ -86,7 +87,7 @@ public override void Update() { double luck = Mod.Luck.GetModifiedLuckForDate(Seed, date); sprite = Sprites![day - 1] = Mod.Luck.GetLuckSprite(luck); Luck![day - 1] = luck; - } else + } else sprite = null; FlowBuilder db = new(); @@ -242,3 +243,4 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { #endregion } +*/ diff --git a/Almanac/Pages/MinesPage.cs b/Almanac/Pages/MinesPage.cs index a1c3a7d..de33107 100644 --- a/Almanac/Pages/MinesPage.cs +++ b/Almanac/Pages/MinesPage.cs @@ -40,15 +40,15 @@ public MinesPage(AlmanacMenu menu, ModEntry mod) : base(menu, mod) { Sprites = new(); Sprites[LevelType.Mushroom] = SpriteHelper.GetSprite( - InventoryHelper.CreateItemById("(O)420", 1) // Red Mushroom + ItemRegistry.Create("(O)420", 1) // Red Mushroom ); Sprites[LevelType.InfestedMonster] = SpriteHelper.GetSprite( - InventoryHelper.CreateItemById("(W)0", 1) // Rusty Sword + ItemRegistry.Create("(W)0", 1) // Rusty Sword ); Sprites[LevelType.InfestedSlime] = SpriteHelper.GetSprite( - InventoryHelper.CreateItemById("(O)766", 1) // Slime + ItemRegistry.Create("(O)766", 1) // Slime ); Sprites[LevelType.Quarry] = new SpriteInfo( @@ -62,7 +62,7 @@ public MinesPage(AlmanacMenu menu, ModEntry mod) : base(menu, mod) { ); Sprites[LevelType.Dino] = SpriteHelper.GetSprite( - InventoryHelper.CreateItemById("(O)107", 1) // Dino Egg + ItemRegistry.Create("(O)107", 1) // Dino Egg ); Update(); diff --git a/Almanac/Pages/NoticesPage.cs b/Almanac/Pages/NoticesPage.cs index 8510146..5c70ff4 100644 --- a/Almanac/Pages/NoticesPage.cs +++ b/Almanac/Pages/NoticesPage.cs @@ -1,3 +1,4 @@ +/* #nullable enable using System; @@ -411,3 +412,4 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { #endregion } +*/ diff --git a/Almanac/Pages/WeatherPage.cs b/Almanac/Pages/WeatherPage.cs index e621c8e..42d7ca5 100644 --- a/Almanac/Pages/WeatherPage.cs +++ b/Almanac/Pages/WeatherPage.cs @@ -1,3 +1,4 @@ +/* #nullable enable using System; @@ -214,7 +215,7 @@ public override void Update() { } builder.Text("\n\n"); - }*/ + } if (pirateDays!.Count > 0) { string dates = string.Join(", ", pirateDays); @@ -333,3 +334,4 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { #endregion } +*/ diff --git a/Almanac/Patches/Game1_Patches.cs b/Almanac/Patches/Game1_Patches.cs index 8830b35..55308c6 100644 --- a/Almanac/Patches/Game1_Patches.cs +++ b/Almanac/Patches/Game1_Patches.cs @@ -18,7 +18,7 @@ internal static class Game1_Patches { internal static void Patch(ModEntry mod) { Monitor = mod.Monitor; - + /* try { mod.Harmony.Patch( original: AccessTools.Method(typeof(Game1), nameof(Game1.UpdateWeatherForNewDay)), @@ -27,9 +27,9 @@ internal static void Patch(ModEntry mod) { } catch(Exception ex) { mod.Log("An error occurred while registering a harmony patch for Game1.", LogLevel.Error, ex); - } + }*/ } - + /* public static void UpdateWeatherForNewDay_Postfix() { try { ModEntry.Instance.Weather.UpdateForNewDay(); @@ -39,4 +39,5 @@ public static void UpdateWeatherForNewDay_Postfix() { Monitor.Log($"Details:\n{ex}", LogLevel.Error); } } + */ } diff --git a/Almanac/i18n/default.json b/Almanac/i18n/default.json index 8887ac0..3b07ab6 100644 --- a/Almanac/i18n/default.json +++ b/Almanac/i18n/default.json @@ -351,27 +351,31 @@ "location.Forest.River": "River", "location.Forest.Pond": "Pond", + "location.Forest.Lake": "Lake", "location.Island.Ocean": "Ocean", "location.Island.Freshwater": "Freshwater", - + "Location.Desert.TopPond": "TopPond", // Maps: Stardew Aquarium "location.Custom_ExteriorMuseum": "Stardew Aquarium", // Maps: Stardew Valley Expanded "location.Custom_AdventurerSummit": "Adventurer Summit", "location.Custom_BlueMoonVineyard": "Blue Moon Vineyard", - "location.Custom_BlueMoonVineyard.0": "Ocean", - "location.Custom_BlueMoonVineyard.1": "River", + "location.Custom_BlueMoonVineyard.Ocean": "Ocean", + "location.Custom_BlueMoonVineyard.River": "River", "location.Custom_CrimsonBadlands": "Crimson Badlands", "location.Custom_FableReef": "Fable Reef", "location.Custom_ForestWest": "West Cindersap Forest", "location.Custom_Highlands": "The Highlands", - "location.Custom_Highlands.0": "Ruins", - "location.Custom_Highlands.1": "River", + "location.Custom_Highlands.Pond": "Pond", + "location.Custom_Highlands.River": "River", "location.Custom_HighlandsCavern": "The Highlands (Cavern)", "location.Custom_JunimoWoods": "Junimo Woods", - //"location.Custom_MorrisProperty": "", + "location.Custom_MorrisProperty": "Morris Property", + "location.Custom_HenchmanBackyard": "Henchman Backyard", + "location.Custom_ForbiddenMaze": "Forbidden Maze", + "location.Custom_DiamondCavern": "Diamond Cavern", "location.Custom_ShearwaterBridge": "Shearwater Bridge", "location.Custom_SpriteSpring2": "Sprite Spring", "location.Custom_GrampletonSuburbs": "Grampleton Suburbs", diff --git a/Almanac/i18n/zh.json b/Almanac/i18n/zh.json index ce29dc4..df9fe2a 100644 --- a/Almanac/i18n/zh.json +++ b/Almanac/i18n/zh.json @@ -21,7 +21,16 @@ "crop.last-day": "最后一天:", "crop.toggle": "切换{{mode}}", - "crop.paddy": "水稻奖金", + "crop.paddy": "水稻灌溉加成", + + "crop.crop.none": "没有作物符合你现在的筛选。", + + "page.crop.seed-filter": "按种子筛选", + "page.crop.seed-filter.disabled": "关闭", + "page.crop.seed-filter.inventory": "背包中", + "page.crop.seed-filter.owned": "拥有的", + + "crop.seed-filter": "只展示拥有的种子", "crop.using-none": "以下的生长时间和日期假设你没有肥料或技能。", "crop.using-agri": "以下的生长时间和日期假设你是一个{{agriculturist}}。", @@ -54,9 +63,10 @@ "weather.sunny": "晴天", "weather.rain": "雨天", "weather.debris": "多云", - "weather.lightning": "雷雨天", + "weather.lightning": "雷雨", "weather.festival": "晴天", - "weather.snow": "下雪天", + "weather.snow": "雪天", + "weather.green": "绿雨", "page.train": "火车时刻表", "page.train.about": "@B警告:@b 火车将在以下时间经过星露谷火车站。 为了您的安全,当有火车出现时,请保持与轨道的距离。", @@ -124,8 +134,8 @@ "page.fish.size": "你钓到过的最大尺寸是@B{{big_cm}}\"厘米@b。", "page.fish.size.range": "它的尺寸范围为@B{{min_cm}}\"厘米@b至@B{{max_cm}}\"厘米@b。", - "page.fish.weather.Sunny": "{{fish}}只在@Bsunny@b的天气可钓到。", - "page.fish.weather.Rainy": "{{fish}}只在@Brainy@b的天气可钓到。", + "page.fish.weather.Sunny": "{{fish}}只在@B晴朗@b的天气可钓到。", + "page.fish.weather.Rainy": "{{fish}}只在@B下雨@b的天气可钓到。", "page.fish.weather.Any": "{{fish}}任何天气都能钓,风雨无阻。 ", "page.fish.level": "你需要{{skill}}的@B{{level}}@b级别才能钓到这种鱼。", "page.fish.legendary": "{{fish}}是传说鱼类。", @@ -157,6 +167,13 @@ "page.fish.location.fresh": "{{fish}}生活在淡水区域。", "page.fish.location.ocean": "{{fish}}生活在海水区域。", + "page.fish.filter.aquarium": "按水族馆捐赠筛选", + "page.fish.aquarium.true": "已捐赠", + "page.fish.aquarium.false": "未捐赠", + + "page.fish.aquarium.donated": "你已经捐给了星露谷水族馆。", + "page.fish.aquarium.not-donated": "你还没有捐给星露谷水族馆。", + // Settings "settings.button": "显示年历按钮", @@ -272,7 +289,7 @@ // Locations - "location.WitchSwamp": "女巫的沼泽", + "location.WitchSwamp": "女巫沼泽", "location.BugLand": "突变虫穴", "location.Caldera": "姜岛(火山地牢)", @@ -282,34 +299,43 @@ "location.IslandSouthEast": "姜岛东南部", "location.IslandSouthEastCave": "姜岛东南部(洞穴)", "location.IslandEast": "姜岛东部", + "location.Submarine": "潜艇", "location.sub-any": "任何地点", "location.sub-floor": "{{floor}}层", "location.Forest.River": "河边", "location.Forest.Pond": "池塘", + "location.Forest.Lake": "湖泊", "location.Island.Ocean": "海边", "location.Island.Freshwater": "淡水区", + "Location.Desert.TopPond": "池塘", + // Maps: Stardew Aquarium "location.Custom_ExteriorMuseum": "星露谷水族馆", "location.ExteriorMuseum": "星露谷水族馆", // Maps: Stardew Valley Expanded - "location.Custom_AdventurerSummit": "冒险家公会", + "location.Custom_AdventurerSummit": "探险家山峰", "location.Custom_BlueMoonVineyard": "蓝月亮葡萄园", - "location.Custom_BlueMoonVineyard.0": "海边", - "location.Custom_BlueMoonVineyard.1": "河边", - "location.Custom_CrimsonBadlands": "猩红荒地", - "location.Custom_FableReef": "寓言礁石", + "location.Custom_BlueMoonVineyard.Ocean": "海边", + "location.Custom_BlueMoonVineyard.River": "河边", + "location.Custom_CrimsonBadlands": "绯红荒地", + "location.Custom_FableReef": "寓言礁", "location.Custom_ForestWest": "煤矿森林西", "location.Custom_Highlands": "高地", - "location.Custom_Highlands.0": "废墟", - "location.Custom_Highlands.1": "河边", - "location.Custom_HighlandsCavern": "高地(洞穴)", + "location.Custom_Highlands.Pond": "池塘", + "location.Custom_Highlands.River": "河边", + "location.Custom_HighlandsCavern": "高地洞穴", "location.Custom_JunimoWoods": "祝尼魔森林", - //"location.Custom_MorrisProperty": "", - "location.Custom_ShearwaterBridge": "海鸥栈桥", - "location.Custom_SpriteSpring2": "精灵温泉" + "location.Custom_MorrisProperty": "莫里斯地产", + "location.Custom_HenchmanBackyard": "仆从后院", + "location.Custom_ForbiddenMaze": "禁忌迷宫", + "location.Custom_DiamondCavern": "钻石洞穴", + "location.Custom_ShearwaterBridge": "海鸥桥", + "location.Custom_SpriteSpring2": "精灵泉", + "location.Custom_GrampletonSuburbs": "郊区", + "location.Custom_GrampletonSuburbsTrainStation": "郊区火车站" } diff --git a/Almanac/manifest.json b/Almanac/manifest.json index f30096e..f9a08b9 100644 --- a/Almanac/manifest.json +++ b/Almanac/manifest.json @@ -10,9 +10,10 @@ "UniqueID": "leclair.almanac", "Name": "Almanac", "Author": "Khloe Leclair", - "Version": "0.18.1-Debug", + "Version": "0.18.1", "Description": "Adds an Almanac for the player, with forecasts, planting dates, and other calendar information useful for a farmer.", - "MinimumApiVersion": "3.17.0", + "MinimumApiVersion": "4.1.0", + "MinimumGameVersion": "1.6.9", "EntryDll": "Almanac.dll", "Dependencies": [ { From e007810540d61106242772c98870a639b0bfc579 Mon Sep 17 00:00:00 2001 From: xysztm Date: Tue, 5 Nov 2024 20:46:13 +0800 Subject: [PATCH 33/39] Revert "Edit the path for my path" This reverts commit aa42d21a1cbacb6dcd279ca02249f2e8488cc9fb. --- _build/common.targets | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_build/common.targets b/_build/common.targets index c131382..18f0d85 100644 --- a/_build/common.targets +++ b/_build/common.targets @@ -30,9 +30,9 @@ - - - D:\SteamLibrary\steamapps\common\Stardew Valley + + + C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley C:\ $(GamePath)\$(ModSet) From 31163891f91bcc8a650cef0f2939dc2400e3238c Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:02:11 -0500 Subject: [PATCH 34/39] Merged in ShotrayWW's fish fixes then added back the Random forecasts, Since ShotrayWW just commented out the entire files, it didn't conflict with the fixes I already did for the new 1.6 things (Bookseller, new crops, green rain) Also did some spacing shenanigans because I'm weird about that. --- Almanac/Managers/LuckManager.cs | 44 +- Almanac/Managers/NoticesManager.cs | 1133 ++++++++++++++-------------- Almanac/ModAPI.cs | 24 +- Almanac/ModEntry.cs | 72 +- Almanac/Pages/FortunePage.cs | 14 +- Almanac/Pages/NoticesPage.cs | 20 +- Almanac/Pages/WeatherPage.cs | 26 +- Almanac/Patches/Game1_Patches.cs | 3 +- Almanac/manifest.json | 6 +- 9 files changed, 655 insertions(+), 687 deletions(-) diff --git a/Almanac/Managers/LuckManager.cs b/Almanac/Managers/LuckManager.cs index 9b3a43c..7df9c89 100644 --- a/Almanac/Managers/LuckManager.cs +++ b/Almanac/Managers/LuckManager.cs @@ -1,5 +1,5 @@ #nullable enable -/* + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -19,6 +19,7 @@ using StardewValley; using Leclair.Stardew.Almanac.Models; +using StardewValley.TokenizableStrings; namespace Leclair.Stardew.Almanac.Managers; @@ -183,7 +184,7 @@ public void RegisterHook(IManifest mod, Func GetEventsForDate(ulong seed, WorldDate date) { Load(); - var state = new Common.GameStateQuery.GameState( - Random: Game1.random, - Date: date, - TimeOfDay: 600, - Ticks: 0, - Farmer: Game1.player, - Location: null, - Item: null, - Monitor: Mod.Monitor, - DoTrace: false - ); if (DataEvents != null) foreach (var entry in DataEvents) { - IRichEvent? hydrated = HydrateEvent(entry.Value, date, state, entry.Key); + IRichEvent? hydrated = HydrateEvent(entry.Value, date, entry.Key); if (hydrated != null) yield return hydrated; } @@ -451,7 +441,7 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { public static IRichEvent? GetTrashEvent(ulong seed, WorldDate date) { for (int i = 0; i < 8; i++) { - Random rnd = new((date.TotalDays + 1) + ((int)seed / 2) + 777 + i * 77); + Random rnd = new((date.TotalDays + 1) + ((int) seed / 2) + 777 + i * 77); int prewarm = rnd.Next(0, 100); for (int j = 0; j < prewarm; j++) @@ -466,7 +456,7 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { if (rnd.NextDouble() >= 0.002) continue; - Item? item = InventoryHelper.CreateItemById("(H)66", 1); + Item? item = ItemRegistry.Create("(H)66", 1); SpriteInfo? sprite = SpriteHelper.GetSprite(item); return new RichEvent( @@ -485,7 +475,7 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { if (days == 31) return null; - Random rnd = new(days + (int)seed / 2); + Random rnd = new(days + (int) seed / 2); // Don't track any of the Community Center / Joja events because // those all rely on game state and are not random based on the @@ -536,9 +526,10 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { null, SpriteHelper.GetSprite(new SObject(Vector2.Zero, 96)) ); + */ - return null; - } + return null; + } private static IRichEvent? GetLuckSkillEventForDate(ulong seed, WorldDate date) { int days = date.TotalDays + 1 + 999999; @@ -596,13 +587,10 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { SpriteHelper.GetSprite(new SObject(Vector2.Zero, "95")) ); - // Don't track Strange Capsule, because that relies on whether - // or not the player has already seen it. - return null; } - #endregion +#endregion } -*/ + diff --git a/Almanac/Managers/NoticesManager.cs b/Almanac/Managers/NoticesManager.cs index 7ef8079..d5e0ad5 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -1,4 +1,4 @@ -/*#nullable enable +#nullable enable using System; using System.Collections.Generic; @@ -25,6 +25,7 @@ using StardewValley.GameData.Shops; using StardewValley.Internal; using StardewValley.GameData; +using StardewValley.TokenizableStrings; namespace Leclair.Stardew.Almanac.Managers; @@ -54,7 +55,7 @@ public void Invalidate() { [Subscriber] private void OnAssetInvalidated(object? sender, AssetsInvalidatedEventArgs e) { - foreach(var name in e.Names) + foreach (var name in e.Names) if (name.IsEquivalentTo(AssetManager.LocalNoticesPath)) { Loaded = false; DataEvents = null; @@ -105,715 +106,693 @@ private Dictionary LoadEvents() { } } - return events; - } - - foreach (var entry in data) { - entry.Value.Translation = cp.Translation; - entry.Value.ModContent = cp.ModContent; - if (!events.ContainsKey(entry.Key)) - events[entry.Key] = entry.Value; - } - } - return events; } [MemberNotNull(nameof(DataEvents))] - private void Load() { - if (Loaded && DataEvents != null) - return; +private void Load() { + if (Loaded && DataEvents != null) + return; - DataEvents = Mod.Helper.GameContent.Load>(AssetManager.LocalNoticesPath); - Loaded = true; - } + DataEvents = Mod.Helper.GameContent.Load>(AssetManager.LocalNoticesPath); + Loaded = true; +} - #endregion +#endregion - #region Mod Management +#region Mod Management - public void ClearHook(IManifest mod) { - if (InterfaceHooks.ContainsKey(mod)) - InterfaceHooks.Remove(mod); +public void ClearHook(IManifest mod) { + if (InterfaceHooks.ContainsKey(mod)) + InterfaceHooks.Remove(mod); - if (ModHooks.ContainsKey(mod)) - ModHooks.Remove(mod); - } + if (ModHooks.ContainsKey(mod)) + ModHooks.Remove(mod); +} - public void RegisterHook(IManifest mod, Func>> hook) { - if (InterfaceHooks.ContainsKey(mod)) - InterfaceHooks.Remove(mod); +public void RegisterHook(IManifest mod, Func>> hook) { + if (InterfaceHooks.ContainsKey(mod)) + InterfaceHooks.Remove(mod); - if (hook == null && ModHooks.ContainsKey(mod)) - ModHooks.Remove(mod); - else if (hook != null) - ModHooks[mod] = hook; - } + if (hook == null && ModHooks.ContainsKey(mod)) + ModHooks.Remove(mod); + else if (hook != null) + ModHooks[mod] = hook; +} - public void RegisterHook(IManifest mod, Func> hook) { - if (ModHooks.ContainsKey(mod)) - ModHooks.Remove(mod); +public void RegisterHook(IManifest mod, Func> hook) { + if (ModHooks.ContainsKey(mod)) + ModHooks.Remove(mod); - if (hook == null && InterfaceHooks.ContainsKey(mod)) - InterfaceHooks.Remove(mod); - else if (hook != null) - InterfaceHooks[mod] = hook; - } + if (hook == null && InterfaceHooks.ContainsKey(mod)) + InterfaceHooks.Remove(mod); + else if (hook != null) + InterfaceHooks[mod] = hook; +} - #endregion +#endregion - #region Events +#region Events - public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, Common.GameStateQuery.GameState state, string? key = null) { - if (notice == null) - return null; +public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, string? key = null) { + if (notice == null) + return null; - // Year Validation - if (notice.FirstYear > date.Year || notice.LastYear < date.Year) - return null; + // Year Validation + if (notice.FirstYear > date.Year || notice.LastYear < date.Year) + return null; - if (notice.ValidYears != null && !notice.ValidYears.Contains(date.Year)) - return null; + if (notice.ValidYears != null && !notice.ValidYears.Contains(date.Year)) + return null; - // Season Validation - if (notice.ValidSeasons != null && !notice.ValidSeasons.Contains(Common.Enums.Season.All) && !notice.ValidSeasons.Contains((Common.Enums.Season) date.SeasonIndex)) - return null; + // Season Validation + if (notice.ValidSeasons != null && !notice.ValidSeasons.Contains(Common.Enums.Season.All) && !notice.ValidSeasons.Contains((Common.Enums.Season) date.SeasonIndex)) + return null; - // Date Range Validation - int day; - - bool first = true; - - switch(notice.Period) { - case TimeScale.Year: - day = date.TotalDays % (WorldDate.MonthsPerYear * ModEntry.DaysPerMonth); - break; - case TimeScale.Season: - day = date.DayOfMonth; - break; - case TimeScale.Week: - day = date.DayOfMonth % 7; - break; - default: - day = -1; - break; - } + // Date Range Validation + int day; - if (notice.Ranges != null) { - bool matched = false; - first = false; - foreach (var range in notice.Ranges) { - if (range.Start <= day && range.End >= day && (range.Valid == null || range.Valid.Contains(day))) { - if (range.Start == day) - first = true; - matched = true; - } - } + bool first = true; - if (!matched) - return null; - } - - // Condition Validation - if (!string.IsNullOrEmpty(notice.Condition) && !Common.GameStateQuery.CheckConditions(notice.Condition, state)) - return null; - - // Get icon - Item? item = null; - SpriteInfo? sprite; + switch (notice.Period) { + case TimeScale.Year: + day = date.TotalDays % (WorldDate.MonthsPerYear * ModEntry.DaysPerMonth); + break; + case TimeScale.Season: + day = date.DayOfMonth; + break; + case TimeScale.Week: + day = date.DayOfMonth % 7; + break; + default: + day = -1; + break; + } - // Try parsing the item. - // This will change in 1.6 - if (!string.IsNullOrEmpty(notice.Item)) { - try { - item = InventoryHelper.CreateItemById(notice.Item, 1); - } catch(Exception ex) { - Log($"Unable to get item instance for: {notice.Item}", LogLevel.Warn, ex); - item = null; + if (notice.Ranges != null) { + bool matched = false; + first = false; + foreach (var range in notice.Ranges) { + if (range.Start <= day && range.End >= day && (range.Valid == null || range.Valid.Contains(day))) { + if (range.Start == day) + first = true; + matched = true; } } - if (notice.IconType == NoticeIconType.Item) { - sprite = item == null ? null : SpriteHelper.GetSprite(item); - - } else if (notice.IconType == NoticeIconType.ModTexture) { - Texture2D? tex; - if (!string.IsNullOrEmpty(notice.IconPath) && notice.ModContent != null) - tex = notice.ModContent.Load(notice.IconPath); - else - tex = null; - - sprite = tex == null ? null : new SpriteInfo( - tex, - notice.IconSourceRect ?? tex.Bounds - ); - - } else if (notice.IconType == NoticeIconType.Texture) { - Texture2D? tex; - if (!string.IsNullOrEmpty(notice.IconPath)) - tex = Mod.Helper.GameContent.Load(notice.IconPath); - else if (notice.IconSource.HasValue) - tex = SpriteHelper.GetTexture(notice.IconSource.Value); - else - tex = null; - - sprite = tex == null ? null : new SpriteInfo( - tex, - notice.IconSourceRect ?? tex.Bounds - ); + if (!matched) + return null; + } - } else { + // Condition Validation + if (!string.IsNullOrEmpty(notice.Condition) && !GameStateQuery.CheckConditions(notice.Condition)) + return null; + + // Get icon + Item? item = null; + SpriteInfo? sprite; + + // Try parsing the item. + // This will change in 1.6 + if (!string.IsNullOrEmpty(notice.Item)) { + try { + item = ItemRegistry.Create(notice.Item, 1); + } catch (Exception ex) { + Log($"Unable to get item instance for: {notice.Item}", LogLevel.Warn, ex); item = null; - sprite = null; } + } - if (notice.Translation != null && ! string.IsNullOrEmpty(notice.I18nKey)) - notice.Description = notice.Translation.Get(notice.I18nKey).ToString(); + if (notice.IconType == NoticeIconType.Item) { + sprite = item == null ? null : SpriteHelper.GetSprite(item); - string? desc = string.IsNullOrEmpty(notice.Description) ? null : StringTokenizer.ParseString(notice.Description, state); - if (desc != null && Mod.Config.DebugMode && !string.IsNullOrEmpty(key)) - desc = $"{desc} @C@c@h(#{key})"; + } else if (notice.IconType == NoticeIconType.ModTexture) { + Texture2D? tex; + if (!string.IsNullOrEmpty(notice.IconPath) && notice.ModContent != null) + tex = notice.ModContent.Load(notice.IconPath); + else + tex = null; - return new RichEvent( - (first || notice.ShowEveryDay) ? desc : null, - null, - sprite, - item + sprite = tex == null ? null : new SpriteInfo( + tex, + notice.IconSourceRect ?? tex.Bounds ); + + } else if (notice.IconType == NoticeIconType.Texture) { + Texture2D? tex; + if (!string.IsNullOrEmpty(notice.IconPath)) + tex = Mod.Helper.GameContent.Load(notice.IconPath); + else if (notice.IconSource.HasValue) + tex = SpriteHelper.GetTexture(notice.IconSource.Value); + else + tex = null; + + sprite = tex == null ? null : new SpriteInfo( + tex, + notice.IconSourceRect ?? tex.Bounds + ); + + } else { + item = null; + sprite = null; } - public IEnumerable GetEventsForDate(int seed, WorldDate date) { + if (notice.Translation != null && !string.IsNullOrEmpty(notice.I18nKey)) + notice.Description = notice.Translation.Get(notice.I18nKey).ToString(); - Load(); + string? desc = string.IsNullOrEmpty(notice.Description) ? null : TokenParser.ParseText(notice.Description); + if (desc != null && Mod.Config.DebugMode && !string.IsNullOrEmpty(key)) + desc = $"{desc} @C@c@h(#{key})"; - var state = new Common.GameStateQuery.GameState( - Random: Game1.random, - Date: date, - TimeOfDay: 600, - Ticks: 0, - Farmer: Game1.player, - Location: null, - Item: null, - Monitor: Mod.Monitor, - DoTrace: false - ); + return new RichEvent( + (first || notice.ShowEveryDay) ? desc : null, + null, + sprite, + item + ); +} - if (DataEvents != null) - foreach(var entry in DataEvents) { - IRichEvent? hydrated = HydrateEvent(entry.Value, date, state, entry.Key); - if (hydrated != null) - yield return hydrated; - } +public IEnumerable GetEventsForDate(int seed, WorldDate date) { - foreach (var ihook in InterfaceHooks.Values) { - if (ihook != null) - foreach (var entry in ihook(seed, date)) { - if (entry == null) - continue; + Load(); - yield return entry; - } + if (DataEvents != null) + foreach (var entry in DataEvents) { + IRichEvent? hydrated = HydrateEvent(entry.Value, date, entry.Key); + if (hydrated != null) + yield return hydrated; } - foreach (var hook in ModHooks.Values) { - if (hook != null) - foreach (var entry in hook(seed, date)) { - if (entry == null || string.IsNullOrEmpty(entry.Item1)) - continue; + foreach (var ihook in InterfaceHooks.Values) { + if (ihook != null) + foreach (var entry in ihook(seed, date)) { + if (entry == null) + continue; - SpriteInfo? sprite; - - if (entry.Item3.HasValue && entry.Item3.Value == Rectangle.Empty) - sprite = null; - else if (entry.Item2 != null) - sprite = new( - entry.Item2, - entry.Item3 ?? entry.Item2.Bounds - ); - else if (entry.Item4 != null) - sprite = SpriteHelper.GetSprite(entry.Item4); - else - sprite = null; + yield return entry; + } + } - yield return new RichEvent( - entry.Item1, - null, - sprite, - entry.Item4 - ); - } - } + foreach (var hook in ModHooks.Values) { + if (hook != null) + foreach (var entry in hook(seed, date)) { + if (entry == null || string.IsNullOrEmpty(entry.Item1)) + continue; - foreach (var evt in GetVanillaEventsForDate(date)) { - if (evt != null) - yield return evt; - } - } + SpriteInfo? sprite; - #endregion + if (entry.Item3.HasValue && entry.Item3.Value == Rectangle.Empty) + sprite = null; + else if (entry.Item2 != null) + sprite = new( + entry.Item2, + entry.Item3 ?? entry.Item2.Bounds + ); + else if (entry.Item4 != null) + sprite = SpriteHelper.GetSprite(entry.Item4); + else + sprite = null; - #region Vanilla Events + yield return new RichEvent( + entry.Item1, + null, + sprite, + entry.Item4 + ); + } + } - public IEnumerable GetVanillaEventsForDate(WorldDate date) { + foreach (var evt in GetVanillaEventsForDate(date)) { + if (evt != null) + yield return evt; + } +} - // Berry Season - bool gathering = Mod.Config.NoticesShowGathering; +#endregion - if (gathering && bush == null) - bush = new(); +#region Vanilla Events - if (gathering && IsBlooming(bush!, date.Season, date.DayOfMonth)) { - Item? berry = null; - if (date.SeasonIndex == 0) - berry = ItemRegistry.Create("(O)296", 1); // Salmonberry +public IEnumerable GetVanillaEventsForDate(WorldDate date) { - else if (date.SeasonIndex == 2) - berry = ItemRegistry.Create("(O)410", 1); // Blackberry + // Berry Season + bool gathering = Mod.Config.NoticesShowGathering; - if (berry != null) { - bool first_day = date.DayOfMonth == 1 || !IsBlooming(bush!, date.Season, date.DayOfMonth - 1); - int last = date.DayOfMonth; + if (gathering && bush == null) + bush = new(); - // If it's the first day, then we also need the last day - // so we can display a nice string to the user. - if (first_day) - for (int d = date.DayOfMonth + 1; d <= ModEntry.DaysPerMonth; d++) { - if (IsBlooming(bush!, date.Season, d)) - last = d; - else - break; - } + if (gathering && IsBlooming(bush!, date.Season, date.DayOfMonth)) { + Item? berry = null; + if (date.SeasonIndex == 0) + berry = ItemRegistry.Create("(O)296", 1); // Salmonberry - yield return new RichEvent( - null, - first_day ? - FlowHelper.Translate( - Mod.Helper.Translation.Get("page.notices.season"), - new { - item = berry.DisplayName, - start = new SDate(date.DayOfMonth, date.Season).ToLocaleString(withYear: false), - end = new SDate(last, date.Season).ToLocaleString(withYear: false) - }, - align: Alignment.VCenter - ) : null, - SpriteHelper.GetSprite(berry), - berry - ); - } - } + else if (date.SeasonIndex == 2) + berry = ItemRegistry.Create("(O)410", 1); // Blackberry - // Festivals - if (Mod.Config.NoticesShowFestivals && Utility.isFestivalDay(date.DayOfMonth, date.Season)) { - var data = Game1.temporaryContent.Load>("Data\\Festivals\\" + date.SeasonKey + date.DayOfMonth); - if (data.ContainsKey("name") && data.ContainsKey("conditions")) { - string name = data["name"]; - string[] conds = data["conditions"].Split('/'); - string? where = conds.Length >= 1 ? conds[0] : null; - - int start = -1; - int end = -1; - - if (conds.Length >= 2) { - string[] bits = conds[1].Split(' '); - if (bits.Length >= 2) { - start = Convert.ToInt32(bits[0]); - end = Convert.ToInt32(bits[1]); - } - } + if (berry != null) { + bool first_day = date.DayOfMonth == 1 || !IsBlooming(bush!, date.Season, date.DayOfMonth - 1); + int last = date.DayOfMonth; - foreach (GameLocation loc in Game1.locations) { - if (loc?.Name == where) { - where = Mod.GetLocationName(loc) ?? where; + // If it's the first day, then we also need the last day + // so we can display a nice string to the user. + if (first_day) + for (int d = date.DayOfMonth + 1; d <= ModEntry.DaysPerMonth; d++) { + if (IsBlooming(bush!, date.Season, d)) + last = d; + else break; - } } - yield return new RichEvent( - null, + yield return new RichEvent( + null, + first_day ? FlowHelper.Translate( - Mod.Helper.Translation.Get("page.notices.festival"), + Mod.Helper.Translation.Get("page.notices.season"), new { - name, - where, - start = Mod.FormatTime(start), - end = Mod.FormatTime(end) + item = berry.DisplayName, + start = new SDate(date.DayOfMonth, date.Season).ToLocaleString(withYear: false), + end = new SDate(last, date.Season).ToLocaleString(withYear: false) }, align: Alignment.VCenter - ), - new SpriteInfo( - Game1.temporaryContent.Load("LooseSprites\\Billboard"), - new Rectangle( - 1, 398, - 84, 12 - ), - baseFrames: 6 - ) - ); + ) : null, + SpriteHelper.GetSprite(berry), + berry + ); + } + } + + // Festivals + if (Mod.Config.NoticesShowFestivals && Utility.isFestivalDay(date.DayOfMonth, date.Season)) { + var data = Game1.temporaryContent.Load>("Data\\Festivals\\" + date.SeasonKey + date.DayOfMonth); + if (data.ContainsKey("name") && data.ContainsKey("conditions")) { + string name = data["name"]; + string[] conds = data["conditions"].Split('/'); + string? where = conds.Length >= 1 ? conds[0] : null; + + int start = -1; + int end = -1; + + if (conds.Length >= 2) { + string[] bits = conds[1].Split(' '); + if (bits.Length >= 2) { + start = Convert.ToInt32(bits[0]); + end = Convert.ToInt32(bits[1]); + } + } + + foreach (GameLocation loc in Game1.locations) { + if (loc?.Name == where) { + where = Mod.GetLocationName(loc) ?? where; + break; + } } + + yield return new RichEvent( + null, + FlowHelper.Translate( + Mod.Helper.Translation.Get("page.notices.festival"), + new { + name, + where, + start = Mod.FormatTime(start), + end = Mod.FormatTime(end) + }, + align: Alignment.VCenter + ), + new SpriteInfo( + Game1.temporaryContent.Load("LooseSprites\\Billboard"), + new Rectangle( + 1, 398, + 84, 12 + ), + baseFrames: 6 + ) + ); } + } - if (Mod.Config.NoticesShowFestivals && Utility.TryGetPassiveFestivalDataForDay(date.DayOfMonth, date.Season, null, out string id, out PassiveFestivalData passFestData)) { - string displayDate = Utility.getDateStringFor(date.DayOfMonth, date.SeasonIndex, date.Year); - if (passFestData.StartDay <= date.DayOfMonth && passFestData.EndDay >= date.DayOfMonth) { - string festName = id; - try { - Dictionary content = Game1.content.Load>("Strings\\1_6_Strings"); - if (content.ContainsKey(id)) - festName = content[id]; - //These festivals aren't in 1_6_Strings - switch (id) { - case "NightMarket": { + if (Mod.Config.NoticesShowFestivals && Utility.TryGetPassiveFestivalDataForDay(date.DayOfMonth, date.Season, null, out string id, out PassiveFestivalData passFestData)) { + string displayDate = Utility.getDateStringFor(date.DayOfMonth, date.SeasonIndex, date.Year); + if (passFestData.StartDay <= date.DayOfMonth && passFestData.EndDay >= date.DayOfMonth) { + string festName = id; + try { + Dictionary content = Game1.content.Load>("Strings\\1_6_Strings"); + if (content.ContainsKey(id)) + festName = content[id]; + //These festivals aren't in 1_6_Strings + switch (id) { + case "NightMarket": { festName = "Night Market"; break; } - case "DesertFestival": { + case "DesertFestival": { festName = "Desert Festival"; break; } - } - } catch { - ModEntry.Instance.Log("Cannot get festival name.", LogLevel.Warn); } - yield return new RichEvent( - null, - passFestData.StartDay == date.DayOfMonth? FlowHelper.Translate( - Mod.Helper.Translation.Get("page.notices.passive-festival"), - new { - name = festName, - startTime = Mod.FormatTime(passFestData.StartTime), - startDay = Utility.getDateStringFor(passFestData.StartDay, Utility.getSeasonNumber(Utility.getSeasonKey(passFestData.Season)), 1), - endDay = Utility.getDateStringFor(passFestData.EndDay, Utility.getSeasonNumber(Utility.getSeasonKey(passFestData.Season)), 1) - }, - align: Alignment.VCenter - ) : null, - new SpriteInfo( - Game1.mouseCursors, - new Rectangle(346, 392, 8, 8) - ) - ); - } else ModEntry.Instance.Log("Adding festival failed.", LogLevel.Warn); - } - - // Weddings / Anniversaries / Children - foreach (var who in Game1.getAllFarmers()) { - // Children - // TODO: This. - - if (!Mod.Config.NoticesShowAnniversaries) - continue; - - // Player Weddings and Anniversaries - // TODO: This. - - // NPC Weddings and Anniversaries - if ((who.isEngaged() || who.isMarriedOrRoommates()) && who.friendshipData != null) { - foreach (var entry in who.friendshipData.Pairs) { - if (entry.Value == null || entry.Value.WeddingDate == null) - continue; + } catch { + ModEntry.Instance.Log("Cannot get festival name.", LogLevel.Warn); + } + yield return new RichEvent( + null, + passFestData.StartDay == date.DayOfMonth ? FlowHelper.Translate( + Mod.Helper.Translation.Get("page.notices.passive-festival"), + new { + name = festName, + startTime = Mod.FormatTime(passFestData.StartTime), + startDay = Utility.getDateStringFor(passFestData.StartDay, Utility.getSeasonNumber(Utility.getSeasonKey(passFestData.Season)), 1), + endDay = Utility.getDateStringFor(passFestData.EndDay, Utility.getSeasonNumber(Utility.getSeasonKey(passFestData.Season)), 1) + }, + align: Alignment.VCenter + ) : null, + new SpriteInfo( + Game1.mouseCursors, + new Rectangle(346, 392, 8, 8) + ) + ); + } else ModEntry.Instance.Log("Adding festival failed.", LogLevel.Warn); + } - if (entry.Value.IsDivorced()) - continue; + // Weddings / Anniversaries / Children + foreach (var who in Game1.getAllFarmers()) { + // Children + // TODO: This. - WorldDate? wedding = entry.Value.WeddingDate; - if (wedding == null || wedding.SeasonIndex != date.SeasonIndex || wedding.DayOfMonth != date.DayOfMonth) - continue; + if (!Mod.Config.NoticesShowAnniversaries) + continue; - NPC? spouse = Game1.getCharacterFromName(entry.Key); - if (spouse == null) - continue; + // Player Weddings and Anniversaries + // TODO: This. - char last = spouse.displayName.Last(); - - bool no_s = last == 's' || - LocalizedContentManager.CurrentLanguageCode == - LocalizedContentManager.LanguageCode.de && - (last == 'x' || last == 'ß' || last == 'z'); - - var pendant = InventoryHelper.CreateItemById("(O)460", 1); // Mermaid Pendant - var sprite = SpriteHelper.GetSprite(pendant); - - // Wedding? - if (date.Year == wedding.Year) { - yield return new RichEvent( - null, - FlowHelper.Translate( - Mod.Helper.Translation.Get( - no_s ? - "page.notices.wedding.no-s" - : "page.notices.wedding.s" - ), - new { - name = who.displayName, - spouse = spouse.displayName - }, - align: Alignment.VCenter - ), - sprite - ); - } else { - yield return new RichEvent( - null, - FlowHelper.Translate( - Mod.Helper.Translation.Get( - no_s ? - "page.notices.anniversary.no-s" - : "page.notices.anniversary.s" - ), - new { - name = who.displayName, - spouse = spouse.displayName - }, - align: Alignment.VCenter - ), - sprite - ); - } - } - } - } - - // Trains - if (Mod.Config.NoticesShowTrains) { - int time = TrainHelper.GetTrainTime(date); - if (time >= 0) - yield return new RichEvent( - null, - FlowHelper.Translate( - Mod.Helper.Translation.Get("page.notices.train"), - new { - time = Mod.FormatTime(time) - }, - align: Alignment.VCenter - ), - new SpriteInfo( - Game1.mouseCursors, - TrainHelper.TRAIN - ) - ); - } + // NPC Weddings and Anniversaries + if ((who.isEngaged() || who.isMarriedOrRoommates()) && who.friendshipData != null) { + foreach (var entry in who.friendshipData.Pairs) { + if (entry.Value == null || entry.Value.WeddingDate == null) + continue; - // Spring - if (date.SeasonIndex == 0) { + if (entry.Value.IsDivorced()) + continue; - } + WorldDate? wedding = entry.Value.WeddingDate; + if (wedding == null || wedding.SeasonIndex != date.SeasonIndex || wedding.DayOfMonth != date.DayOfMonth) + continue; - // Summer - else if (date.SeasonIndex == 1) { + NPC? spouse = Game1.getCharacterFromName(entry.Key); + if (spouse == null) + continue; - // Extra Foragables - if (gathering && date.DayOfMonth >= 12 && date.DayOfMonth <= 14) { - yield return new RichEvent( - date.DayOfMonth == 12 ? - I18n.Page_Notices_Summer() : null, - null, - SpriteHelper.GetSprite( - InventoryHelper.CreateItemById("(O)394", 1) // Rainbow Shell - ) - ); - } - } + char last = spouse.displayName.Last(); - // Fall - else if (date.SeasonIndex == 2) { + bool no_s = last == 's' || + LocalizedContentManager.CurrentLanguageCode == + LocalizedContentManager.LanguageCode.de && + (last == 'x' || last == 'ß' || last == 'z'); - if (gathering && date.DayOfMonth >= 15 && date.DayOfMonth <= 28) { - Item? nut = InventoryHelper.CreateItemById("(O)408", 1); + var pendant = ItemRegistry.Create("(O)460", 1); // Mermaid Pendant + var sprite = SpriteHelper.GetSprite(pendant); - // Should never be null but just in case. - if (nut != null) + // Wedding? + if (date.Year == wedding.Year) { yield return new RichEvent( null, - date.DayOfMonth == 15 ? FlowHelper.Translate( - Mod.Helper.Translation.Get("page.notices.season"), + Mod.Helper.Translation.Get( + no_s ? + "page.notices.wedding.no-s" + : "page.notices.wedding.s" + ), new { - item = nut.DisplayName, - start = new SDate(15, date.Season).ToLocaleString(withYear: false), - end = new SDate(28, date.Season).ToLocaleString(withYear: false), + name = who.displayName, + spouse = spouse.displayName }, align: Alignment.VCenter - ) : null, - SpriteHelper.GetSprite(nut), - nut + ), + sprite ); + } else { + yield return new RichEvent( + null, + FlowHelper.Translate( + Mod.Helper.Translation.Get( + no_s ? + "page.notices.anniversary.no-s" + : "page.notices.anniversary.s" + ), + new { + name = who.displayName, + spouse = spouse.displayName + }, + align: Alignment.VCenter + ), + sprite + ); + } } } + } - // Winter - else if (date.SeasonIndex == 3) { + // Trains + if (Mod.Config.NoticesShowTrains) { + int time = TrainHelper.GetTrainTime(date); + if (time >= 0) + yield return new RichEvent( + null, + FlowHelper.Translate( + Mod.Helper.Translation.Get("page.notices.train"), + new { + time = Mod.FormatTime(time) + }, + align: Alignment.VCenter + ), + new SpriteInfo( + Game1.mouseCursors, + TrainHelper.TRAIN + ) + ); + } + // Spring + if (date.SeasonIndex == 0) { + } + + // Summer + else if (date.SeasonIndex == 1) { + + // Extra Foragables + if (gathering && date.DayOfMonth >= 12 && date.DayOfMonth <= 14) { + yield return new RichEvent( + date.DayOfMonth == 12 ? + I18n.Page_Notices_Summer() : null, + null, + SpriteHelper.GetSprite( + ItemRegistry.Create("(O)394", 1) // Rainbow Shell + ) + ); } + } + // Fall + else if (date.SeasonIndex == 2) { - // Traveling Merchant - if (Mod.Config.NoticesShowMerchant != MerchantMode.Disabled && date.DayOfMonth % 7 % 5 == 0) { - - var sprite = new SpriteInfo( - Game1.mouseCursors, - new Rectangle(193, 1412, 18, 18) - ); + if (gathering && date.DayOfMonth >= 15 && date.DayOfMonth <= 28) { + Item? nut = ItemRegistry.Create("(O)408", 1); - if (Mod.Config.NoticesShowMerchant == MerchantMode.Visit) + // Should never be null but just in case. + if (nut != null) yield return new RichEvent( null, - FlowHelper.Builder() - .FormatText(I18n.Page_Notices_Merchant(), align: Alignment.VCenter) - .Build(), - sprite + date.DayOfMonth == 15 ? + FlowHelper.Translate( + Mod.Helper.Translation.Get("page.notices.season"), + new { + item = nut.DisplayName, + start = new SDate(15, date.Season).ToLocaleString(withYear: false), + end = new SDate(28, date.Season).ToLocaleString(withYear: false), + }, + align: Alignment.VCenter + ) : null, + SpriteHelper.GetSprite(nut), + nut ); + } + } + + // Winter + else if (date.SeasonIndex == 3) { - else { - var stock = ShopBuilder.GetShopStock(Game1.shop_travelingCart); - if (stock.Count > 0) { - var builder = FlowHelper.Builder() - .FormatText(I18n.Page_Notices_Merchant_Stock(), align: Alignment.VCenter) - .Text("\n "); - bool first = true; + } - foreach (var pair in stock) { - var item = pair.Key; - if (item.Stack < 1 && !item.IsInfiniteStock()) - continue; - if (first) - first = false; - else - builder.Text(", ", shadow: false); + // Traveling Merchant + if (Mod.Config.NoticesShowMerchant != MerchantMode.Disabled && date.DayOfMonth % 7 % 5 == 0) { - if (item is SObject sobj) - builder - .Sprite(SpriteHelper.GetSprite(sobj), scale: 2, align: Alignment.VCenter) - .Text(" "); + var sprite = new SpriteInfo( + Game1.mouseCursors, + new Rectangle(193, 1412, 18, 18) + ); - builder.Text(item.DisplayName, shadow: false); - } + if (Mod.Config.NoticesShowMerchant == MerchantMode.Visit) + yield return new RichEvent( + null, + FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Merchant(), align: Alignment.VCenter) + .Build(), + sprite + ); + else { + var stock = ShopBuilder.GetShopStock(Game1.shop_travelingCart); + if (stock.Count > 0) { + var builder = FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Merchant_Stock(), align: Alignment.VCenter) + .Text("\n "); - yield return new RichEvent( - null, - builder.Build(), - sprite: sprite - ); + bool first = true; + + foreach (var pair in stock) { + var item = pair.Key; + if (item.Stack < 1 && !item.IsInfiniteStock()) + continue; + + if (first) + first = false; + else + builder.Text(", ", shadow: false); + + if (item is SObject sobj) + builder + .Sprite(SpriteHelper.GetSprite(sobj), scale: 2, align: Alignment.VCenter) + .Text(" "); + + builder.Text(item.DisplayName, shadow: false); } - } - } - //Bookseller - if (Mod.Config.NoticesShowBookseller != MerchantMode.Disabled && IsBooksellerVisiting(date)) { - var sprite = new SpriteInfo( - Game1.mouseCursors_1_6, - new Rectangle(177, 488, 18, 24) - ); - if (Mod.Config.NoticesShowBookseller == MerchantMode.Visit) + yield return new RichEvent( null, - FlowHelper.Builder() - .FormatText(I18n.Page_Notices_Bookseller(), align: Alignment.VCenter) - .Build(), - sprite + builder.Build(), + sprite: sprite ); + } + } + } + //Bookseller + if (Mod.Config.NoticesShowBookseller != MerchantMode.Disabled && IsBooksellerVisiting(date)) { + var sprite = new SpriteInfo( + Game1.mouseCursors_1_6, + new Rectangle(177, 488, 18, 24) + ); - else { - var stock = ShopBuilder.GetShopStock(Game1.shop_bookseller); - if (stock.Count > 0) { - var builder = FlowHelper.Builder() - .FormatText(I18n.Page_Notices_Bookseller_Stock(), align: Alignment.VCenter) - .Text("\n "); - - bool first = true; + if (Mod.Config.NoticesShowBookseller == MerchantMode.Visit) + yield return new RichEvent( + null, + FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Bookseller(), align: Alignment.VCenter) + .Build(), + sprite + ); - foreach (var pair in stock) { - var item = pair.Key; - if (item.Stack < 1 && !item.IsInfiniteStock()) - continue; + else { + var stock = ShopBuilder.GetShopStock(Game1.shop_bookseller); + if (stock.Count > 0) { + var builder = FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Bookseller_Stock(), align: Alignment.VCenter) + .Text("\n "); - if (first) - first = false; - else - builder.Text(", ", shadow: false); + bool first = true; - if (item is SObject sobj) - builder - .Sprite(SpriteHelper.GetSprite(sobj,Game1.objectSpriteSheet_2), scale: 2, align: Alignment.VCenter) - .Text(" "); + foreach (var pair in stock) { + var item = pair.Key; + if (item.Stack < 1 && !item.IsInfiniteStock()) + continue; - builder.Text(item.DisplayName, shadow: false); - } + if (first) + first = false; + else + builder.Text(", ", shadow: false); + if (item is SObject sobj) + builder + //Keeping for texture reference .Sprite(SpriteHelper.GetSprite(sobj, Game1.objectSpriteSheet_2), scale: 2, align: Alignment.VCenter) + .Sprite(SpriteHelper.GetSprite(sobj), scale: 2, align: Alignment.VCenter) + .Text(" "); - yield return new RichEvent( - null, - builder.Build(), - sprite: sprite - ); + builder.Text(item.DisplayName, shadow: false); } + + + yield return new RichEvent( + null, + builder.Build(), + sprite: sprite + ); } } } - #endregion +} +#endregion - #region Utility methods - //Copied IsBloom from Bush.cs and switched calls for current season & day to passed values - public bool IsBlooming(Bush bush, StardewValley.Season season, int dayOfMonth) { - if (bush.size.Value == 4) { - return bush.tileSheetOffset.Value == 1; - } - if (bush.size.Value == 3) { - bool inBloom = bush.getAge() >= 20 && dayOfMonth >= 22 && (season != StardewValley.Season.Winter || bush.IsSheltered()); - if (inBloom && bush.Location != null && bush.Location.IsFarm) { - foreach (Farmer allFarmer in Game1.getAllFarmers()) { - allFarmer.autoGenerateActiveDialogueEvent("cropMatured_815"); - } +#region Utility methods +//Copied IsBloom from Bush.cs and switched calls for current season & day to passed values +public bool IsBlooming(Bush bush, StardewValley.Season season, int dayOfMonth) { + if (bush.size.Value == 4) { + return bush.tileSheetOffset.Value == 1; + } + if (bush.size.Value == 3) { + bool inBloom = bush.getAge() >= 20 && dayOfMonth >= 22 && (season != StardewValley.Season.Winter || bush.IsSheltered()); + if (inBloom && bush.Location != null && bush.Location.IsFarm) { + foreach (Farmer allFarmer in Game1.getAllFarmers()) { + allFarmer.autoGenerateActiveDialogueEvent("cropMatured_815"); } - return inBloom; - } - switch (season) { - case StardewValley.Season.Spring: - if (dayOfMonth > 14) { - return dayOfMonth < 19; - } - return false; - case StardewValley.Season.Fall: - if (dayOfMonth > 7) { - return dayOfMonth < 12; - } - return false; - default: - return false; } + return inBloom; } - //Copied from Utility.cs and switched calls for current date info to passed value - public static List GetDaysOfBooksellerThisSeason(WorldDate date) { - Random r = Utility.CreateRandom(date.Year * 11, Game1.uniqueIDForThisGame, date.SeasonIndex); - int[]? possible_days = null; - List days = new List(); - switch (date.Season) { - case StardewValley.Season.Spring: - possible_days = new int[5] { 11, 12, 21, 22, 25 }; - break; - case StardewValley.Season.Summer: - possible_days = new int[5] { 9, 12, 18, 25, 27 }; - break; - case StardewValley.Season.Fall: - possible_days = new int[8] { 4, 7, 8, 9, 12, 19, 22, 25 }; - break; - case StardewValley.Season.Winter: - possible_days = new int[6] { 5, 11, 12, 19, 22, 24 }; - break; - } - int index1 = r.Next(possible_days!.Length); - days.Add(possible_days[index1]); - days.Add(possible_days[(index1 + possible_days.Length / 2) % possible_days.Length]); - return days; + switch (season) { + case StardewValley.Season.Spring: + if (dayOfMonth > 14) { + return dayOfMonth < 19; + } + return false; + case StardewValley.Season.Fall: + if (dayOfMonth > 7) { + return dayOfMonth < 12; + } + return false; + default: + return false; } - public static bool IsBooksellerVisiting(WorldDate date) { - List visitDays = GetDaysOfBooksellerThisSeason(date); - foreach(int day in visitDays) if(day==date.DayOfMonth) return true; - return false; +} +//Copied from Utility.cs and switched calls for current date info to passed value +public static List GetDaysOfBooksellerThisSeason(WorldDate date) { + Random r = Utility.CreateRandom(date.Year * 11, Game1.uniqueIDForThisGame, date.SeasonIndex); + int[]? possible_days = null; + List days = new List(); + switch (date.Season) { + case StardewValley.Season.Spring: + possible_days = new int[5] { 11, 12, 21, 22, 25 }; + break; + case StardewValley.Season.Summer: + possible_days = new int[5] { 9, 12, 18, 25, 27 }; + break; + case StardewValley.Season.Fall: + possible_days = new int[8] { 4, 7, 8, 9, 12, 19, 22, 25 }; + break; + case StardewValley.Season.Winter: + possible_days = new int[6] { 5, 11, 12, 19, 22, 24 }; + break; } + int index1 = r.Next(possible_days!.Length); + days.Add(possible_days[index1]); + days.Add(possible_days[(index1 + possible_days.Length / 2) % possible_days.Length]); + return days; +} +public static bool IsBooksellerVisiting(WorldDate date) { + List visitDays = GetDaysOfBooksellerThisSeason(date); + foreach (int day in visitDays) if (day == date.DayOfMonth) return true; + return false; +} #endregion } -*/ + diff --git a/Almanac/ModAPI.cs b/Almanac/ModAPI.cs index 29d198b..e809728 100644 --- a/Almanac/ModAPI.cs +++ b/Almanac/ModAPI.cs @@ -25,9 +25,9 @@ public interface IAlmanacAPI { /// itself doesn't have the ability. /// int DaysPerMonth { get; } - /* - #region Custom Pages + #region Custom Pages + /* void RegisterPage( IManifest manifest, string id, @@ -75,9 +75,10 @@ void RegisterPage( ); void UnregisterPage(IManifest manifest, string id); - - #endregion */ + #endregion + + #region Crops Page @@ -175,7 +176,7 @@ IReadOnlyCollection phaseSprites #endregion - /* + #region Fortunes Page /// @@ -291,7 +292,7 @@ IReadOnlyCollection phaseSprites string GetWeatherForDate(WorldDate date, string context = "Default"); #endregion - */ + } public class ModAPI : IAlmanacAPI { @@ -517,7 +518,7 @@ public List GetSeasonCrops(string season) { } #endregion - /* + #region Fortune Telling public void SetFortuneHook(IManifest manifest, Func>> hook) { @@ -533,7 +534,7 @@ public void ClearFortuneHook(IManifest manifest) { } #endregion - + #region Local Notices public void SetNoticesHook(IManifest manifest, Func>> hook) { @@ -549,7 +550,7 @@ public void ClearNoticesHook(IManifest manifest) { } #endregion - + #region Weather public string GetWeatherForDate(WorldDate date, GameLocation location) { @@ -571,7 +572,7 @@ public string GetWeatherForDate(WorldDate date, GameLocation location) { public string GetWeatherForDate(WorldDate date, string context = "Default") { LocationContextData ctx; - switch(context) { + switch (context) { case "Default": ctx = Game1.locationContextData["Default"]; break; @@ -587,9 +588,10 @@ public string GetWeatherForDate(WorldDate date, string context = "Default") { string weather = Mod.Weather.GetWeatherForDate(Mod.GetBaseWorldSeed(), date, ctx, context); return weather; + } #endregion - */ + } } diff --git a/Almanac/ModEntry.cs b/Almanac/ModEntry.cs index e0c147f..b39500d 100644 --- a/Almanac/ModEntry.cs +++ b/Almanac/ModEntry.cs @@ -63,9 +63,9 @@ public class ModEntry : ModSubscriber { #nullable disable public ModConfig Config; - //public WeatherManager Weather; - //public LuckManager Luck; - //public NoticesManager Notices; + public WeatherManager Weather; + public LuckManager Luck; + public NoticesManager Notices; internal CropManager Crops; internal FishManager Fish; internal BookManager Books; @@ -113,9 +113,9 @@ public override void Entry(IModHelper helper) { Crops = new(this); Fish = new(this); - //Weather = new(this); - //Luck = new(this); - //Notices = new(this); + Weather = new(this); + Luck = new(this); + Notices = new(this); Books = new(this); ThemeManager = new(this, Config.Theme); @@ -125,14 +125,14 @@ public override void Entry(IModHelper helper) { // Init RegisterBuilder(CoverPage.GetPage); RegisterBuilder(CropPage.GetPage); - //RegisterBuilder(WeatherPage.GetPage); - //RegisterBuilder(WeatherPage.GetIslandPage); + RegisterBuilder(WeatherPage.GetPage); + RegisterBuilder(WeatherPage.GetIslandPage); RegisterBuilder(TrainPage.GetPage); - //RegisterBuilder(FortunePage.GetPage); + RegisterBuilder(FortunePage.GetPage); RegisterBuilder(MinesPage.GetPage); - //RegisterBuilder(NoticesPage.GetPage); + RegisterBuilder(NoticesPage.GetPage); RegisterBuilder(FishingPage.GetPage); - //RegisterBuilder(DebugItemsPage.GetPage); + RegisterBuilder(DebugItemsPage.GetPage); } public override object GetApi() { @@ -265,7 +265,7 @@ private void OnButton(object? sender, ButtonPressedEventArgs e) { [Subscriber] [EventPriority(EventPriority.High)] - /*private void OnDayStarted(object? sender, DayStartedEventArgs e) { + private void OnDayStarted(object? sender, DayStartedEventArgs e) { ulong seed = GetBaseWorldSeed(); if (Config.EnableDeterministicLuck && Game1.IsMasterGame) { @@ -290,15 +290,15 @@ private void OnButton(object? sender, ButtonPressedEventArgs e) { .GetWeatherForDate(seed, tomorrow, ctx, "Island"); } } - }*/ + } public void Invalidate() { Assets.Invalidate(); Crops.Invalidate(); Fish.Invalidate(); - //Luck.Invalidate(); - //Notices.Invalidate(); - //Weather.Invalidate(); + Luck.Invalidate(); + Notices.Invalidate(); + Weather.Invalidate(); } [Subscriber] @@ -344,7 +344,7 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { Log($" Query: {query}"); if (seed != -1) Log($" Seed: {seed}"); - //Log($"Result: {GameStateQuery.CheckConditions(query, rnd: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); + //Log($"Result: {GameStateQuery.CheckConditions(query, random: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); }); Helper.ConsoleCommands.Add("al_update", "Invalidate cached data.", (name, args) => { @@ -372,25 +372,25 @@ private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { } }); - Helper.ConsoleCommands.Add("al_now", "Print information about the in-game time.", (_, _) => { - Log($"Date: {Game1.Date.Localize()}", LogLevel.Info); - Log($"- Year: {Game1.year}", LogLevel.Info); - Log($"- Season: {Game1.currentSeason}", LogLevel.Info); - Log($"- DayOf: {Game1.dayOfMonth}", LogLevel.Info); - Log($"- TDays: {Game1.Date.TotalDays}", LogLevel.Info); - Log($"DaysPlayed: {Game1.stats.DaysPlayed}", LogLevel.Info); - }); - - /*Helper.ConsoleCommands.Add("al_forecast", "Get the forecast for the loaded save.", (name, args) => { - ulong seed = GetBaseWorldSeed(); - WorldDate date = new(Game1.Date); - for (int i = 0; i < 4 * 28; i++) { - string weather = Weather.GetWeatherForDate(seed, date, Game1.locationContextData["Default"], "Default"); - Log($"Date: {date.Localize()} -- Weather: {weather}"); - date.TotalDays++; - } - });*/ - } + Helper.ConsoleCommands.Add("al_now", "Print information about the in-game time.", (_, _) => { + Log($"Date: {Game1.Date.Localize()}", LogLevel.Info); + Log($"- Year: {Game1.year}", LogLevel.Info); + Log($"- Season: {Game1.currentSeason}", LogLevel.Info); + Log($"- DayOf: {Game1.dayOfMonth}", LogLevel.Info); + Log($"- TDays: {Game1.Date.TotalDays}", LogLevel.Info); + Log($"DaysPlayed: {Game1.stats.DaysPlayed}", LogLevel.Info); + }); + + Helper.ConsoleCommands.Add("al_forecast", "Get the forecast for the loaded save.", (name, args) => { + ulong seed = GetBaseWorldSeed(); + WorldDate date = new(Game1.Date); + for (int i = 0; i < 4 * 28; i++) { + string weather = Weather.GetWeatherForDate(seed, date, Game1.locationContextData["Default"], "Default"); + Log($"Date: {date.Localize()} -- Weather: {weather}"); + date.TotalDays++; + } + }); + } [Subscriber] private void OnMenuChanged(object sender, MenuChangedEventArgs e) { diff --git a/Almanac/Pages/FortunePage.cs b/Almanac/Pages/FortunePage.cs index 5032602..f885aab 100644 --- a/Almanac/Pages/FortunePage.cs +++ b/Almanac/Pages/FortunePage.cs @@ -1,4 +1,4 @@ -/* + #nullable enable using System; @@ -87,7 +87,7 @@ public override void Update() { double luck = Mod.Luck.GetModifiedLuckForDate(Seed, date); sprite = Sprites![day - 1] = Mod.Luck.GetLuckSprite(luck); Luck![day - 1] = luck; - } else + } else sprite = null; FlowBuilder db = new(); @@ -105,7 +105,7 @@ public override void Update() { if (has_line) { db.Text("\n"); if (evt.Item != null) - onHover = (_,_,_) => { + onHover = (_, _, _) => { Menu.HoveredItem = evt.Item; return true; }; @@ -208,7 +208,7 @@ public void DrawUnderCell(SpriteBatch b, WorldDate date, Rectangle bounds) { } public void DrawOverCell(SpriteBatch b, WorldDate date, Rectangle bounds) { - + } public bool ReceiveCellLeftClick(int x, int y, WorldDate date, Rectangle bounds) { @@ -228,7 +228,7 @@ public bool ReceiveCellRightClick(int x, int y, WorldDate date, Rectangle bounds } public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { - if (Luck == null || ! Luck[date.DayOfMonth - 1].HasValue) + if (Luck == null || !Luck[date.DayOfMonth - 1].HasValue) return; double luck = Luck[date.DayOfMonth - 1]!.Value; @@ -236,11 +236,11 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { Menu.HoverMagic = true; Menu.HoverText = Mod.Config.ShowExactLuck - ? $"{fortune} ({(luck*100):F1}%)" + ? $"{fortune} ({(luck * 100):F1}%)" : fortune; } #endregion } -*/ + diff --git a/Almanac/Pages/NoticesPage.cs b/Almanac/Pages/NoticesPage.cs index 5c70ff4..edc20e2 100644 --- a/Almanac/Pages/NoticesPage.cs +++ b/Almanac/Pages/NoticesPage.cs @@ -1,4 +1,4 @@ -/* + #nullable enable using System; @@ -38,7 +38,7 @@ public class NoticesPage : BasePage, ICalendarPage { #region Lifecycle public static NoticesPage? GetPage(AlmanacMenu menu, ModEntry mod) { - if (! mod.Config.ShowNotices) + if (!mod.Config.ShowNotices) return null; return new(menu, mod); @@ -68,7 +68,7 @@ public void LoadOverrides() { Texture2D texture; try { texture = Game1.content.Load(@"Characters\" + npc.getTextureName()); - } catch(Exception) { + } catch (Exception) { texture = npc.Sprite.Texture; } @@ -148,7 +148,7 @@ public override void Update() { List? chars = null; try { chars = Utility.getAllCharacters(); - } catch(Exception ex) { + } catch (Exception ex) { Mod.Log($"Unable to load list of characters: {ex}", StardewModdingAPI.LogLevel.Error); builder .Text("\n\n") @@ -188,11 +188,11 @@ public override void Update() { date.DayOfMonth = day; List sprites = new(); - foreach(var evt in Mod.Notices.GetEventsForDate(0, date)) { + foreach (var evt in Mod.Notices.GetEventsForDate(0, date)) { if (evt == null) continue; - bool has_simple = ! string.IsNullOrEmpty(evt.SimpleLabel); + bool has_simple = !string.IsNullOrEmpty(evt.SimpleLabel); bool has_line = has_simple || evt.AdvancedLabel != null; Func? onHover = null; @@ -200,7 +200,7 @@ public override void Update() { if (has_line) { db.Text("\n"); if (evt.Item != null) - onHover = (_,_,_) => { + onHover = (_, _, _) => { Menu.HoveredItem = evt.Item; return true; }; @@ -340,7 +340,7 @@ public void DrawUnderCell(SpriteBatch b, WorldDate date, Rectangle bounds) { } else { int to_show = Math.Min(sprites.Count, bdays == null ? 3 : 1); - int idx = (int) (ms / Mod.Config.CycleTime) % (int)Math.Ceiling(sprites.Count / (float) to_show) * to_show; + int idx = (int) (ms / Mod.Config.CycleTime) % (int) Math.Ceiling(sprites.Count / (float) to_show) * to_show; for (int i = 0; i < to_show; i++) { if (i + idx >= sprites.Count) @@ -377,7 +377,7 @@ public void DrawUnderCell(SpriteBatch b, WorldDate date, Rectangle bounds) { } public void DrawOverCell(SpriteBatch b, WorldDate date, Rectangle bounds) { - + } public bool ReceiveCellLeftClick(int x, int y, WorldDate date, Rectangle bounds) { @@ -412,4 +412,4 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { #endregion } -*/ + diff --git a/Almanac/Pages/WeatherPage.cs b/Almanac/Pages/WeatherPage.cs index 42d7ca5..9bcda12 100644 --- a/Almanac/Pages/WeatherPage.cs +++ b/Almanac/Pages/WeatherPage.cs @@ -1,4 +1,4 @@ -/* + #nullable enable using System; @@ -24,11 +24,11 @@ public class WeatherPage : BasePage, ICalendarPage { public static readonly Rectangle WEATHER_ICON = new(384, 352, 16, 16); - private readonly ulong Seed; - private IFlowNode[] Nodes; - private string[] Forecast; - private bool[] Festivals; - private bool[] Pirates; + private readonly ulong Seed; + private IFlowNode[] Nodes; + private string[] Forecast; + private bool[] Festivals; + private bool[] Pirates; readonly bool IsIsland; readonly bool IsDesert; @@ -113,13 +113,13 @@ public override void Update() { for (int day = 1; day <= ModEntry.DaysPerMonth; day++) { date.DayOfMonth = day; bool shown = forecastLength == -1 || date.TotalDays - today <= forecastLength; - string weather = Forecast[day - 1] = shown ? Mod.Weather.GetWeatherForDate(Seed, date, context, contextID): ""; + string weather = Forecast[day - 1] = shown ? Mod.Weather.GetWeatherForDate(Seed, date, context, contextID) : ""; if (IsIsland) { - bool pirates = Pirates![day - 1] = shown && day % 2 == 0 && ! WeatherHelper.IsRainOrSnow(weather); + bool pirates = Pirates![day - 1] = shown && day % 2 == 0 && !WeatherHelper.IsRainOrSnow(weather); if (pirates) pirateDays!.Add(day); - } else if ( Utility.isFestivalDay(day, date.Season)) { + } else if (Utility.isFestivalDay(day, date.Season)) { SDate sdate = new(day, date.Season); var data = Game1.temporaryContent.Load>("Data\\Festivals\\" + date.Season + day); @@ -143,7 +143,7 @@ public override void Update() { } } - foreach(GameLocation loc in Game1.locations) { + foreach (GameLocation loc in Game1.locations) { if (loc?.Name == where) { where = Mod.GetLocationName(loc); break; @@ -156,7 +156,7 @@ public override void Update() { font: Game1.dialogueFont, shadow: true ), - onClick: (_,_,_) => false + onClick: (_, _, _) => false ); Nodes[day - 1] = node; @@ -215,7 +215,7 @@ public override void Update() { } builder.Text("\n\n"); - } + }*/ if (pirateDays!.Count > 0) { string dates = string.Join(", ", pirateDays); @@ -334,4 +334,4 @@ public void PerformCellHover(int x, int y, WorldDate date, Rectangle bounds) { #endregion } -*/ + diff --git a/Almanac/Patches/Game1_Patches.cs b/Almanac/Patches/Game1_Patches.cs index 55308c6..0e32cac 100644 --- a/Almanac/Patches/Game1_Patches.cs +++ b/Almanac/Patches/Game1_Patches.cs @@ -29,7 +29,7 @@ internal static void Patch(ModEntry mod) { mod.Log("An error occurred while registering a harmony patch for Game1.", LogLevel.Error, ex); }*/ } - /* + public static void UpdateWeatherForNewDay_Postfix() { try { ModEntry.Instance.Weather.UpdateForNewDay(); @@ -39,5 +39,4 @@ public static void UpdateWeatherForNewDay_Postfix() { Monitor.Log($"Details:\n{ex}", LogLevel.Error); } } - */ } diff --git a/Almanac/manifest.json b/Almanac/manifest.json index dbeae3f..18a3320 100644 --- a/Almanac/manifest.json +++ b/Almanac/manifest.json @@ -10,10 +10,10 @@ "UniqueID": "leclair.almanac", "Name": "Almanac", "Author": "Khloe Leclair", - "Version": "0.18.1", + "Version": "0.18.1-Debug", "Description": "Adds an Almanac for the player, with forecasts, planting dates, and other calendar information useful for a farmer.", "MinimumApiVersion": "4.1.0", - "MinimumGameVersion": "1.6.9", + "MinimumGameVersion": "1.6.15", "EntryDll": "Almanac.dll", "Dependencies": [ { @@ -40,4 +40,4 @@ "UpdateKeys": [ "Nexus:11022@Main" ] -} +} \ No newline at end of file From 6b310076d0c09dc6f44cb5b47e128006080d53d5 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:28:48 -0500 Subject: [PATCH 35/39] Delete old BCBuildings files To match KhloeLeclair/StardewMods main --- BCBuildings/BPRecipe.cs | 156 --------------------- BCBuildings/IBCBuildingsApi.cs | 71 ---------- BCBuildings/ModApi.cs | 64 --------- BCBuildings/Properties/launchSettings.json | 8 -- 4 files changed, 299 deletions(-) delete mode 100644 BCBuildings/BPRecipe.cs delete mode 100644 BCBuildings/IBCBuildingsApi.cs delete mode 100644 BCBuildings/ModApi.cs delete mode 100644 BCBuildings/Properties/launchSettings.json diff --git a/BCBuildings/BPRecipe.cs b/BCBuildings/BPRecipe.cs deleted file mode 100644 index fde7501..0000000 --- a/BCBuildings/BPRecipe.cs +++ /dev/null @@ -1,156 +0,0 @@ -#nullable enable - -using System.Collections.Generic; - -using Leclair.Stardew.Common; -using Leclair.Stardew.BetterCrafting; - -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -using StardewValley; -using StardewValley.Locations; - -namespace Leclair.Stardew.BCBuildings; - -public class BPRecipe : IRecipe { - - public readonly ModEntry Mod; - public readonly BluePrint Blueprint; - - public readonly string? RawQuery; - public readonly GameStateQuery.ParsedQuery? Query; - - public BPRecipe(BluePrint blueprint, ModEntry mod) { - Mod = mod; - Name = $"blueprint:{blueprint.name}"; - Blueprint = blueprint; - - if (mod.ApiBlueprints.TryGetValue(blueprint.name, out var data)) - RawQuery = data.Item3; - - if (!string.IsNullOrEmpty(RawQuery)) - Query = GameStateQuery.ParseConditions(RawQuery); - - List ingredients = new(); - int amount; - - if (blueprint.itemsRequired != null) - foreach (var entry in blueprint.itemsRequired) { - amount = (int) (entry.Value * (mod.Config.CostMaterial / 100.0)); - if ( amount > 0 ) - ingredients.Add(mod.BCAPI!.CreateBaseIngredient(entry.Key, amount)); - } - - amount = (int) (blueprint.moneyRequired * (mod.Config.CostCurrency / 100.0)); - if (amount > 0) - ingredients.Add(mod.BCAPI!.CreateCurrencyIngredient(CurrencyType.Money, amount)); - - IEnumerable? additional = Mod.GetAdditionalCost(); - if (additional is not null) - ingredients.AddRange(additional); - - Ingredients = ingredients.ToArray(); - - if (mod.BuildingSources.TryGetValue(blueprint.name, out Rectangle? source)) { - SourceRectangle = (source.HasValue && !source.Value.IsEmpty) ? source.Value : Blueprint.texture.Bounds; - } else - SourceRectangle = Blueprint.sourceRectForMenuView; - } - - // Identity - - public int SortValue { get; } - public string Name { get; } - public string DisplayName => Blueprint.displayName; - public string Description => Blueprint.description; - - public virtual bool HasRecipe(Farmer who) { - return true; - } - - public virtual int GetTimesCrafted(Farmer who) { - return 0; - } - - public CraftingRecipe? CraftingRecipe => null; - - // Display - - public Texture2D Texture => Blueprint.texture; - public Rectangle SourceRectangle { get; } - - public int GridHeight { - get { - Rectangle rect = SourceRectangle; - if (rect.Height > rect.Width) - return 2; - return 1; - } - } - - public int GridWidth { - get { - Rectangle rect = SourceRectangle; - if (rect.Width > rect.Height) - return 2; - return 1; - } - } - - // Cost - - public int QuantityPerCraft => 1; - public IIngredient[] Ingredients { get; } - - // Creation - - public bool Stackable => false; - - public Item? CreateItem() { - return null; - } - - public bool CanCraft(Farmer who) { - if (who.currentLocation is not BuildableGameLocation bgl) - return false; - - if (Blueprint.isUpgrade()) { - return bgl.isBuildingConstructed(Blueprint.nameOfBuildingToUpgrade); - } - - if (Query.HasValue && !Query.Value.Evaluate()) - return false; - - return true; - } - - public string? GetTooltipExtra(Farmer who) { - if (who.currentLocation is not BuildableGameLocation bgl) - return I18n.Error_NotBuildable(); - - if (Blueprint.isUpgrade() && !bgl.isBuildingConstructed(Blueprint.nameOfBuildingToUpgrade)) { - string other = new BluePrint(Blueprint.nameOfBuildingToUpgrade).displayName; - return I18n.Error_CantUpgrade(other); - } - - if (Query.HasValue && !Query.Value.Evaluate()) - return I18n.Error_UnmetCondition(); - - return null; - } - - public void PerformCraft(IPerformCraftEvent evt) { - - var menu = new BuildMenu(Blueprint, Blueprint.isUpgrade() ? ActionType.Upgrade : ActionType.Build, evt, Mod); - var old_menu = Game1.activeClickableMenu; - - Game1.activeClickableMenu = menu; - - menu.exitFunction = () => { - Game1.activeClickableMenu = old_menu; - evt.Cancel(); - }; - } - -} diff --git a/BCBuildings/IBCBuildingsApi.cs b/BCBuildings/IBCBuildingsApi.cs deleted file mode 100644 index df8ee53..0000000 --- a/BCBuildings/IBCBuildingsApi.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.Xna.Framework; - -using System; -using System.Collections.Generic; - -using StardewValley; - -namespace Leclair.Stardew.BCBuildings; - -public interface IBlueprintPopulationEventArgs { - Dictionary Blueprints { get; } -} - -public interface IBCBuildingsApi { - - /// - /// Add a blueprint to the BC: Buildings menu, by name. - /// - /// The name of the blueprint to add - /// An optional game state query to control - /// visibility of the blueprint in the menu. - /// - /// An optional game state query to control - /// whether or not players can construct a blueprint. If the condition is - /// set and doesn't pass, the building will be grayed out and unavailable - /// to construct, with a message displayed to the user that a condition - /// has not been fulfilled. - void AddBlueprint(string name, string? displayCondition, string? buildCondition); - - /// - /// Add a blueprint to the BC: Buildings menu. - /// - /// The blueprint to add - /// An optional game state query to control - /// visibility of the blueprint in the menu. - /// - /// An optional game state query to control - /// whether or not players can construct a blueprint. If the condition is - /// set and doesn't pass, the building will be grayed out and unavailable - /// to construct, with a message displayed to the user that a condition - /// has not been fulfilled. - void AddBlueprint(BluePrint blueprint, string? displayCondition, string? buildCondition); - - /// - /// Remove a blueprint from the BC: Buildings menu, by name. - /// - /// The name of the blueprint to remove - void RemoveBlueprint(string name); - - /// - /// Remove a blueprint from the BC: Buildings menu. - /// - /// The blueprint to remove - void RemoveBlueprint(BluePrint blueprint); - - /// - /// Override the source rectangle used for drawing the building in the - /// BC: Buildings menu. - /// - /// The name of the blueprint to change - /// The rectangle of the texture to use - void SetTextureSourceRect(string name, Rectangle? rect); - - /// - /// This event is emitted when discovering blueprints for display in the - /// menu. Additional logic to control the visibility of a blueprint can - /// be performed here, assuming game state queries aren't enough. - /// - event EventHandler? OnBlueprintPopulation; - -} diff --git a/BCBuildings/ModApi.cs b/BCBuildings/ModApi.cs deleted file mode 100644 index 680596b..0000000 --- a/BCBuildings/ModApi.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; - -using Microsoft.Xna.Framework; - -using StardewValley; - -namespace Leclair.Stardew.BCBuildings; - -public class BlueprintPopulationEventArgs: EventArgs, IBlueprintPopulationEventArgs { - - public Dictionary Blueprints { get; } - - internal BlueprintPopulationEventArgs(Dictionary blueprints) { - Blueprints = blueprints; - } -} - -public class ModApi : IBCBuildingsApi { - - private readonly ModEntry Mod; - - internal ModApi(ModEntry mod) { - Mod = mod; - } - - public void AddBlueprint(string name, string? displayCondition, string? buildCondition) { - AddBlueprint(new BluePrint(name), displayCondition, buildCondition); - } - - public void AddBlueprint(BluePrint blueprint, string? displayCondition, string? buildCondition) { - Mod.ApiBlueprints[blueprint.name] = (blueprint, displayCondition, buildCondition); - } - - public void RemoveBlueprint(string name) { - if (Mod.ApiBlueprints.ContainsKey(name)) - Mod.ApiBlueprints.Remove(name); - } - - public void RemoveBlueprint(BluePrint blueprint) { - RemoveBlueprint(blueprint.name); - } - - public void SetTextureSourceRect(string name, Rectangle? rect) { - if (string.IsNullOrEmpty(name)) - return; - - if (rect.HasValue) - Mod.BuildingSources[name] = rect; - else if (Mod.BuildingSources.ContainsKey(name)) - Mod.BuildingSources.Remove(name); - } - - public event EventHandler? OnBlueprintPopulation; - - internal void EmitBlueprintPopulation(Dictionary blueprints) { - if (OnBlueprintPopulation is null) - return; - - var evt = new BlueprintPopulationEventArgs(blueprints); - OnBlueprintPopulation.Invoke(this, evt); - } - -} diff --git a/BCBuildings/Properties/launchSettings.json b/BCBuildings/Properties/launchSettings.json deleted file mode 100644 index 1321e0f..0000000 --- a/BCBuildings/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "BCBuildings": { - "commandName": "Project", - "commandLineArgs": "--mods-path \"$(ModSet)\"" - } - } -} \ No newline at end of file From fe97ce758be421118badfb0e5e353b1689cb87de Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:31:04 -0500 Subject: [PATCH 36/39] Delete old SeeMeRollin files To match KhloeLeclair/StardewMods main --- SeeMeRollin/Patches/BuffPatches.cs | 65 ------------------------------ SeeMeRollin/RollinBuff.cs | 17 -------- 2 files changed, 82 deletions(-) delete mode 100644 SeeMeRollin/Patches/BuffPatches.cs delete mode 100644 SeeMeRollin/RollinBuff.cs diff --git a/SeeMeRollin/Patches/BuffPatches.cs b/SeeMeRollin/Patches/BuffPatches.cs deleted file mode 100644 index 2624676..0000000 --- a/SeeMeRollin/Patches/BuffPatches.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; - -using HarmonyLib; - -using Microsoft.Xna.Framework; - -using StardewModdingAPI; - -using StardewValley; -using StardewValley.Menus; - -namespace Leclair.Stardew.SeeMeRollin.Patches { - public class BuffPatches { - - public static IMonitor Monitor => ModEntry.instance.Monitor; - - [HarmonyPatch(typeof(Buff), nameof(Buff.update))] - public static class Buff_Update { - static bool Prefix(Buff __instance, GameTime time, ref bool __result) { - if (__instance is RollinBuff) { - __result = false; - return false; - } - - return true; - } - } - - [HarmonyPatch(typeof(Buff), nameof(Buff.getTimeLeft))] - public static class Buff_TimeLeft { - static bool Prefix(Buff __instance, ref string __result) { - if (__instance is RollinBuff) { - __result = ""; - return false; - } - - return true; - } - } - [HarmonyPatch(typeof(Buff), nameof(Buff.getClickableComponents))] - public static class Buff_Components { - static bool Prefix(Buff __instance, ref List __result) { - if (__instance is RollinBuff&&!ModEntry.instance.Config.ShowBuff) { - __result = new(); - return false; - } - - return true; - } - } - - [HarmonyPatch(typeof(Buff), nameof(Buff.getClickableComponents))] - public static class Buff_Components { - static bool Prefix(Buff __instance, ref List __result) { - if (__instance is RollinBuff && !ModEntry.instance.Config.ShowBuff) { - __result = new(); - return false; - } - - return true; - } - } - - } -} diff --git a/SeeMeRollin/RollinBuff.cs b/SeeMeRollin/RollinBuff.cs deleted file mode 100644 index b11a85b..0000000 --- a/SeeMeRollin/RollinBuff.cs +++ /dev/null @@ -1,17 +0,0 @@ - -using StardewValley; - -namespace Leclair.Stardew.SeeMeRollin { - public class RollinBuff : Buff { - - public int Speed; - - public RollinBuff(int speed) : base(I18n.Buff_Name(), 1000, null, 9) { - which - ModEntry.BUFF; - - Speed = speed; - buffAttributes[Buff.speed] = Speed; - } - - } -} From 99085c225927ecb177116b189282b29620abf594 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:31:59 -0500 Subject: [PATCH 37/39] Delete old GiantCropTweaks files To match KhloeLeclair/StardewMods main --- GiantCropTweaks/Properties/launchSettings.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 GiantCropTweaks/Properties/launchSettings.json diff --git a/GiantCropTweaks/Properties/launchSettings.json b/GiantCropTweaks/Properties/launchSettings.json deleted file mode 100644 index 91134e3..0000000 --- a/GiantCropTweaks/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "GiantCropTweaks": { - "commandName": "Project", - "commandLineArgs": "--mods-path \"$(ModSet)\"" - } - } -} \ No newline at end of file From 1042f95e48ec008d136eacb9060a92a91692b9b2 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:34:49 -0500 Subject: [PATCH 38/39] Delete old BetterCrafting files To match KhloeLeclair/StardewMods main --- .../DynamicGameAssets/DGAIntegration.cs | 38 ---- .../IDynamicGameAssetsApi.cs | 60 ------ .../Integrations/SpaceCore/DGAIngredient.cs | 197 ------------------ BetterCrafting/Patches/FarmHousePatches.cs | 80 ------- 4 files changed, 375 deletions(-) delete mode 100644 BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs delete mode 100644 BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs delete mode 100644 BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs delete mode 100644 BetterCrafting/Patches/FarmHousePatches.cs diff --git a/BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs b/BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs deleted file mode 100644 index 82f7555..0000000 --- a/BetterCrafting/Integrations/DynamicGameAssets/DGAIntegration.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Leclair.Stardew.Common.Integrations; - -using DynamicGameAssets; -using StardewValley; - -namespace Leclair.Stardew.BetterCrafting.Integrations.DynamicGameAssets; - -public class DGAIntegration : BaseAPIIntegration { - - public DGAIntegration(ModEntry mod) - : base(mod, "spacechase0.DynamicGameAssets", "1.4.4") { - - } - - public IEnumerable GetAllItems() { - if (!IsLoaded) - yield break; - - foreach(string id in API.GetAllItems()) { - Item? item = null; - try { - item = API.SpawnDGAItem(id) as Item; - } catch { - /* no-op */ - } - - if (item is not null) - yield return item; - } - } - -} diff --git a/BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs b/BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs deleted file mode 100644 index b02e041..0000000 --- a/BetterCrafting/Integrations/DynamicGameAssets/IDynamicGameAssetsApi.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.Xna.Framework; - -using StardewModdingAPI; - -namespace DynamicGameAssets { - public interface IDynamicGameAssetsApi { - - /// - /// Get the DGA item ID of this item, if it has one. - /// - /// The item to get the DGA item ID of. - /// The DGA item ID if it has one, otherwise null. - string GetDGAItemId(object item); - - /// - /// Spawn a DGA item, referenced with its full ID ("mod.id/ItemId"). - /// Some items, such as crafting recipes or crops, don't have an item representation. - /// - /// The full ID of the item to spawn. - /// The color of the item. - /// - object SpawnDGAItem(string fullId, Color? color); - - /// - /// Spawn a DGA item, referenced with its full ID ("mod.id/ItemId"). - /// Some items, such as crafting recipes or crops, don't have an item representation. - /// - /// The full ID of the item to spawn. - /// - object SpawnDGAItem(string fullId); - - /// - /// Gets the names of all installed packs. - /// - /// Array of all pack names. - string[] ListContentPacks(); - - /// - /// Gets all items provided by a pack. - /// - /// The name of the pack. - /// Namespaced item names. - string[]? GetItemsByPack(string packname); - - /// - /// Gets all the items (namespaced names) - /// - /// A list of all items. - string[] GetAllItems(); - - /// - /// Register a DGA pack embedded in another mod. - /// Needs the standard DGA fields in the manifest. (See documentation.) - /// Probably shouldn't use config-schema.json for these, because if you do it will overwrite your mod's config.json. - /// - /// The mod manifest. - /// The absolute path to the directory of the pack. - void AddEmbeddedPack(IManifest manifest, string dir); - } -} diff --git a/BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs b/BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs deleted file mode 100644 index 134e8d8..0000000 --- a/BetterCrafting/Integrations/SpaceCore/DGAIngredient.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -using HarmonyLib; - -using Leclair.Stardew.Common; -using Leclair.Stardew.Common.Crafting; -using Leclair.Stardew.Common.Inventory; -using Leclair.Stardew.Common.Types; - -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -using StardewModdingAPI; - -using StardewValley; - -namespace Leclair.Stardew.BetterCrafting.Integrations.SpaceCore; - -public class DGAIngredient : IOptimizedIngredient, IRecyclable { - - private readonly ModEntry Mod; - public readonly object Ingredient; - public readonly IItemAbstraction? ItemAbstraction; - - public readonly string? IngredientType; - - public readonly Func ItemMatcher; - - private readonly IReflectedProperty DisplayNameProp; - private readonly IReflectedProperty TextureProp; - private readonly IReflectedProperty SourceProp; - private readonly IReflectedProperty QuantityProp; - private readonly IReflectedMethod MatchesMethod; - - public DGAIngredient(object ingredient, ModEntry mod, IItemAbstraction? itemAbstraction) { - Mod = mod; - Ingredient = ingredient; - ItemAbstraction = itemAbstraction; - - DisplayNameProp = Mod.Helper.Reflection.GetProperty(Ingredient, "DispayName"); - TextureProp = Mod.Helper.Reflection.GetProperty(Ingredient, "IconTexture"); - SourceProp = Mod.Helper.Reflection.GetProperty(Ingredient, "IconSubrect"); - QuantityProp = Mod.Helper.Reflection.GetProperty(Ingredient, "Quantity"); - - MatchesMethod = Mod.Helper.Reflection.GetMethod(Ingredient, "ItemMatches"); - - ItemMatcher = item => MatchesMethod.Invoke(item); - - // Ensure we can do stuff. - DisplayNameProp.GetValue(); - TextureProp.GetValue(); - SourceProp.GetValue(); - QuantityProp.GetValue(); - - RecycledSprite = new(item => SpriteHelper.GetSprite(item), () => RecycledItem?.Item2); - - // Try to get the matcher type. - try { - object? abstraction = mod.Helper.Reflection.GetField(Ingredient, "ingred", true).GetValue(); - if (abstraction is not null) { - var prop = AccessTools.Property(abstraction.GetType(), "Type"); - IngredientType = prop.GetValue(abstraction)?.ToString(); - } else - IngredientType = null; - } catch { - IngredientType = null; - } - } - - #region IRecyclable - - private Tuple? RecycledItem; - private readonly Cache RecycledSprite; - - [MemberNotNull(nameof(RecycledItem))] - private void LoadRecycledItem() { - if (RecycledItem is not null) - return; - - // TODO: Make this better. - switch(IngredientType) { - case "DGAItem": - case "VanillaObject": - case "VanillaObjectColored": - case "VanillaBigCraftable": - case "VanillaWeapon": - case "VanillaHat": - case "VanillaClothing": - case "VanillaBoots": - case "VanillaFurniture": - // TODO: Check for vanilla categories? - RecycledItem = new(false, ItemAbstraction?.Create()); - return; - - case "DGARecipe": - // Recipes are not supported. - RecycledItem = new(false, null); - return; - } - - // If we got here, we're something odd. But probably just a context - // tag match. Either way, we're fuzzy. - - Item? result = null; - int price = 0; - int count = 0; - - foreach (Item item in ModEntry.Instance.GetMatchingItems(ItemMatcher)) { - int ip = item.salePrice(); - count++; - if (result is null || ip < price) { - result = item; - price = ip; - } - } - - ModEntry.Instance.Log($"Item matches for \"{DisplayName}\": {count} -- Using: {result?.Name}", LogLevel.Trace); - - RecycledItem = new(true, result); - } - - public Texture2D GetRecycleTexture(Farmer who, Item? recycledItem, bool fuzzyItems) { - LoadRecycledItem(); - if (!fuzzyItems && RecycledItem.Item1) - return Texture; - return RecycledSprite.Value?.Texture ?? Texture; - } - - public Rectangle GetRecycleSourceRect(Farmer who, Item? recycledItem, bool fuzzyItems) { - LoadRecycledItem(); - if (!fuzzyItems && RecycledItem.Item1) - return SourceRectangle; - return RecycledSprite.Value?.BaseSource ?? SourceRectangle; - } - - public string GetRecycleDisplayName(Farmer who, Item? recycledItem, bool fuzzyItems) { - LoadRecycledItem(); - if (!fuzzyItems && RecycledItem.Item1) - return DisplayName; - return RecycledItem.Item2?.DisplayName ?? DisplayName; - } - - public int GetRecycleQuantity(Farmer who, Item? recycledItem, bool fuzzyItems) { - return Quantity; - } - - public bool CanRecycle(Farmer who, Item? recycledItem, bool fuzzyItems) { - LoadRecycledItem(); - if (!fuzzyItems && RecycledItem.Item1) - return false; - return RecycledItem.Item2 is not null; - } - - public IEnumerable? Recycle(Farmer who, Item? recycledItem, bool fuzzyItems) { - LoadRecycledItem(); - if (!fuzzyItems && RecycledItem.Item1) - return null; - - if (RecycledItem.Item2 is not null) - return IRecyclable.GetManyOf(RecycledItem.Item2, Quantity); - - return null; - } - - #endregion - - #region IIngredient - - public bool SupportsQuality => true; - - public string DisplayName => DisplayNameProp.GetValue(); - - public Texture2D Texture => TextureProp.GetValue(); - - public Rectangle SourceRectangle => SourceProp.GetValue() ?? Texture.Bounds; - - public int Quantity => QuantityProp.GetValue(); - - public int GetAvailableQuantity(Farmer who, IList? items, IList? inventories, int maxQuality) { - return InventoryHelper.CountItem(ItemMatcher, who, items, out bool _, max_quality: maxQuality); - } - - public bool HasAvailableQuantity(int quantity, Farmer who, IList? items, IList? inventories, int maxQuality) { - return InventoryHelper.CountItem(ItemMatcher, who, items, out bool _, max_quality: maxQuality, limit: quantity) >= quantity; - } - - public void Consume(Farmer who, IList? inventories, int maxQuality, bool lowQualityFirst) { - InventoryHelper.ConsumeItems(new (Func, int)[] { - (ItemMatcher, Quantity) - }, who, inventories, maxQuality, lowQualityFirst); - } - - #endregion - -} diff --git a/BetterCrafting/Patches/FarmHousePatches.cs b/BetterCrafting/Patches/FarmHousePatches.cs deleted file mode 100644 index 641538e..0000000 --- a/BetterCrafting/Patches/FarmHousePatches.cs +++ /dev/null @@ -1,80 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; - -using HarmonyLib; - -using Microsoft.Xna.Framework; - -using StardewModdingAPI; - -using StardewValley; -using StardewValley.Locations; -using StardewValley.Objects; - -using xTile.Dimensions; - -namespace Leclair.Stardew.BetterCrafting.Patches; - -public static class GameLocation_Patches { - - private static IMonitor? Monitor; - - public static void Patch(ModEntry mod) { - Monitor = mod.Monitor; - - try { - mod.Harmony!.Patch( - original: AccessTools.Method(typeof(FarmHouse), nameof(FarmHouse.performAction)), - prefix: new HarmonyMethod(typeof(GameLocation_Patches), nameof(performAction_Prefix)) - ); - - mod.Harmony!.Patch( - original: AccessTools.Method(typeof(FarmHouse), nameof(IslandFarmHouse.performAction)), - prefix: new HarmonyMethod(typeof(GameLocation_Patches), nameof(performAction_Prefix)) - ); - - mod.Harmony!.Patch( - original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.performAction)), - prefix: new HarmonyMethod(typeof(GameLocation_Patches), nameof(performAction_Prefix)) - ); - - } catch (Exception ex) { - mod.Log("An error occurred while registering a harmony patch for the FarmHouse.", LogLevel.Warn, ex); - } - } - - public static bool performAction_Prefix(GameLocation __instance, string action, Farmer who, Location tileLocation, ref bool __result) { - try { - if (action is not null && who.IsLocalPlayer && action.Split(' ')[0] == "kitchen") { - // It's the kitchen! Handle it ourselves. - ModEntry mod = ModEntry.Instance; - if (mod.Config.ReplaceCrafting && !(mod.Config.SuppressBC?.IsDown() ?? false)) { - - // TODO: Build containers list! - - Game1.activeClickableMenu = Menus.BetterCraftingPage.Open( - mod, - location: __instance, - position: new Vector2(tileLocation.X, tileLocation.Y), - standalone_menu: true, - cooking: true, - material_containers: (IList?) null - ); - - __result = true; - return false; - } - } - - } catch(Exception ex) { - Monitor?.Log($"An error occurred while attempting to perform a location action.", LogLevel.Warn); - Monitor?.Log($"Details:\n{ex}", LogLevel.Warn); - } - - return true; - } - - -} From aa96af7e547c4e10143511d82bf61ad9d87e5c94 Mon Sep 17 00:00:00 2001 From: Cat Vindeflei <33209145+Vindeflei@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:37:59 -0500 Subject: [PATCH 39/39] Update BetterCrafting/../GardenPotRecipe To match KhloeLeclair/StardewMods main --- .../Integrations/RaisedGardenBeds/GardenPotRecipe.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs b/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs index 37a781e..6a1552f 100644 --- a/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs +++ b/BetterCrafting/Integrations/RaisedGardenBeds/GardenPotRecipe.cs @@ -39,7 +39,7 @@ public GardenPotRecipe(RGBIntegration rgb, CraftingRecipe recipe) { // Identity - public int SortValue => Recipe.itemToProduce[0]; + public string SortValue => Recipe.itemToProduce[0]; // Display @@ -47,6 +47,8 @@ public GardenPotRecipe(RGBIntegration rgb, CraftingRecipe recipe) { public string DisplayName { get; } public string Description { get; } + public bool AllowRecycling => true; + public virtual bool HasRecipe(Farmer who) { return who.craftingRecipes.ContainsKey(Name); }

p6EVGpZ!*Fe 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 Js}!w1{y*AslI{Qi literal 117133 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�uv{8N|Lumq6mwPBSH8l$L&hb;+S3xpA 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