diff --git a/Almanac/Almanac.csproj b/Almanac/Almanac.csproj index 46c5492..c1365fb 100644 --- a/Almanac/Almanac.csproj +++ b/Almanac/Almanac.csproj @@ -4,11 +4,16 @@ Almanac 0.18.1 Leclair.Stardew.Almanac - true true + net6.0 + Crafting;BCInventory;Inventory;GMCM;Mutex;Overlay;UI;SimpleLayout;Flow;SpookyAction;ThemeManager - - + + + + + + diff --git a/Almanac/AssetManager.cs b/Almanac/AssetManager.cs index 10b63aa..9591d15 100644 --- a/Almanac/AssetManager.cs +++ b/Almanac/AssetManager.cs @@ -146,18 +146,18 @@ private void Load(string? locale) { 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; 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..7df9c89 100644 --- a/Almanac/Managers/LuckManager.cs +++ b/Almanac/Managers/LuckManager.cs @@ -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 @@ -535,10 +525,11 @@ public IEnumerable GetEventsForDate(ulong seed, WorldDate date) { I18n.Page_Fortune_Event_Ufo(), 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,12 +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 d2c9b8e..d5e0ad5 100644 --- a/Almanac/Managers/NoticesManager.cs +++ b/Almanac/Managers/NoticesManager.cs @@ -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; @@ -109,699 +110,689 @@ private Dictionary LoadEvents() { } [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; - } - - #endregion - - #region Mod Management + DataEvents = Mod.Helper.GameContent.Load>(AssetManager.LocalNoticesPath); + Loaded = true; +} - public void ClearHook(IManifest mod) { - if (InterfaceHooks.ContainsKey(mod)) - InterfaceHooks.Remove(mod); +#endregion - if (ModHooks.ContainsKey(mod)) - ModHooks.Remove(mod); - } +#region Mod Management - public void RegisterHook(IManifest mod, Func>> hook) { - if (InterfaceHooks.ContainsKey(mod)) - InterfaceHooks.Remove(mod); +public void ClearHook(IManifest mod) { + if (InterfaceHooks.ContainsKey(mod)) + InterfaceHooks.Remove(mod); - if (hook == null && ModHooks.ContainsKey(mod)) - ModHooks.Remove(mod); - else if (hook != null) - ModHooks[mod] = hook; - } + if (ModHooks.ContainsKey(mod)) + ModHooks.Remove(mod); +} - public void RegisterHook(IManifest mod, Func> hook) { - if (ModHooks.ContainsKey(mod)) - ModHooks.Remove(mod); +public void RegisterHook(IManifest mod, Func>> hook) { + if (InterfaceHooks.ContainsKey(mod)) + InterfaceHooks.Remove(mod); - if (hook == null && InterfaceHooks.ContainsKey(mod)) - InterfaceHooks.Remove(mod); - else if (hook != null) - InterfaceHooks[mod] = hook; - } + if (hook == null && ModHooks.ContainsKey(mod)) + ModHooks.Remove(mod); + else if (hook != null) + ModHooks[mod] = hook; +} - #endregion +public void RegisterHook(IManifest mod, Func> hook) { + if (ModHooks.ContainsKey(mod)) + ModHooks.Remove(mod); - #region Events + if (hook == null && InterfaceHooks.ContainsKey(mod)) + InterfaceHooks.Remove(mod); + else if (hook != null) + InterfaceHooks[mod] = hook; +} - public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, Common.GameStateQuery.GameState state, string? key = null) { - if (notice == null) - return null; +#endregion - // Year Validation - if (notice.FirstYear > date.Year || notice.LastYear < date.Year) - return null; +#region Events - if (notice.ValidYears != null && !notice.ValidYears.Contains(date.Year)) - return null; +public IRichEvent? HydrateEvent(LocalNotice notice, WorldDate date, string? key = null) { + if (notice == null) + 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; + // Year Validation + if (notice.FirstYear > date.Year || notice.LastYear < date.Year) + 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; - } + if (notice.ValidYears != null && !notice.ValidYears.Contains(date.Year)) + return 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; - } - } + // Season Validation + if (notice.ValidSeasons != null && !notice.ValidSeasons.Contains(Common.Enums.Season.All) && !notice.ValidSeasons.Contains((Common.Enums.Season) date.SeasonIndex)) + return null; - if (!matched) - return null; - } + // Date Range Validation + int day; - // Condition Validation - if (!string.IsNullOrEmpty(notice.Condition) && !Common.GameStateQuery.CheckConditions(notice.Condition, state)) - return null; + bool first = true; - // 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.IconType == NoticeIconType.Item) { + sprite = item == null ? null : SpriteHelper.GetSprite(item); - if (notice.Translation != null && ! string.IsNullOrEmpty(notice.I18nKey)) - notice.Description = notice.Translation.Get(notice.I18nKey).ToString(); + } 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; - 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})"; + sprite = tex == null ? null : new SpriteInfo( + tex, + notice.IconSourceRect ?? tex.Bounds + ); - return new RichEvent( - (first || notice.ShowEveryDay) ? desc : null, - null, - sprite, - item + } 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; - - 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 - ); - } - } - } - } + // Player Weddings and Anniversaries + // TODO: This. - // 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 ); + } + } - 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 "); + // Winter + else if (date.SeasonIndex == 3) { - 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(" "); + // Traveling Merchant + if (Mod.Config.NoticesShowMerchant != MerchantMode.Disabled && date.DayOfMonth % 7 % 5 == 0) { - builder.Text(item.DisplayName, shadow: false); - } + var sprite = new SpriteInfo( + Game1.mouseCursors, + new Rectangle(193, 1412, 18, 18) + ); + if (Mod.Config.NoticesShowMerchant == MerchantMode.Visit) + yield return new RichEvent( + null, + FlowHelper.Builder() + .FormatText(I18n.Page_Notices_Merchant(), align: Alignment.VCenter) + .Build(), + sprite + ); - yield return new RichEvent( - null, - builder.Build(), - sprite: 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 "); + + 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/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..e809728 100644 --- a/Almanac/ModAPI.cs +++ b/Almanac/ModAPI.cs @@ -27,8 +27,8 @@ public interface IAlmanacAPI { int DaysPerMonth { get; } #region Custom Pages - - /*void RegisterPage( + /* + void RegisterPage( IManifest manifest, string id, // State @@ -74,9 +74,11 @@ 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 +176,7 @@ IReadOnlyCollection phaseSprites #endregion + #region Fortunes Page /// @@ -289,6 +292,7 @@ IReadOnlyCollection phaseSprites string GetWeatherForDate(WorldDate date, string context = "Default"); #endregion + } public class ModAPI : IAlmanacAPI { @@ -568,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; @@ -584,6 +588,7 @@ 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 c2ab690..b39500d 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; @@ -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); @@ -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, random: rnd, item: Game1.player.CurrentItem, monitor: Monitor, trace: true)}"); }); Helper.ConsoleCommands.Add("al_update", "Invalidate cached data.", (name, args) => { @@ -371,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) { @@ -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..f885aab 100644 --- a/Almanac/Pages/FortunePage.cs +++ b/Almanac/Pages/FortunePage.cs @@ -1,3 +1,4 @@ + #nullable enable using System; @@ -104,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; }; @@ -207,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) { @@ -227,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; @@ -235,10 +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/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..edc20e2 100644 --- a/Almanac/Pages/NoticesPage.cs +++ b/Almanac/Pages/NoticesPage.cs @@ -1,3 +1,4 @@ + #nullable enable using System; @@ -37,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); @@ -67,7 +68,7 @@ public void LoadOverrides() { Texture2D texture; try { texture = Game1.content.Load(@"Characters\" + npc.getTextureName()); - } catch(Exception) { + } catch (Exception) { texture = npc.Sprite.Texture; } @@ -147,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") @@ -187,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; @@ -199,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; }; @@ -339,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) @@ -376,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) { @@ -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..9bcda12 100644 --- a/Almanac/Pages/WeatherPage.cs +++ b/Almanac/Pages/WeatherPage.cs @@ -1,3 +1,4 @@ + #nullable enable using System; @@ -23,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; @@ -112,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); @@ -142,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; @@ -155,7 +156,7 @@ public override void Update() { font: Game1.dialogueFont, shadow: true ), - onClick: (_,_,_) => false + onClick: (_, _, _) => false ); Nodes[day - 1] = node; @@ -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..0e32cac 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(); 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..18a3320 100644 --- a/Almanac/manifest.json +++ b/Almanac/manifest.json @@ -12,7 +12,8 @@ "Author": "Khloe Leclair", "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": "3.17.0", + "MinimumApiVersion": "4.1.0", + "MinimumGameVersion": "1.6.15", "EntryDll": "Almanac.dll", "Dependencies": [ {