Compare commits
24 Commits
c3cec1a490
...
cdb262a26a
Author | SHA1 | Date |
---|---|---|
erion | cdb262a26a | |
erion | 6128e2dd72 | |
Mohammad Shoaib Khan | 0e1f9d0cab | |
Katie Durden | 803bc64868 | |
Katie Durden | dacbff92ea | |
Katie Durden | 529a110617 | |
Katie Durden | ed405a139e | |
Katie Durden | ad0cb687b7 | |
Katie Durden | 91643e184f | |
Katie Durden | 8509cbfc0b | |
Katie Durden | 35115223d3 | |
Katie Durden | a7dfeb5526 | |
Katie Durden | 34c182ddc6 | |
Katie Durden | 245959366c | |
Katie Durden | 5d8f85fa5b | |
Katie Durden | dd812851b4 | |
Katie Durden | 4b2e31fadc | |
Katie Durden | 541fd42133 | |
Katie Durden | a88172c0cf | |
Katie Durden | e9ba7c96b9 | |
Katie Durden | 32db14b329 | |
Katie Durden | 1c61580d6b | |
Mohammad Shoaib Khan | 7bb4819601 | |
Mohammad Shoaib Khan | ce6f67a0dc |
|
@ -61,3 +61,9 @@
|
||||||
#*.PDF diff=astextplain
|
#*.PDF diff=astextplain
|
||||||
#*.rtf diff=astextplain
|
#*.rtf diff=astextplain
|
||||||
#*.RTF diff=astextplain
|
#*.RTF diff=astextplain
|
||||||
|
|
||||||
|
|
||||||
|
# Keep line endings consistent for files.
|
||||||
|
*.cs eol=crlf
|
||||||
|
*.json eol=crlf
|
||||||
|
LICENSE* eol=crlf
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace stardew_access.ScreenReader
|
||||||
/// <returns>Name of the object as the first item (name) and category as the second item (category). Returns null if no object found.</returns>
|
/// <returns>Name of the object as the first item (name) and category as the second item (category). Returns null if no object found.</returns>
|
||||||
public (string? name, string? category) GetNameWithCategoryNameAtTile(Vector2 tile)
|
public (string? name, string? category) GetNameWithCategoryNameAtTile(Vector2 tile)
|
||||||
{
|
{
|
||||||
return TileInfo.getNameWithCategoryNameAtTile(tile);
|
return TileInfo.getNameWithCategoryNameAtTile(tile, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -65,7 +65,7 @@ namespace stardew_access.ScreenReader
|
||||||
/// <returns>Name of the object. Returns null if no object found.</returns>
|
/// <returns>Name of the object. Returns null if no object found.</returns>
|
||||||
public string? GetNameAtTile(Vector2 tile)
|
public string? GetNameAtTile(Vector2 tile)
|
||||||
{
|
{
|
||||||
return TileInfo.getNameAtTile(tile);
|
return TileInfo.GetNameAtTile(tile, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Speaks the text via the loaded screen reader (if any).</summary>
|
/// <summary>Speaks the text via the loaded screen reader (if any).</summary>
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace stardew_access
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#region Read Tile
|
#region Read Tile
|
||||||
helper.ConsoleCommands.Add("readtile", "Toggle read tile feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("readtile", "Toggle read tile feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.ReadTile = !MainClass.Config.ReadTile;
|
MainClass.Config.ReadTile = !MainClass.Config.ReadTile;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -26,7 +26,7 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Read Tile is " + (MainClass.Config.ReadTile ? "on" : "off"));
|
MainClass.InfoLog("Read Tile is " + (MainClass.Config.ReadTile ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("flooring", "Toggle flooring in read tile.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("flooring", "Toggle flooring in read tile.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.ReadFlooring = !MainClass.Config.ReadFlooring;
|
MainClass.Config.ReadFlooring = !MainClass.Config.ReadFlooring;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -34,7 +34,7 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Flooring is " + (MainClass.Config.ReadFlooring ? "on" : "off"));
|
MainClass.InfoLog("Flooring is " + (MainClass.Config.ReadFlooring ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("watered", "Toggle speaking watered or unwatered for crops.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("watered", "Toggle speaking watered or unwatered for crops.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.WateredToggle = !MainClass.Config.WateredToggle;
|
MainClass.Config.WateredToggle = !MainClass.Config.WateredToggle;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -44,7 +44,7 @@ namespace stardew_access
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Radar Feature
|
#region Radar Feature
|
||||||
helper.ConsoleCommands.Add("radar", "Toggle radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("radar", "Toggle radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.Radar = !MainClass.Config.Radar;
|
MainClass.Config.Radar = !MainClass.Config.Radar;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -52,14 +52,14 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Radar " + (MainClass.Config.Radar ? "on" : "off"));
|
MainClass.InfoLog("Radar " + (MainClass.Config.Radar ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rdebug", "Toggle debugging in radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rdebug", "Toggle debugging in radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.radarDebug = !MainClass.radarDebug;
|
MainClass.radarDebug = !MainClass.radarDebug;
|
||||||
|
|
||||||
MainClass.InfoLog("Radar debugging " + (MainClass.radarDebug ? "on" : "off"));
|
MainClass.InfoLog("Radar debugging " + (MainClass.radarDebug ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rstereo", "Toggle stereo sound in radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rstereo", "Toggle stereo sound in radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.RadarStereoSound = !MainClass.Config.RadarStereoSound;
|
MainClass.Config.RadarStereoSound = !MainClass.Config.RadarStereoSound;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -67,14 +67,14 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Stereo sound is " + (MainClass.Config.RadarStereoSound ? "on" : "off"));
|
MainClass.InfoLog("Stereo sound is " + (MainClass.Config.RadarStereoSound ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rfocus", "Toggle focus mode in radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rfocus", "Toggle focus mode in radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
bool focus = MainClass.RadarFeature.ToggleFocus();
|
bool focus = MainClass.RadarFeature.ToggleFocus();
|
||||||
|
|
||||||
MainClass.InfoLog("Focus mode is " + (focus ? "on" : "off"));
|
MainClass.InfoLog("Focus mode is " + (focus ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rdelay", "Set the delay of radar feature in milliseconds.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rdelay", "Set the delay of radar feature in milliseconds.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string? delayInString = null;
|
string? delayInString = null;
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ namespace stardew_access
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rrange", "Set the range of radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rrange", "Set the range of radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string? rangeInString = null;
|
string? rangeInString = null;
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ namespace stardew_access
|
||||||
|
|
||||||
|
|
||||||
#region Exclusions
|
#region Exclusions
|
||||||
helper.ConsoleCommands.Add("readd", "Add an object key to the exclusions list of radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("readd", "Add an object key to the exclusions list of radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string? keyToAdd = null;
|
string? keyToAdd = null;
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("reremove", "Remove an object key from the exclusions list of radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("reremove", "Remove an object key from the exclusions list of radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string? keyToAdd = null;
|
string? keyToAdd = null;
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("relist", "List all the exclusions in the radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("relist", "List all the exclusions in the radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
if (MainClass.RadarFeature.exclusions.Count > 0)
|
if (MainClass.RadarFeature.exclusions.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -209,20 +209,20 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("reclear", "Clear the focus exclusions in the radar featrure.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("reclear", "Clear the focus exclusions in the radar featrure.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.RadarFeature.exclusions.Clear();
|
MainClass.RadarFeature.exclusions.Clear();
|
||||||
MainClass.InfoLog($"Cleared the focus list in the exclusions feature.");
|
MainClass.InfoLog($"Cleared the focus list in the exclusions feature.");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("recount", "Number of exclusions in the radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("recount", "Number of exclusions in the radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.InfoLog($"There are {MainClass.RadarFeature.exclusions.Count} exclusiond in the radar feature.");
|
MainClass.InfoLog($"There are {MainClass.RadarFeature.exclusions.Count} exclusiond in the radar feature.");
|
||||||
});
|
});
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Focus
|
#region Focus
|
||||||
helper.ConsoleCommands.Add("rfadd", "Add an object key to the focus list of radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rfadd", "Add an object key to the focus list of radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string? keyToAdd = null;
|
string? keyToAdd = null;
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rfremove", "Remove an object key from the focus list of radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rfremove", "Remove an object key from the focus list of radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string? keyToAdd = null;
|
string? keyToAdd = null;
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rflist", "List all the exclusions in the radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rflist", "List all the exclusions in the radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
if (MainClass.RadarFeature.focus.Count > 0)
|
if (MainClass.RadarFeature.focus.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -289,13 +289,13 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rfclear", "Clear the focus list in the radar featrure.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rfclear", "Clear the focus list in the radar featrure.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.RadarFeature.focus.Clear();
|
MainClass.RadarFeature.focus.Clear();
|
||||||
MainClass.InfoLog($"Cleared the focus list in the radar feature.");
|
MainClass.InfoLog($"Cleared the focus list in the radar feature.");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("rfcount", "Number of list in the radar feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("rfcount", "Number of list in the radar feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.InfoLog($"There are {MainClass.RadarFeature.focus.Count} objects in the focus list in the radar feature.");
|
MainClass.InfoLog($"There are {MainClass.RadarFeature.focus.Count} objects in the focus list in the radar feature.");
|
||||||
});
|
});
|
||||||
|
@ -304,7 +304,7 @@ namespace stardew_access
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tile marking
|
#region Tile marking
|
||||||
helper.ConsoleCommands.Add("mark", "Marks the player's position for use in building construction in Carpenter Menu.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("mark", "Marks the player's position for use in building construction in Carpenter Menu.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
if (Game1.currentLocation is not Farm)
|
if (Game1.currentLocation is not Farm)
|
||||||
{
|
{
|
||||||
|
@ -332,7 +332,7 @@ namespace stardew_access
|
||||||
MainClass.InfoLog($"Location {(int)Game1.player.getTileX()}x {(int)Game1.player.getTileY()}y added at {index} index.");
|
MainClass.InfoLog($"Location {(int)Game1.player.getTileX()}x {(int)Game1.player.getTileY()}y added at {index} index.");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
string toPrint = "";
|
string toPrint = "";
|
||||||
for (int i = 0; i < BuildingOperations.marked.Length; i++)
|
for (int i = 0; i < BuildingOperations.marked.Length; i++)
|
||||||
|
@ -349,12 +349,12 @@ namespace stardew_access
|
||||||
MainClass.InfoLog($"Marked positions:{toPrint}\nOpen command menu and use pageup and pagedown to check the list");
|
MainClass.InfoLog($"Marked positions:{toPrint}\nOpen command menu and use pageup and pagedown to check the list");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("buildlist", "List all buildings for selection for upgrading/demolishing/painting", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("buildlist", "List all buildings for selection for upgrading/demolishing/painting", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
onBuildListCalled();
|
onBuildListCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("buildsel", "Select the building index which you want to upgrade/demolish/paint", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("buildsel", "Select the building index which you want to upgrade/demolish/paint", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu && Game1.activeClickableMenu is not AnimalQueryMenu) || !CarpenterMenuPatch.isOnFarm)
|
if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu && Game1.activeClickableMenu is not AnimalQueryMenu) || !CarpenterMenuPatch.isOnFarm)
|
||||||
{
|
{
|
||||||
|
@ -451,28 +451,29 @@ namespace stardew_access
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Other
|
#region Other
|
||||||
helper.ConsoleCommands.Add("refsr", "Refresh screen reader", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("refsr", "Refresh screen reader", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.ScreenReader.InitializeScreenReader();
|
MainClass.ScreenReader.InitializeScreenReader();
|
||||||
|
|
||||||
MainClass.InfoLog("Screen Reader refreshed!");
|
MainClass.InfoLog("Screen Reader refreshed!");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("refmc", "Refresh mod config", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("refmc", "Refresh mod config", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config = helper.ReadConfig<ModConfig>();
|
MainClass.Config = helper.ReadConfig<ModConfig>();
|
||||||
|
|
||||||
MainClass.InfoLog("Mod Config refreshed!");
|
MainClass.InfoLog("Mod Config refreshed!");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("refst", "Refresh static tiles", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("refst", "Refresh static tiles", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.STiles = new Features.StaticTiles();
|
StaticTiles.LoadTilesFiles();
|
||||||
|
StaticTiles.SetupTilesDicts();
|
||||||
|
|
||||||
MainClass.InfoLog("Static tiles refreshed!");
|
MainClass.InfoLog("Static tiles refreshed!");
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("hnspercent", "Toggle between speaking in percentage or full health and stamina.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("hnspercent", "Toggle between speaking in percentage or full health and stamina.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.HealthNStaminaInPercentage = !MainClass.Config.HealthNStaminaInPercentage;
|
MainClass.Config.HealthNStaminaInPercentage = !MainClass.Config.HealthNStaminaInPercentage;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -480,7 +481,7 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Speaking in percentage is " + (MainClass.Config.HealthNStaminaInPercentage ? "on" : "off"));
|
MainClass.InfoLog("Speaking in percentage is " + (MainClass.Config.HealthNStaminaInPercentage ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("snapmouse", "Toggle snap mouse feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("snapmouse", "Toggle snap mouse feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.SnapMouse = !MainClass.Config.SnapMouse;
|
MainClass.Config.SnapMouse = !MainClass.Config.SnapMouse;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -488,7 +489,7 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Snap Mouse is " + (MainClass.Config.SnapMouse ? "on" : "off"));
|
MainClass.InfoLog("Snap Mouse is " + (MainClass.Config.SnapMouse ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("warning", "Toggle warnings feature.", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("warning", "Toggle warnings feature.", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.Warning = !MainClass.Config.Warning;
|
MainClass.Config.Warning = !MainClass.Config.Warning;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
@ -496,7 +497,7 @@ namespace stardew_access
|
||||||
MainClass.InfoLog("Warnings is " + (MainClass.Config.Warning ? "on" : "off"));
|
MainClass.InfoLog("Warnings is " + (MainClass.Config.Warning ? "on" : "off"));
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.ConsoleCommands.Add("tts", "Toggles the screen reader/tts", (string commmand, string[] args) =>
|
helper.ConsoleCommands.Add("tts", "Toggles the screen reader/tts", (string command, string[] args) =>
|
||||||
{
|
{
|
||||||
MainClass.Config.TTS = !MainClass.Config.TTS;
|
MainClass.Config.TTS = !MainClass.Config.TTS;
|
||||||
helper.WriteConfig(MainClass.Config);
|
helper.WriteConfig(MainClass.Config);
|
||||||
|
|
|
@ -0,0 +1,812 @@
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Netcode;
|
||||||
|
using StardewValley;
|
||||||
|
using StardewValley.Buildings;
|
||||||
|
using StardewValley.Locations;
|
||||||
|
using StardewValley.Objects;
|
||||||
|
using StardewValley.TerrainFeatures;
|
||||||
|
using static stardew_access.Features.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace stardew_access.Features
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods to locate tiles of interest in various game locations that are conditional or unpredictable (I.E. not static).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The DynamicTiles class currently supports the following location types:
|
||||||
|
/// - Beach
|
||||||
|
/// - BoatTunnel
|
||||||
|
/// - CommunityCenter
|
||||||
|
/// - Farm
|
||||||
|
/// - FarmHouse
|
||||||
|
/// - Forest
|
||||||
|
/// - IslandFarmHouse
|
||||||
|
/// - IslandLocation
|
||||||
|
/// - LibraryMuseum
|
||||||
|
/// - Town
|
||||||
|
///
|
||||||
|
/// And the following Island LocationTypes:
|
||||||
|
/// - IslandNorth
|
||||||
|
/// - IslandWest
|
||||||
|
/// - VolcanoDungeon
|
||||||
|
///
|
||||||
|
/// The class also supports the following named locations:
|
||||||
|
/// - Barn (and its upgraded versions)
|
||||||
|
/// - Coop (and its upgraded versions)
|
||||||
|
///
|
||||||
|
/// The class does not yet support the following location types, but consider adding support in future updates:
|
||||||
|
/// - AbandonedJojaMart
|
||||||
|
/// - AdventureGuild
|
||||||
|
/// - BathHousePool
|
||||||
|
/// - BeachNightMarket
|
||||||
|
/// - BugLand
|
||||||
|
/// - BusStop
|
||||||
|
/// - Caldera
|
||||||
|
/// - Cellar
|
||||||
|
/// - Club
|
||||||
|
/// - Desert
|
||||||
|
/// - FarmCave
|
||||||
|
/// - FishShop
|
||||||
|
/// - JojaMart
|
||||||
|
/// - ManorHouse
|
||||||
|
/// - MermaidHouse
|
||||||
|
/// - Mine
|
||||||
|
/// - Mountain
|
||||||
|
/// - MovieTheater
|
||||||
|
/// - Railroad
|
||||||
|
/// - SeedShop
|
||||||
|
/// - Sewer
|
||||||
|
/// - Submarine
|
||||||
|
/// - Summit
|
||||||
|
/// - WizardHouse
|
||||||
|
/// - Woods
|
||||||
|
///
|
||||||
|
/// The class does not yet support the following named locations, but consider adding support in future updates:
|
||||||
|
/// - "AnimalShop"
|
||||||
|
/// - "Backwoods"
|
||||||
|
/// - "BathHouse_Entry"
|
||||||
|
/// - "BathHouse_MensLocker"
|
||||||
|
/// - "BathHouse_WomensLocker"
|
||||||
|
/// - "Blacksmith"
|
||||||
|
/// - "ElliottHouse"
|
||||||
|
/// - "FarmGreenHouse"
|
||||||
|
/// - "Greenhouse"
|
||||||
|
/// - "HaleyHouse"
|
||||||
|
/// - "HarveyRoom"
|
||||||
|
/// - "Hospital"
|
||||||
|
/// - "JoshHouse"
|
||||||
|
/// - "LeahHouse"
|
||||||
|
/// - "LeoTreeHouse"
|
||||||
|
/// - "Saloon"
|
||||||
|
/// - "SamHouse"
|
||||||
|
/// - "SandyHouse"
|
||||||
|
/// - "ScienceHouse"
|
||||||
|
/// - "SebastianRoom"
|
||||||
|
/// - "SkullCave"
|
||||||
|
/// - "Sunroom"
|
||||||
|
/// - "Tent"
|
||||||
|
/// - "Trailer"
|
||||||
|
/// - "Trailer_Big"
|
||||||
|
/// - "Tunnel"
|
||||||
|
/// - "WitchHut"
|
||||||
|
/// - "WitchSwamp"
|
||||||
|
/// - "WitchWarpCave"
|
||||||
|
/// - "WizardHouseBasement"
|
||||||
|
///
|
||||||
|
/// The class does not yet support the following IslandLocation location types, but consider adding support in future updates:
|
||||||
|
/// - IslandEast
|
||||||
|
/// - IslandFarmCave
|
||||||
|
/// - IslandFieldOffice
|
||||||
|
/// - IslandHut
|
||||||
|
/// - IslandShrine
|
||||||
|
/// - IslandSouth
|
||||||
|
/// - IslandSouthEast
|
||||||
|
/// - IslandSouthEastCave
|
||||||
|
/// - IslandWestCave1
|
||||||
|
///
|
||||||
|
/// The class does not yet support the following IslandLocation named locations, but consider adding support in future updates:
|
||||||
|
/// - "CaptainRoom"
|
||||||
|
/// - "IslandNorthCave1"
|
||||||
|
/// - "QiNutRoom"
|
||||||
|
/// </remarks>
|
||||||
|
public class DynamicTiles
|
||||||
|
{
|
||||||
|
// Static instance for the singleton pattern
|
||||||
|
private static DynamicTiles? _instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The singleton instance of the <see cref="DynamicTiles"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public static DynamicTiles Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_instance ??= new DynamicTiles();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashSet for storing which unimplemented locations have been previously logged
|
||||||
|
private static readonly HashSet<object> loggedLocations = new();
|
||||||
|
|
||||||
|
// Dictionary of coordinates for feeding benches in barns and coops
|
||||||
|
private static readonly Dictionary<string, (int minX, int maxX, int y)> FeedingBenchBounds = new()
|
||||||
|
{
|
||||||
|
{ "Barn", (8, 11, 3) },
|
||||||
|
{ "Barn2", (8, 15, 3) },
|
||||||
|
{ "Big Barn", (8, 15, 3) },
|
||||||
|
{ "Barn3", (8, 19, 3) },
|
||||||
|
{ "Deluxe Barn", (8, 19, 3) },
|
||||||
|
{ "Coop", (6, 9, 3) },
|
||||||
|
{ "Coop2", (6, 13, 3) },
|
||||||
|
{ "Big Coop", (6, 13, 3) },
|
||||||
|
{ "Coop3", (6, 17, 3) },
|
||||||
|
{ "Deluxe Coop", (6, 17, 3) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dictionary to hold event info
|
||||||
|
private static readonly Dictionary<string, Dictionary<(int X, int Y), string>> EventInteractables;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DynamicTiles"/> class.
|
||||||
|
/// Loads the event file.
|
||||||
|
/// </summary>
|
||||||
|
static DynamicTiles()
|
||||||
|
{
|
||||||
|
EventInteractables = LoadEventTiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads event tiles from the "event-tiles.json" file and returns a dictionary representation of the data.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A dictionary with event names as keys and nested dictionaries as values, where nested dictionaries have
|
||||||
|
/// coordinate tuples (x, y) as keys and tile names as values.
|
||||||
|
/// </returns>
|
||||||
|
private static Dictionary<string, Dictionary<(int x, int y), string>> LoadEventTiles()
|
||||||
|
{
|
||||||
|
JsonElement json = LoadJsonFile("event-tiles.json");
|
||||||
|
|
||||||
|
if (json.ValueKind == JsonValueKind.Undefined)
|
||||||
|
{
|
||||||
|
// If the JSON couldn't be loaded or parsed, return an empty dictionary
|
||||||
|
return new Dictionary<string, Dictionary<(int x, int y), string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventTiles = new Dictionary<string, Dictionary<(int x, int y), string>>();
|
||||||
|
|
||||||
|
// Iterate over the JSON properties to create a dictionary representation of the data
|
||||||
|
foreach (JsonProperty eventProperty in json.EnumerateObject())
|
||||||
|
{
|
||||||
|
string eventName = eventProperty.Name;
|
||||||
|
var coordinates = new Dictionary<(int x, int y), string>();
|
||||||
|
|
||||||
|
// Iterate over the coordinate properties to create a nested dictionary with coordinate tuples as keys
|
||||||
|
foreach (JsonProperty coordinateProperty in eventProperty.Value.EnumerateObject())
|
||||||
|
{
|
||||||
|
string[] xy = coordinateProperty.Name.Split(',');
|
||||||
|
int x = int.Parse(xy[0]);
|
||||||
|
int y = int.Parse(xy[1]);
|
||||||
|
coordinates.Add((x, y), value: coordinateProperty.Value.GetString() ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventTiles.Add(eventName, coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventTiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in a Beach.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beach">The Beach to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetBeachInfo(Beach beach, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (MainClass.ModHelper == null)
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
if (MainClass.ModHelper.Reflection.GetField<NPC>(beach, "oldMariner").GetValue() is NPC mariner && mariner.getTileLocation() == new Vector2(x, y))
|
||||||
|
{
|
||||||
|
return ("Old Mariner", CATEGORY.NPCs);
|
||||||
|
}
|
||||||
|
else if (x == 58 && y == 13)
|
||||||
|
{
|
||||||
|
if (!beach.bridgeFixed.Value)
|
||||||
|
{
|
||||||
|
return ("Repair Bridge", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ("Bridge", CATEGORY.Bridges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables or other features at a given coordinate in a BoatTunnel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="boatTunnel">The BoatTunnel to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetBoatTunnelInfo(BoatTunnel boatTunnel, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
// Check if the player has received the specified mail or not
|
||||||
|
bool HasMail(string mail) => Game1.MasterPlayer.hasOrWillReceiveMail(mail);
|
||||||
|
|
||||||
|
// If the position matches one of the interactable elements in the boat tunnel
|
||||||
|
if ((x, y) == (4, 9) || (x, y) == (6, 8) || (x, y) == (8, 9))
|
||||||
|
{
|
||||||
|
string mail = (x, y) switch
|
||||||
|
{
|
||||||
|
(4, 9) => "willyBoatFixed",
|
||||||
|
(6, 8) => "willyBoatHull",
|
||||||
|
(8, 9) => "willyBoatAnchor",
|
||||||
|
_ => throw new InvalidOperationException("Unexpected (x, y) values"),
|
||||||
|
};
|
||||||
|
|
||||||
|
string itemName = (x, y) switch
|
||||||
|
{
|
||||||
|
(4, 9) => "Ticket Machine",
|
||||||
|
(6, 8) => "Boat Hull",
|
||||||
|
(8, 9) => "Boat Anchor",
|
||||||
|
_ => throw new InvalidOperationException("Unexpected (x, y) values"),
|
||||||
|
};
|
||||||
|
|
||||||
|
CATEGORY category = (x, y) == (4, 9) ? CATEGORY.Interactables : (!HasMail(mail) ? CATEGORY.Interactables : CATEGORY.Decor);
|
||||||
|
|
||||||
|
return ((!HasMail(mail) ? "Repair " : "") + itemName, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables or other features at a given coordinate in a CommunityCenter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="communityCenter">The CommunityCenter to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetCommunityCenterInfo(CommunityCenter communityCenter, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (communityCenter.missedRewardsChestVisible.Value && x == 22 && y == 10)
|
||||||
|
{
|
||||||
|
return ("Missed Rewards Chest", CATEGORY.Containers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the building information for a given position on a farm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="building">The Building instance.</param>
|
||||||
|
/// <param name="x">The x-coordinate of the position.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the position.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the door or building found, or (null, null) if no door or building is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetBuildingInfo(Building building, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
string name = building.buildingType.Value;
|
||||||
|
int buildingTileX = building.tileX.Value;
|
||||||
|
int buildingTileY = building.tileY.Value;
|
||||||
|
|
||||||
|
// If the building is a FishPond, prepend the fish name
|
||||||
|
if (building is FishPond fishPond && fishPond.fishType.Value >= 0)
|
||||||
|
{
|
||||||
|
name = $"{Game1.objectInformation[fishPond.fishType.Value].Split('/')[4]} {name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate differences in x and y coordinates
|
||||||
|
int offsetX = x - buildingTileX;
|
||||||
|
int offsetY = y - buildingTileY;
|
||||||
|
|
||||||
|
// Check if the position matches the human door
|
||||||
|
if (building.humanDoor.Value.X == offsetX && building.humanDoor.Value.Y == offsetY)
|
||||||
|
{
|
||||||
|
return (name + " Door", CATEGORY.Doors);
|
||||||
|
}
|
||||||
|
// Check if the position matches the animal door
|
||||||
|
else if (building.animalDoor.Value.X == offsetX && building.animalDoor.Value.Y == offsetY)
|
||||||
|
{
|
||||||
|
return (name + " Animal Door " + ((building.animalDoorOpen.Value) ? "Opened" : "Closed"), CATEGORY.Doors);
|
||||||
|
}
|
||||||
|
// Check if the position matches the building's top-left corner
|
||||||
|
else if (offsetX == 0 && offsetY == 0)
|
||||||
|
{
|
||||||
|
return (name, CATEGORY.Buildings);
|
||||||
|
}
|
||||||
|
// Special handling for Mill buildings
|
||||||
|
else if (building is Mill)
|
||||||
|
{
|
||||||
|
// Check if the position matches the input
|
||||||
|
if (offsetX == 1 && offsetY == 1)
|
||||||
|
{
|
||||||
|
return (name + " input", CATEGORY.Buildings);
|
||||||
|
}
|
||||||
|
// Check if the position matches the output
|
||||||
|
else if (offsetX == 3 && offsetY == 1)
|
||||||
|
{
|
||||||
|
return (name + " output", CATEGORY.Buildings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the building name for any other position within the building's area
|
||||||
|
return (name, CATEGORY.Buildings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables or other features at a given coordinate in a Farm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="farm">The Farm to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetFarmInfo(Farm farm, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
var mainMailboxPos = farm.GetMainMailboxPosition();
|
||||||
|
Building building = farm.getBuildingAt(new Vector2(x, y));
|
||||||
|
|
||||||
|
if (mainMailboxPos.X == x && mainMailboxPos.Y == y)
|
||||||
|
{
|
||||||
|
return ("Mail box", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else if (building is not null) // Check if there is a building at the current position
|
||||||
|
{
|
||||||
|
return GetBuildingInfo(building, x, y, lessInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables or other features at a given coordinate in a FarmHouse.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="farmHouse">The FarmHouse to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetFarmHouseInfo(FarmHouse farmHouse, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (farmHouse.upgradeLevel >= 1)
|
||||||
|
{
|
||||||
|
int kitchenX = farmHouse.getKitchenStandingSpot().X;
|
||||||
|
int kitchenY = farmHouse.getKitchenStandingSpot().Y - 1;
|
||||||
|
|
||||||
|
if (kitchenX == x && kitchenY == y)
|
||||||
|
{
|
||||||
|
return ("Stove", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else if (kitchenX + 1 == x && kitchenY == y)
|
||||||
|
{
|
||||||
|
return ("Sink", CATEGORY.Others);
|
||||||
|
}
|
||||||
|
else if (farmHouse.fridgePosition.X == x && farmHouse.fridgePosition.Y == y)
|
||||||
|
{
|
||||||
|
return ("Fridge", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables or other features at a given coordinate in a Forest.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="forest">The Forest to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetForestInfo(Forest forest, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (forest.travelingMerchantDay && x == 27 && y == 11)
|
||||||
|
{
|
||||||
|
return ("Travelling Cart", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else if (forest.log != null && x == 2 && y == 7)
|
||||||
|
{
|
||||||
|
return ("Log", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else if (forest.log == null && x == 0 && y == 7)
|
||||||
|
{
|
||||||
|
return ("Secret Woods Entrance", CATEGORY.Doors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in an IslandFarmHouse.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="islandFarmHouse">The IslandFarmHouse to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetIslandFarmHouseInfo(IslandFarmHouse islandFarmHouse, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
int fridgeX = islandFarmHouse.fridgePosition.X;
|
||||||
|
int fridgeY = islandFarmHouse.fridgePosition.Y;
|
||||||
|
if (fridgeX - 2 == x && fridgeY == y)
|
||||||
|
{
|
||||||
|
return ("Stove", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else if (fridgeX - 1 == x && fridgeY == y)
|
||||||
|
{
|
||||||
|
return ("Sink", CATEGORY.Others);
|
||||||
|
}
|
||||||
|
else if (fridgeX == x && fridgeY == y)
|
||||||
|
{
|
||||||
|
return ("Fridge", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in an IslandNorth.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="islandNorth">The IslandNorth to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetIslandNorthInfo(IslandNorth islandNorth, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
// Check if the trader is activated and the coordinates match the trader's location
|
||||||
|
if (islandNorth.traderActivated.Value && x == 36 && y == 71)
|
||||||
|
{
|
||||||
|
return ("Island Trader", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return (null, null) if no relevant object is found
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in an IslandWest.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="islandWest">The IslandWest to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetIslandWestInfo(IslandWest islandWest, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
// Check if the coordinates match the shipping bin's location
|
||||||
|
if ((islandWest.shippingBinPosition.X == x || (islandWest.shippingBinPosition.X + 1) == x) && islandWest.shippingBinPosition.Y == y)
|
||||||
|
{
|
||||||
|
return ("Shipping Bin", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return (null, null) if no relevant object is found
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about tiles at a given coordinate in a VolcanoDungeon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dungeon">The VolcanoDungeon to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name of the tile and the CATEGORY, or (null, null) if no relevant tile is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetVolcanoDungeonInfo(VolcanoDungeon dungeon, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (!lessInfo)
|
||||||
|
{
|
||||||
|
if (dungeon.IsCooledLava(x, y))
|
||||||
|
{
|
||||||
|
return ("Cooled lava", CATEGORY.WaterTiles);
|
||||||
|
}
|
||||||
|
else if (StardewValley.Monsters.LavaLurk.IsLavaTile(dungeon, x, y))
|
||||||
|
{
|
||||||
|
return ("Lava", CATEGORY.WaterTiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in a named IslandLocation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="islandLocation">The named IslandLocation to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetNamedIslandLocationInfo(IslandLocation islandLocation, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
object locationType = islandLocation is not null and IslandLocation ? islandLocation.Name ?? "Undefined Island Location" : islandLocation!.GetType();
|
||||||
|
|
||||||
|
// Implement specific logic for named IslandLocations here, if necessary
|
||||||
|
|
||||||
|
// Unimplemented locations are logged.
|
||||||
|
// Check if the location has already been logged
|
||||||
|
if (!loggedLocations.Contains(locationType))
|
||||||
|
{
|
||||||
|
// Log the message
|
||||||
|
MainClass.DebugLog($"Called GetNamedIslandLocationInfo with unimplemented IslandLocation of type {islandLocation.GetType()} and name {islandLocation.Name}");
|
||||||
|
|
||||||
|
// Add the location to the HashSet to prevent logging it again
|
||||||
|
loggedLocations.Add(locationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the name of the IslandGemBird based on its item index value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bird">The IslandGemBird instance.</param>
|
||||||
|
/// <returns>A string representing the name of the IslandGemBird.</returns>
|
||||||
|
private static String GetGemBirdName(IslandGemBird bird)
|
||||||
|
{
|
||||||
|
// Use a switch expression to return the appropriate bird name based on the item index value
|
||||||
|
return bird.itemIndex.Value switch
|
||||||
|
{
|
||||||
|
60 => "Emerald Gem Bird",
|
||||||
|
62 => "Aquamarine Gem Bird",
|
||||||
|
64 => "Ruby Gem Bird",
|
||||||
|
66 => "Amethyst Gem Bird",
|
||||||
|
68 => "Topaz Gem Bird",
|
||||||
|
_ => "Gem Bird", // Default case for when the item index does not match any of the specified values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parrot perch information at the specified tile coordinates in the given island location.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The x-coordinate of the tile to check.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the tile to check.</param>
|
||||||
|
/// <param name="islandLocation">The IslandLocation where the parrot perch might be found.</param>
|
||||||
|
/// <returns>A string containing the parrot perch information if a parrot perch is found at the specified tile; null if no parrot perch is found.</returns>
|
||||||
|
private static string? GetParrotPerchAtTile(IslandLocation islandLocation, int x, int y)
|
||||||
|
{
|
||||||
|
// Use LINQ to find the first parrot perch at the specified tile (x, y) coordinates
|
||||||
|
var foundPerch = islandLocation.parrotUpgradePerches.FirstOrDefault(perch => perch.tilePosition.Value.Equals(new Point(x, y)));
|
||||||
|
|
||||||
|
// If a parrot perch was found at the specified tile coordinates
|
||||||
|
if (foundPerch != null)
|
||||||
|
{
|
||||||
|
string toSpeak = $"Parrot required nuts {foundPerch.requiredNuts.Value}";
|
||||||
|
|
||||||
|
// Return appropriate string based on the current state of the parrot perch
|
||||||
|
return foundPerch.currentState.Value switch
|
||||||
|
{
|
||||||
|
StardewValley.BellsAndWhistles.ParrotUpgradePerch.UpgradeState.Idle => foundPerch.IsAvailable() ? toSpeak : "Empty parrot perch",
|
||||||
|
StardewValley.BellsAndWhistles.ParrotUpgradePerch.UpgradeState.StartBuilding => "Parrots started building request",
|
||||||
|
StardewValley.BellsAndWhistles.ParrotUpgradePerch.UpgradeState.Building => "Parrots building request",
|
||||||
|
StardewValley.BellsAndWhistles.ParrotUpgradePerch.UpgradeState.Complete => "Request Completed",
|
||||||
|
_ => toSpeak,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no parrot perch was found, return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in an IslandLocation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="islandLocation">The IslandLocation to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetIslandLocationInfo(IslandLocation islandLocation, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
var nutTracker = Game1.player.team.collectedNutTracker;
|
||||||
|
string? parrot = GetParrotPerchAtTile(islandLocation, x, y);
|
||||||
|
if (islandLocation.IsBuriedNutLocation(new Point(x, y)) && !nutTracker.ContainsKey($"Buried_{islandLocation.Name}_{x}_{y}"))
|
||||||
|
{
|
||||||
|
return ("Diggable spot", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
else if (islandLocation.locationGemBird.Value is IslandGemBird bird && ((int)bird.position.X / Game1.tileSize) == x && ((int)bird.position.Y / Game1.tileSize) == y)
|
||||||
|
{
|
||||||
|
return (GetGemBirdName(bird), CATEGORY.NPCs);
|
||||||
|
}
|
||||||
|
else if (parrot != null)
|
||||||
|
{
|
||||||
|
return (parrot, CATEGORY.Buildings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return islandLocation switch
|
||||||
|
{
|
||||||
|
IslandNorth islandNorth => GetIslandNorthInfo(islandNorth, x, y, lessInfo),
|
||||||
|
IslandWest islandWest => GetIslandWestInfo(islandWest, x, y, lessInfo),
|
||||||
|
VolcanoDungeon dungeon => GetVolcanoDungeonInfo(dungeon, x, y, lessInfo),
|
||||||
|
_ => GetNamedIslandLocationInfo(islandLocation, x, y, lessInfo)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of the "Action" property from the Buildings layer tile at the given coordinates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryMuseum">The LibraryMuseum containing the tile.</param>
|
||||||
|
/// <param name="x">The x-coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the tile.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>The value of the "Action" property as a string, or null if the property is not found.</returns>
|
||||||
|
private static string? GetTileActionPropertyValue(LibraryMuseum libraryMuseum, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
xTile.Tiles.Tile tile = libraryMuseum.map.GetLayer("Buildings").PickTile(new xTile.Dimensions.Location(x * 64, y * 64), Game1.viewport.Size);
|
||||||
|
return tile.Properties.TryGetValue("Action", out xTile.ObjectModel.PropertyValue? value) ? value.ToString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables, NPCs, or other features at a given coordinate in a LibraryMuseum.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryMuseum">The LibraryMuseum to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetLibraryMuseumInfo(LibraryMuseum libraryMuseum, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (libraryMuseum.museumPieces.TryGetValue(new Vector2(x, y), out int museumPiece))
|
||||||
|
{
|
||||||
|
string displayName = Game1.objectInformation[museumPiece].Split('/')[0];
|
||||||
|
return ($"{displayName} showcase", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
|
||||||
|
int booksFound = Game1.netWorldState.Value.LostBooksFound.Value;
|
||||||
|
string? action = libraryMuseum.doesTileHaveProperty(x, y, "Action", "Buildings");
|
||||||
|
if (action != null && action.Contains("Notes"))
|
||||||
|
{
|
||||||
|
string? actionPropertyValue = GetTileActionPropertyValue(libraryMuseum, x, y, lessInfo);
|
||||||
|
|
||||||
|
if (actionPropertyValue != null)
|
||||||
|
{
|
||||||
|
int which = Convert.ToInt32(actionPropertyValue.Split(' ')[1]);
|
||||||
|
if (booksFound >= which)
|
||||||
|
{
|
||||||
|
string message = Game1.content.LoadString("Strings\\Notes:" + which);
|
||||||
|
return ($"{message.Split('\n')[0]} Book", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
return ($"Lost Book", CATEGORY.Others);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves information about interactables or other features at a given coordinate in a Town.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="town">The Town to search.</param>
|
||||||
|
/// <param name="x">The x-coordinate to search.</param>
|
||||||
|
/// <param name="y">The y-coordinate to search.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the object found, or (null, null) if no relevant object is found.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetTownInfo(Town town, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (SpecialOrder.IsSpecialOrdersBoardUnlocked() && x == 62 && y == 93)
|
||||||
|
{
|
||||||
|
return ("Special quest board", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the feeding bench information for barns and coops.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentLocation">The current GameLocation instance.</param>
|
||||||
|
/// <param name="x">The x coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y coordinate of the tile.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple of (string? name, CATEGORY? category) for the feeding bench, or null if not applicable.</returns>
|
||||||
|
private static (string? name, CATEGORY? category)? GetFeedingBenchInfo(GameLocation currentLocation, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
string locationName = currentLocation.Name;
|
||||||
|
|
||||||
|
if (FeedingBenchBounds.TryGetValue(locationName, out var bounds) && x >= bounds.minX && x <= bounds.maxX && y == bounds.y)
|
||||||
|
{
|
||||||
|
(string? name, CATEGORY category) = TileInfo.getObjectAtTile(currentLocation, x, y, true);
|
||||||
|
return (name?.Contains("hay", StringComparison.OrdinalIgnoreCase) == true ? "Feeding Bench" : "Empty Feeding Bench", category);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about the current location by its name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentLocation">The current GameLocation instance.</param>
|
||||||
|
/// <param name="x">The x coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y coordinate of the tile.</param>
|
||||||
|
/// <param name="lessInfo">Optional. If true, returns information only if the tile coordinates match the resource clump's origin. Default is false.</param>
|
||||||
|
/// <returns>A tuple of (string? name, CATEGORY? category) for the object in the location, or null if not applicable.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetLocationByNameInfo(GameLocation currentLocation, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
object locationType = currentLocation is not null and GameLocation ? currentLocation.Name ?? "Undefined GameLocation" : currentLocation!.GetType(); string locationName = currentLocation.Name ?? "";
|
||||||
|
if (locationName.Contains("coop", StringComparison.OrdinalIgnoreCase) || locationName.Contains("barn", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var feedingBenchInfo = GetFeedingBenchInfo(currentLocation, x, y);
|
||||||
|
if (feedingBenchInfo.HasValue)
|
||||||
|
{
|
||||||
|
return feedingBenchInfo.Value;
|
||||||
|
} // else if something other than feeding benches in barns and coops...
|
||||||
|
} //else if something other than barns and coops...
|
||||||
|
|
||||||
|
// Unimplemented locations are logged.
|
||||||
|
// Check if the location has already been logged
|
||||||
|
if (!loggedLocations.Contains(locationType))
|
||||||
|
{
|
||||||
|
// Log the message
|
||||||
|
MainClass.DebugLog($"Called GetLocationByNameInfo with unimplemented GameLocation of type {currentLocation.GetType()} and name {currentLocation.Name}");
|
||||||
|
|
||||||
|
// Add the location to the HashSet to prevent logging it again
|
||||||
|
loggedLocations.Add(locationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the dynamic tile information for the given coordinates in the specified location.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentLocation">The current GameLocation instance.</param>
|
||||||
|
/// <param name="x">The x-coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the tile.</param>
|
||||||
|
/// <param name="lessInfo">An optional boolean to return less detailed information. Defaults to false.</param>
|
||||||
|
/// <returns>A tuple containing the name and CATEGORY of the dynamic tile, or null values if not found.</returns>
|
||||||
|
public static (string? name, CATEGORY? category) GetDynamicTileAt(GameLocation currentLocation, int x, int y, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
// Check for panning spots
|
||||||
|
if (currentLocation.orePanPoint.Value != Point.Zero && currentLocation.orePanPoint.Value == new Point(x, y))
|
||||||
|
{
|
||||||
|
return ("panning spot", CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
// Check if the current location has an event
|
||||||
|
else if (currentLocation.currentEvent is not null)
|
||||||
|
{
|
||||||
|
string eventName = currentLocation.currentEvent.FestivalName;
|
||||||
|
// Attempt to retrieve the nested dictionary for the event name from the EventInteractables dictionary
|
||||||
|
if (EventInteractables.TryGetValue(eventName, out var coordinateDictionary))
|
||||||
|
{
|
||||||
|
// Attempt to retrieve the interactable value from the nested dictionary using the coordinates (x, y) as the key
|
||||||
|
if (coordinateDictionary.TryGetValue((x, y), value: out var interactable))
|
||||||
|
{
|
||||||
|
// If the interactable value is found, return the corresponding category and interactable name
|
||||||
|
return (interactable, CATEGORY.Interactables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve dynamic tile information based on the current location type
|
||||||
|
return currentLocation switch
|
||||||
|
{
|
||||||
|
Beach beach => GetBeachInfo(beach, x, y, lessInfo),
|
||||||
|
BoatTunnel boatTunnel => GetBoatTunnelInfo(boatTunnel, x, y, lessInfo),
|
||||||
|
CommunityCenter communityCenter => GetCommunityCenterInfo(communityCenter, x, y, lessInfo),
|
||||||
|
Farm farm => GetFarmInfo(farm, x, y, lessInfo),
|
||||||
|
FarmHouse farmHouse => GetFarmHouseInfo(farmHouse, x, y, lessInfo),
|
||||||
|
Forest forest => GetForestInfo(forest, x, y, lessInfo),
|
||||||
|
IslandFarmHouse islandFarmHouse => GetIslandFarmHouseInfo(islandFarmHouse, x, y, lessInfo),
|
||||||
|
IslandLocation islandLocation => GetIslandLocationInfo(islandLocation, x, y, lessInfo),
|
||||||
|
LibraryMuseum libraryMuseum => GetLibraryMuseumInfo(libraryMuseum, x, y, lessInfo),
|
||||||
|
Town town => GetTownInfo(town, x, y, lessInfo),
|
||||||
|
_ => GetLocationByNameInfo(currentLocation, x, y, lessInfo)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,21 +10,23 @@ namespace stardew_access.Features
|
||||||
internal static int prevSlotIndex = -999;
|
internal static int prevSlotIndex = -999;
|
||||||
|
|
||||||
internal static bool narrateHoveredSlot(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y,
|
internal static bool narrateHoveredSlot(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y,
|
||||||
bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1,
|
bool? giveExtraDetails = null, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1,
|
||||||
bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "")
|
bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "")
|
||||||
{
|
{
|
||||||
if (narrateHoveredSlotAndReturnIndex(inventoryMenu, inventory, actualInventory, x, y,
|
if (narrateHoveredSlotAndReturnIndex(inventoryMenu, inventory, actualInventory, x, y,
|
||||||
giveExtraDetails = false, hoverPrice = -1, extraItemToShowIndex = -1, extraItemToShowAmount = -1,
|
giveExtraDetails, hoverPrice, extraItemToShowIndex, extraItemToShowAmount,
|
||||||
handleHighlightedItem = false, highlightedItemPrefix = "", highlightedItemSuffix = "") == -999)
|
handleHighlightedItem, highlightedItemPrefix, highlightedItemSuffix) == -999)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static int narrateHoveredSlotAndReturnIndex(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y,
|
internal static int narrateHoveredSlotAndReturnIndex(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y,
|
||||||
bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1,
|
bool? giveExtraDetails = null, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1,
|
||||||
bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "")
|
bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "")
|
||||||
{
|
{
|
||||||
|
if (giveExtraDetails is null)
|
||||||
|
giveExtraDetails = !MainClass.Config.DisableInventoryVerbosity;
|
||||||
for (int i = 0; i < inventory.Count; i++)
|
for (int i = 0; i < inventory.Count; i++)
|
||||||
{
|
{
|
||||||
if (!inventory[i].containsPoint(x, y)) continue;
|
if (!inventory[i].containsPoint(x, y)) continue;
|
||||||
|
@ -45,27 +47,31 @@ namespace stardew_access.Features
|
||||||
string name = $"{namePrefix}{actualInventory[i].DisplayName}{nameSuffix}";
|
string name = $"{namePrefix}{actualInventory[i].DisplayName}{nameSuffix}";
|
||||||
int stack = actualInventory[i].Stack;
|
int stack = actualInventory[i].Stack;
|
||||||
string quality = getQualityFromItem(actualInventory[i]);
|
string quality = getQualityFromItem(actualInventory[i]);
|
||||||
string healthNStamine = getHealthNStaminaFromItem(actualInventory[i]);
|
string healthNStamina = getHealthNStaminaFromItem(actualInventory[i]);
|
||||||
string buffs = getBuffsFromItem(actualInventory[i]);
|
string buffs = getBuffsFromItem(actualInventory[i]);
|
||||||
string description = actualInventory[i].getDescription();
|
string description = actualInventory[i].getDescription();
|
||||||
string price = getPrice(hoverPrice);
|
string price = getPrice(hoverPrice);
|
||||||
string requirements = getExtraItemInfo(extraItemToShowIndex, extraItemToShowAmount);
|
string requirements = getExtraItemInfo(extraItemToShowIndex, extraItemToShowAmount);
|
||||||
|
|
||||||
if (giveExtraDetails)
|
string details;
|
||||||
|
if (giveExtraDetails == true)
|
||||||
{
|
{
|
||||||
if (stack > 1)
|
if (stack > 1)
|
||||||
toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}";
|
toSpeak = $"{stack} {name}"; // {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamina}, \n{buffs}";
|
||||||
else
|
else
|
||||||
toSpeak = $"{name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}";
|
toSpeak = $"{name}"; //{quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamina}, \n{buffs}";
|
||||||
|
details = string.Join(",\n", new string[] { quality, requirements, price, description, healthNStamina, buffs }.Where(c => !string.IsNullOrEmpty(c)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (stack > 1)
|
if (stack > 1)
|
||||||
toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}";
|
toSpeak = $"{stack} {name}"; //{quality}, \n{requirements}, \n{price}";
|
||||||
else
|
else
|
||||||
toSpeak = $"{name} {quality}, \n{requirements}, \n{price}";
|
toSpeak = $"{name}"; //{quality}, \n{requirements}, \n{price}";
|
||||||
|
details = string.Join(",\n", new string[] { quality, requirements, price }.Where(c => !string.IsNullOrEmpty(c)));
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrEmpty(details))
|
||||||
|
toSpeak = $"{toSpeak}, {details}";
|
||||||
|
|
||||||
checkAndSpeak(toSpeak, i);
|
checkAndSpeak(toSpeak, i);
|
||||||
prevSlotIndex = i;
|
prevSlotIndex = i;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
using StardewValley.Objects;
|
using StardewValley.Objects;
|
||||||
|
@ -6,9 +8,9 @@ namespace stardew_access.Features
|
||||||
{
|
{
|
||||||
public class Radar
|
public class Radar
|
||||||
{
|
{
|
||||||
private List<Vector2> closed;
|
private readonly List<Vector2> closed;
|
||||||
private List<Furniture> furnitures;
|
private readonly List<Furniture> furnitures;
|
||||||
private List<NPC> npcs;
|
private readonly List<NPC> npcs;
|
||||||
public List<string> exclusions;
|
public List<string> exclusions;
|
||||||
private List<string> temp_exclusions;
|
private List<string> temp_exclusions;
|
||||||
public List<string> focus;
|
public List<string> focus;
|
||||||
|
@ -91,10 +93,11 @@ namespace stardew_access.Features
|
||||||
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
|
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
|
||||||
public Dictionary<Vector2, (string, string)> SearchNearbyTiles(Vector2 center, int limit, bool playSound = true)
|
public Dictionary<Vector2, (string, string)> SearchNearbyTiles(Vector2 center, int limit, bool playSound = true)
|
||||||
{
|
{
|
||||||
Dictionary<Vector2, (string, string)> detectedTiles = new Dictionary<Vector2, (string, string)>();
|
var currentLocation = Game1.currentLocation;
|
||||||
|
Dictionary<Vector2, (string, string)> detectedTiles = new();
|
||||||
|
|
||||||
Queue<Vector2> toSearch = new Queue<Vector2>();
|
Queue<Vector2> toSearch = new();
|
||||||
List<Vector2> searched = new List<Vector2>();
|
HashSet<Vector2> searched = new();
|
||||||
int[] dirX = { -1, 0, 1, 0 };
|
int[] dirX = { -1, 0, 1, 0 };
|
||||||
int[] dirY = { 0, 1, 0, -1 };
|
int[] dirY = { 0, 1, 0, -1 };
|
||||||
|
|
||||||
|
@ -105,10 +108,10 @@ namespace stardew_access.Features
|
||||||
{
|
{
|
||||||
Vector2 item = toSearch.Dequeue();
|
Vector2 item = toSearch.Dequeue();
|
||||||
if (playSound)
|
if (playSound)
|
||||||
CheckTileAndPlaySound(item);
|
CheckTileAndPlaySound(item, currentLocation);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(bool, string?, string) tileInfo = CheckTile(item);
|
(bool, string?, string) tileInfo = CheckTile(item, currentLocation);
|
||||||
if (tileInfo.Item1 && tileInfo.Item2 != null)
|
if (tileInfo.Item1 && tileInfo.Item2 != null)
|
||||||
{
|
{
|
||||||
// Add detected tile to the dictionary
|
// Add detected tile to the dictionary
|
||||||
|
@ -118,7 +121,7 @@ namespace stardew_access.Features
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
Vector2 dir = new Vector2(item.X + dirX[i], item.Y + dirY[i]);
|
Vector2 dir = new(item.X + dirX[i], item.Y + dirY[i]);
|
||||||
|
|
||||||
if (isValid(dir, center, searched, limit))
|
if (isValid(dir, center, searched, limit))
|
||||||
{
|
{
|
||||||
|
@ -128,6 +131,7 @@ namespace stardew_access.Features
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searched.Clear();
|
||||||
return detectedTiles;
|
return detectedTiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,12 +141,15 @@ namespace stardew_access.Features
|
||||||
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
|
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
|
||||||
public Dictionary<Vector2, (string, string)> SearchLocation()
|
public Dictionary<Vector2, (string, string)> SearchLocation()
|
||||||
{
|
{
|
||||||
Dictionary<Vector2, (string, string)> detectedTiles = new Dictionary<Vector2, (string, string)>();
|
//var watch = new Stopwatch();
|
||||||
|
//watch.Start();
|
||||||
|
var currentLocation = Game1.currentLocation;
|
||||||
|
Dictionary<Vector2, (string, string)> detectedTiles = new();
|
||||||
Vector2 position = Vector2.Zero;
|
Vector2 position = Vector2.Zero;
|
||||||
(bool, string? name, string category) tileInfo;
|
(bool, string? name, string category) tileInfo;
|
||||||
|
|
||||||
Queue<Vector2> toSearch = new Queue<Vector2>();
|
Queue<Vector2> toSearch = new();
|
||||||
List<Vector2> searched = new List<Vector2>();
|
HashSet<Vector2> searched = new();
|
||||||
int[] dirX = { -1, 0, 1, 0 };
|
int[] dirX = { -1, 0, 1, 0 };
|
||||||
int[] dirY = { 0, 1, 0, -1 };
|
int[] dirY = { 0, 1, 0, -1 };
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
@ -150,10 +157,15 @@ namespace stardew_access.Features
|
||||||
toSearch.Enqueue(Game1.player.getTileLocation());
|
toSearch.Enqueue(Game1.player.getTileLocation());
|
||||||
searched.Add(Game1.player.getTileLocation());
|
searched.Add(Game1.player.getTileLocation());
|
||||||
|
|
||||||
|
//watch.Stop();
|
||||||
|
//var elapsedMs = watch.ElapsedMilliseconds;
|
||||||
|
//MainClass.DebugLog($"Search init duration: {elapsedMs}");
|
||||||
|
//watch.Reset();
|
||||||
|
//watch.Start();
|
||||||
while (toSearch.Count > 0)
|
while (toSearch.Count > 0)
|
||||||
{
|
{
|
||||||
Vector2 item = toSearch.Dequeue();
|
Vector2 item = toSearch.Dequeue();
|
||||||
tileInfo = CheckTile(item, true);
|
tileInfo = CheckTile(item, currentLocation, true);
|
||||||
if (tileInfo.Item1 && tileInfo.name != null)
|
if (tileInfo.Item1 && tileInfo.name != null)
|
||||||
{
|
{
|
||||||
// Add detected tile to the dictionary
|
// Add detected tile to the dictionary
|
||||||
|
@ -164,16 +176,19 @@ namespace stardew_access.Features
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
Vector2 dir = new Vector2(item.X + dirX[i], item.Y + dirY[i]);
|
Vector2 dir = new(item.X + dirX[i], item.Y + dirY[i]);
|
||||||
|
|
||||||
if (!searched.Contains(dir) && (TileInfo.isWarpPointAtTile((int)dir.X, (int)dir.Y) || Game1.currentLocation.isTileOnMap(dir)))
|
if (!searched.Contains(dir) && (TileInfo.isWarpPointAtTile(currentLocation, (int)dir.X, (int)dir.Y) || currentLocation.isTileOnMap(dir)))
|
||||||
{
|
{
|
||||||
toSearch.Enqueue(dir);
|
toSearch.Enqueue(dir);
|
||||||
searched.Add(dir);
|
searched.Add(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//watch.Stop();
|
||||||
|
//elapsedMs = watch.ElapsedMilliseconds;
|
||||||
|
//MainClass.DebugLog($"Search loop duration: {elapsedMs}; {count} iterations.");
|
||||||
|
searched.Clear();
|
||||||
return detectedTiles;
|
return detectedTiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +200,7 @@ namespace stardew_access.Features
|
||||||
/// <param name="searched">The list of searched items.</param>
|
/// <param name="searched">The list of searched items.</param>
|
||||||
/// <param name="limit">The radius of search</param>
|
/// <param name="limit">The radius of search</param>
|
||||||
/// <returns>Returns true if the tile is valid for search.</returns>
|
/// <returns>Returns true if the tile is valid for search.</returns>
|
||||||
public bool isValid(Vector2 item, Vector2 center, List<Vector2> searched, int limit)
|
public bool isValid(Vector2 item, Vector2 center, HashSet<Vector2> searched, int limit)
|
||||||
{
|
{
|
||||||
if (Math.Abs(item.X - center.X) > limit)
|
if (Math.Abs(item.X - center.X) > limit)
|
||||||
return false;
|
return false;
|
||||||
|
@ -198,29 +213,28 @@ namespace stardew_access.Features
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (bool, string? name, string category) CheckTile(Vector2 position, bool lessInfo = false)
|
public (bool, string? name, string category) CheckTile(Vector2 position, GameLocation currentLocation, bool lessInfo = false)
|
||||||
{
|
{
|
||||||
(string? name, CATEGORY? category) tileDetail = TileInfo.getNameWithCategoryAtTile(position, lessInfo);
|
(string? name, CATEGORY? category) = TileInfo.getNameWithCategoryAtTile(position, currentLocation, lessInfo);
|
||||||
if (tileDetail.name == null)
|
if (name == null)
|
||||||
return (false, null, CATEGORY.Others.ToString());
|
return (false, null, CATEGORY.Others.ToString());
|
||||||
|
|
||||||
if (tileDetail.category == null)
|
category ??= CATEGORY.Others;
|
||||||
tileDetail.category = CATEGORY.Others;
|
|
||||||
|
|
||||||
return (true, tileDetail.name, tileDetail.category.ToString());
|
return (true, name, category.ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckTileAndPlaySound(Vector2 position)
|
public void CheckTileAndPlaySound(Vector2 position, GameLocation currentLocation)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Game1.currentLocation.isObjectAtTile((int)position.X, (int)position.Y))
|
if (currentLocation.isObjectAtTile((int)position.X, (int)position.Y))
|
||||||
{
|
{
|
||||||
(string? name, CATEGORY category) objDetails = TileInfo.getObjectAtTile((int)position.X, (int)position.Y);
|
(string? name, CATEGORY category) objDetails = TileInfo.getObjectAtTile(currentLocation, (int)position.X, (int)position.Y);
|
||||||
string? objectName = objDetails.name;
|
string? objectName = objDetails.name;
|
||||||
CATEGORY category = objDetails.category;
|
CATEGORY category = objDetails.category;
|
||||||
StardewValley.Object obj = Game1.currentLocation.getObjectAtTile((int)position.X, (int)position.Y);
|
StardewValley.Object obj = currentLocation.getObjectAtTile((int)position.X, (int)position.Y);
|
||||||
|
|
||||||
if (objectName != null)
|
if (objectName != null)
|
||||||
{
|
{
|
||||||
|
@ -231,23 +245,22 @@ namespace stardew_access.Features
|
||||||
if (!furnitures.Contains((Furniture)obj))
|
if (!furnitures.Contains((Furniture)obj))
|
||||||
{
|
{
|
||||||
furnitures.Add((Furniture)obj);
|
furnitures.Add((Furniture)obj);
|
||||||
PlaySoundAt(position, objectName, category);
|
PlaySoundAt(position, objectName, category, currentLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
PlaySoundAt(position, objectName, category);
|
PlaySoundAt(position, objectName, category, currentLocation);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(string? name, CATEGORY? category) tileDetail = TileInfo.getNameWithCategoryAtTile(position);
|
(string? name, CATEGORY? category) = TileInfo.getNameWithCategoryAtTile(position, currentLocation);
|
||||||
if (tileDetail.name != null)
|
if (name != null)
|
||||||
{
|
{
|
||||||
if (tileDetail.category == null)
|
category ??= CATEGORY.Others;
|
||||||
tileDetail.category = CATEGORY.Others;
|
|
||||||
|
|
||||||
PlaySoundAt(position, tileDetail.name, tileDetail.category);
|
PlaySoundAt(position, name, category, currentLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,7 +270,7 @@ namespace stardew_access.Features
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlaySoundAt(Vector2 position, string searchQuery, CATEGORY category)
|
public void PlaySoundAt(Vector2 position, string searchQuery, CATEGORY category, GameLocation currentLocation)
|
||||||
{
|
{
|
||||||
#region Check whether to skip the object or not
|
#region Check whether to skip the object or not
|
||||||
|
|
||||||
|
@ -318,19 +331,19 @@ namespace stardew_access.Features
|
||||||
|
|
||||||
if (dy < 0 && (Math.Abs(dy) >= Math.Abs(dx))) // Object is at top
|
if (dy < 0 && (Math.Abs(dy) >= Math.Abs(dx))) // Object is at top
|
||||||
{
|
{
|
||||||
Game1.currentLocation.localSoundAt(GetSoundName(category, "top"), position);
|
currentLocation.localSoundAt(GetSoundName(category, "top"), position);
|
||||||
}
|
}
|
||||||
else if (dx > 0 && (Math.Abs(dx) >= Math.Abs(dy))) // Object is at right
|
else if (dx > 0 && (Math.Abs(dx) >= Math.Abs(dy))) // Object is at right
|
||||||
{
|
{
|
||||||
Game1.currentLocation.localSoundAt(GetSoundName(category, "right"), position);
|
currentLocation.localSoundAt(GetSoundName(category, "right"), position);
|
||||||
}
|
}
|
||||||
else if (dx < 0 && (Math.Abs(dx) > Math.Abs(dy))) // Object is at left
|
else if (dx < 0 && (Math.Abs(dx) > Math.Abs(dy))) // Object is at left
|
||||||
{
|
{
|
||||||
Game1.currentLocation.localSoundAt(GetSoundName(category, "left"), position);
|
currentLocation.localSoundAt(GetSoundName(category, "left"), position);
|
||||||
}
|
}
|
||||||
else if (dy > 0 && (Math.Abs(dy) > Math.Abs(dx))) // Object is at bottom
|
else if (dy > 0 && (Math.Abs(dy) > Math.Abs(dx))) // Object is at bottom
|
||||||
{
|
{
|
||||||
Game1.currentLocation.localSoundAt(GetSoundName(category, "bottom"), position);
|
currentLocation.localSoundAt(GetSoundName(category, "bottom"), position);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,9 +89,10 @@ namespace stardew_access.Features
|
||||||
MainClass.ScreenReader.PrevTextTile = " ";
|
MainClass.ScreenReader.PrevTextTile = " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isColliding = TileInfo.isCollidingAtTile(x, y);
|
var currentLocation = Game1.currentLocation;
|
||||||
|
bool isColliding = TileInfo.IsCollidingAtTile(currentLocation, x, y);
|
||||||
|
|
||||||
(string? name, string? category) info = TileInfo.getNameWithCategoryNameAtTile(tile);
|
(string? name, string? category) info = TileInfo.getNameWithCategoryNameAtTile(tile, currentLocation);
|
||||||
|
|
||||||
#region Narrate toSpeak
|
#region Narrate toSpeak
|
||||||
if (info.name != null)
|
if (info.name != null)
|
||||||
|
|
|
@ -1,196 +1,86 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
using static stardew_access.Features.Utils;
|
||||||
|
|
||||||
namespace stardew_access.Features
|
namespace stardew_access.Features
|
||||||
{
|
{
|
||||||
public class StaticTiles
|
public class StaticTiles
|
||||||
{
|
{
|
||||||
private JObject? staticTilesData = null;
|
// Static instance for the singleton pattern
|
||||||
private JObject? customTilesData = null;
|
private static StaticTiles? _instance;
|
||||||
|
|
||||||
public StaticTiles()
|
/// <summary>
|
||||||
|
/// The singleton instance of the <see cref="StaticTiles"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public static StaticTiles Instance
|
||||||
{
|
{
|
||||||
if (MainClass.ModHelper == null)
|
get
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using (StreamReader file = new StreamReader(Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "static-tiles.json")))
|
_instance ??= new StaticTiles();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A nullable JsonElement containing static tile data.
|
||||||
|
/// </summary>
|
||||||
|
private static JsonElement? staticTilesData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A nullable JsonElement containing custom tile data.
|
||||||
|
/// </summary>
|
||||||
|
private static JsonElement? customTilesData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary that maps location names to tile data dictionaries for static tiles.
|
||||||
|
/// Each tile data dictionary maps tile coordinates (x, y) to a tuple containing the object name and category.
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> staticTilesDataDict = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary that maps location names to tile data dictionaries for custom tiles.
|
||||||
|
/// Each tile data dictionary maps tile coordinates (x, y) to a tuple containing the object name and category.
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> customTilesDataDict = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file name of the JSON file containing static tile data.
|
||||||
|
/// </summary>
|
||||||
|
private const string StaticTilesFileName = "static-tiles.json";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file name of the JSON file containing custom tile data.
|
||||||
|
/// </summary>
|
||||||
|
private const string CustomTilesFileName = "custom-tiles.json";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary that contains conditional lambda functions for checking specific game conditions.
|
||||||
|
/// Each lambda function takes two arguments: a conditionType (string) and a uniqueModId (string) and returns a boolean value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The following lambda functions are currently supported:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// <description>"Farm": Checks if the current in-game farm type matches the given farm type (conditionType).</description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <description>"JojaMember": Checks if the player has the "JojaMember" mail. The input arguments are ignored.</description>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// Additional lambda functions can be added as needed.
|
||||||
|
/// </remarks>
|
||||||
|
private static readonly Dictionary<string, Func<string, string, bool>> conditionals = new()
|
||||||
{
|
{
|
||||||
string json = file.ReadToEnd();
|
["Farm"] = (conditionType, uniqueModId) =>
|
||||||
staticTilesData = JObject.Parse(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
MainClass.InfoLog($"Loaded static-tile.json");
|
|
||||||
}
|
|
||||||
catch (System.Exception)
|
|
||||||
{
|
{
|
||||||
MainClass.ErrorLog($"static-tiles.json file not found or an error occured while initializing static-tiles.json\nThe path of the file should be:\n\t{Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "static-tiles.json")}");
|
if (string.IsNullOrEmpty(uniqueModId))
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using (StreamReader file = new StreamReader(Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "custom-tiles.json")))
|
// Branch for vanilla locations
|
||||||
{
|
// Calculate farmTypeIndex using the switch expression
|
||||||
string json = file.ReadToEnd();
|
int farmTypeIndex = conditionType.ToLower() switch
|
||||||
customTilesData = JObject.Parse(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
MainClass.InfoLog($"Loaded custom-tile.json");
|
|
||||||
}
|
|
||||||
catch (System.Exception)
|
|
||||||
{
|
|
||||||
MainClass.InfoLog($"custom-tiles.json file not found or an error occured while initializing custom-tiles.json\nThe path of the file should be:\n\t{Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "custom-tiles.json")}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool isAvailable(string locationName)
|
|
||||||
{
|
|
||||||
List<JObject> allData = new List<JObject>();
|
|
||||||
|
|
||||||
if (customTilesData != null) allData.Add(customTilesData);
|
|
||||||
if (staticTilesData != null) allData.Add(staticTilesData);
|
|
||||||
|
|
||||||
foreach (JObject data in allData)
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<string, JToken?> location in data)
|
|
||||||
{
|
|
||||||
if (location.Key.Contains("||") && MainClass.ModHelper != null)
|
|
||||||
{
|
|
||||||
string uniqueModID = location.Key.Substring(location.Key.LastIndexOf("||") + 2);
|
|
||||||
string locationNameInJson = location.Key.Remove(location.Key.LastIndexOf("||"));
|
|
||||||
bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID);
|
|
||||||
|
|
||||||
if (!isLoaded) continue; // Skip if the specified mod is not loaded
|
|
||||||
if (locationName.ToLower().Equals(locationNameInJson.ToLower())) return true;
|
|
||||||
}
|
|
||||||
else if (locationName.ToLower().Equals(location.Key.ToLower()))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string? getStaticTileInfoAt(int x, int y)
|
|
||||||
{
|
|
||||||
return getStaticTileInfoAtWithCategory(x, y).name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public (string? name, CATEGORY category) getStaticTileInfoAtWithCategory(int x, int y) {
|
|
||||||
List<JObject> allData = new List<JObject>();
|
|
||||||
|
|
||||||
if (customTilesData != null) allData.Add(customTilesData);
|
|
||||||
if (staticTilesData != null) allData.Add(staticTilesData);
|
|
||||||
|
|
||||||
foreach (JObject data in allData) {
|
|
||||||
foreach (KeyValuePair<string, JToken?> location in data)
|
|
||||||
{
|
|
||||||
string locationName = location.Key;
|
|
||||||
if (locationName.Contains("||") && MainClass.ModHelper != null)
|
|
||||||
{
|
|
||||||
// Mod Specific Tiles
|
|
||||||
// We can add tiles that only get detected when the specified mod is loaded.
|
|
||||||
// Syntax: <location name>||<Mod's unique id, look into the mod's manifest.json for unique id>
|
|
||||||
// Example: THe following tile will only be detected if Stardew Valley Expanded mod is installed
|
|
||||||
// {
|
|
||||||
// .
|
|
||||||
// .
|
|
||||||
// .
|
|
||||||
// "Town||FlashShifter.StardewValleyExpandedCP":{
|
|
||||||
// "<Tile Name>":{
|
|
||||||
// "x": [<x location(s)>],
|
|
||||||
// "y": [<y location(s)>],
|
|
||||||
// "type": "<Category name>"
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// .
|
|
||||||
// .
|
|
||||||
// .
|
|
||||||
// }
|
|
||||||
string uniqueModID = locationName.Substring(locationName.LastIndexOf("||") + 2);
|
|
||||||
locationName = locationName.Remove(locationName.LastIndexOf("||"));
|
|
||||||
bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID);
|
|
||||||
|
|
||||||
if (!isLoaded) continue; // Skip if the specified mod is not loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locationName.Contains("_") && locationName.ToLower().StartsWith("farm_"))
|
|
||||||
{
|
|
||||||
string farmType = locationName.Substring(locationName.LastIndexOf("_") + 1);
|
|
||||||
int farmTypeIndex = getFarmTypeIndex(farmType);
|
|
||||||
locationName = locationName.Remove(locationName.LastIndexOf("_"));
|
|
||||||
|
|
||||||
if (farmTypeIndex != Game1.whichFarm) continue; // Skip if current farm type does not matches
|
|
||||||
// if (Game1.whichModFarm != null) MainClass.DebugLog($"{farmType} {Game1.whichModFarm.MapName}");
|
|
||||||
if (farmTypeIndex != 7 || Game1.whichModFarm == null || !farmType.ToLower().Equals(Game1.whichModFarm.MapName.ToLower())) continue; // Not tested but should work
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locationName.ToLower().Equals("town_joja") && Game1.MasterPlayer.mailReceived.Contains("JojaMember"))
|
|
||||||
{
|
|
||||||
locationName = "town";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Game1.currentLocation.Name.ToLower().Equals(locationName.ToLower())) continue;
|
|
||||||
if (location.Value == null) continue;
|
|
||||||
|
|
||||||
foreach (var tile in ((JObject)location.Value))
|
|
||||||
{
|
|
||||||
if (tile.Value == null) continue;
|
|
||||||
|
|
||||||
JToken? tileXArray = tile.Value["x"];
|
|
||||||
JToken? tileYArray = tile.Value["y"];
|
|
||||||
JToken? tileType = tile.Value["type"];
|
|
||||||
|
|
||||||
if (tileXArray == null || tileYArray == null || tileType == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool isXPresent = false;
|
|
||||||
bool isYPresent = false;
|
|
||||||
|
|
||||||
foreach (var item in tileXArray)
|
|
||||||
{
|
|
||||||
if (short.Parse(item.ToString()) != x)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
isXPresent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in tileYArray)
|
|
||||||
{
|
|
||||||
if (short.Parse(item.ToString()) != y)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
isYPresent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isXPresent && isYPresent)
|
|
||||||
{
|
|
||||||
string key = tile.Key;
|
|
||||||
if (key.Contains('[') && key.Contains(']'))
|
|
||||||
{
|
|
||||||
int i1 = key.IndexOf('[');
|
|
||||||
int i2 = key.LastIndexOf(']');
|
|
||||||
|
|
||||||
if (i1 < i2)
|
|
||||||
{
|
|
||||||
key = key.Remove(i1, ++i2 - i1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (key.Trim(), CATEGORY.FromString(tileType.ToString().ToLower()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (null, CATEGORY.Others);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getFarmTypeIndex(string farmType)
|
|
||||||
{
|
|
||||||
return farmType.ToLower() switch
|
|
||||||
{
|
{
|
||||||
"default" => 0,
|
"default" => 0,
|
||||||
"riverlands" => 1,
|
"riverlands" => 1,
|
||||||
|
@ -201,6 +91,425 @@ namespace stardew_access.Features
|
||||||
"beach" => 6,
|
"beach" => 6,
|
||||||
_ => 7,
|
_ => 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Return true if the farmTypeIndex matches the current in-game farm type, otherwise false
|
||||||
|
return farmTypeIndex == Game1.whichFarm;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Branch for mod locations
|
||||||
|
// Log an error message and return false, as mod locations are not yet supported for the Farm conditional
|
||||||
|
MainClass.ErrorLog("Mod locations are not yet supported for the Farm conditional.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["JojaMember"] = (conditionType, uniqueModId) =>
|
||||||
|
{
|
||||||
|
// Return true if the player has the "JojaMember" mail, otherwise false
|
||||||
|
return Game1.MasterPlayer.mailReceived.Contains("JojaMember");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="StaticTiles"/> class.
|
||||||
|
/// Loads the tile files and sets up the tile dictionaries.
|
||||||
|
/// </summary>
|
||||||
|
private StaticTiles()
|
||||||
|
{
|
||||||
|
LoadTilesFiles();
|
||||||
|
SetupTilesDicts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the static and custom tile files.
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadTilesFiles()
|
||||||
|
{
|
||||||
|
if (MainClass.ModHelper is null) return;
|
||||||
|
|
||||||
|
staticTilesData = LoadJsonFile(StaticTilesFileName);
|
||||||
|
customTilesData = LoadJsonFile(CustomTilesFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a conditional lambda function to the conditionals dictionary at runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conditionName">The name of the condition to be added.</param>
|
||||||
|
/// <param name="conditionLambda">The lambda function to be added. It should accept two strings (conditionName and uniqueModID) and return a bool.</param>
|
||||||
|
/// <returns>Returns true if the lambda was added successfully, and false otherwise.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if the conditionName or conditionLambda is null or empty.</exception>
|
||||||
|
public static bool AddConditionalLambda(string conditionName, Func<string, string, bool> conditionLambda)
|
||||||
|
{
|
||||||
|
// Check if the conditionName is not null or empty
|
||||||
|
if (string.IsNullOrEmpty(conditionName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Condition name cannot be null or empty.", nameof(conditionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the conditionLambda is not null
|
||||||
|
if (conditionLambda == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Condition lambda cannot be null.", nameof(conditionLambda));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the conditionName already exists in the dictionary
|
||||||
|
if (conditionals.ContainsKey(conditionName))
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"A conditional with the name '{conditionName}' already exists.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the lambda to the dictionary
|
||||||
|
conditionals.Add(conditionName, conditionLambda);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a location tile dictionary based on the given JSON dictionary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonDict">The JSON dictionary containing location tile data.</param>
|
||||||
|
/// <returns>A dictionary mapping tile coordinates to tile names and categories.</returns>
|
||||||
|
public static Dictionary<(short x, short y), (string name, CATEGORY category)> CreateLocationTileDict(JsonElement locationJson)
|
||||||
|
{
|
||||||
|
var jsonDict = locationJson.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
|
||||||
|
var locationData = new Dictionary<(short x, short y), (string name, CATEGORY category)>(jsonDict.Count);
|
||||||
|
|
||||||
|
// Iterate over the JSON dictionary
|
||||||
|
foreach (var item in jsonDict)
|
||||||
|
{
|
||||||
|
var name = item.Key;
|
||||||
|
|
||||||
|
// Error handling: Check if "x" and "y" properties exist in the JSON object
|
||||||
|
if (!item.Value.TryGetProperty("x", out var xElement) || !item.Value.TryGetProperty("y", out var yElement))
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Missing x or y property for {name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xValues = xElement.EnumerateArray().Select(x => x.GetInt16()).ToArray();
|
||||||
|
var yValues = yElement.EnumerateArray().Select(y => y.GetInt16()).ToArray();
|
||||||
|
|
||||||
|
// Error handling: Ensure that x and y arrays are not empty
|
||||||
|
if (xValues.Length == 0 || yValues.Length == 0)
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Empty x or y array for {name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the "type" property if it exists, otherwise use the default value "Others"
|
||||||
|
var type = item.Value.TryGetProperty("type", out var typeElement) ? typeElement.GetString() : "Others";
|
||||||
|
|
||||||
|
// Obtain the category instance
|
||||||
|
var category = CATEGORY.FromString(type!);
|
||||||
|
|
||||||
|
// Iterate over y and x values, adding entries to the locationData dictionary
|
||||||
|
for (int j = 0; j < yValues.Length; j++)
|
||||||
|
{
|
||||||
|
var y = yValues[j];
|
||||||
|
for (int i = 0; i < xValues.Length; i++)
|
||||||
|
{
|
||||||
|
var x = xValues[i];
|
||||||
|
locationData.TryAdd((x, y), (name, category));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return locationData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the different categories of locations.
|
||||||
|
/// </summary>
|
||||||
|
public enum LocationCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents mod locations with conditional requirements.
|
||||||
|
/// </summary>
|
||||||
|
ModConditional,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents mod locations without conditional requirements.
|
||||||
|
/// </summary>
|
||||||
|
Mod,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents vanilla locations with conditional requirements.
|
||||||
|
/// </summary>
|
||||||
|
VanillaConditional,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents vanilla locations without conditional requirements.
|
||||||
|
/// </summary>
|
||||||
|
Vanilla
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the location category based on the given location name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The location name.</param>
|
||||||
|
/// <returns>The location category.</returns>
|
||||||
|
public static LocationCategory GetLocationCategory(string name)
|
||||||
|
{
|
||||||
|
bool hasDoubleUnderscore = name.Contains("__");
|
||||||
|
bool hasDoubleVerticalBar = name.Contains("||");
|
||||||
|
|
||||||
|
if (hasDoubleUnderscore && hasDoubleVerticalBar)
|
||||||
|
return LocationCategory.ModConditional;
|
||||||
|
if (hasDoubleVerticalBar)
|
||||||
|
return LocationCategory.Mod;
|
||||||
|
if (hasDoubleUnderscore)
|
||||||
|
return LocationCategory.VanillaConditional;
|
||||||
|
|
||||||
|
return LocationCategory.Vanilla;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts location data from a JsonElement into four dictionaries based on their type (mod conditional, mod, vanilla conditional, or vanilla).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="json">A JsonElement containing location data.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A tuple containing four dictionaries:
|
||||||
|
/// - modConditionalLocations: A dictionary of mod locations with conditionals.
|
||||||
|
/// - modLocations: A dictionary of mod locations without conditionals.
|
||||||
|
/// - vanillaConditionalLocations: A dictionary of vanilla locations with conditionals.
|
||||||
|
/// - vanillaLocations: A dictionary of vanilla locations without conditionals.
|
||||||
|
/// Each dictionary maps a location name to another dictionary, which maps tile coordinates (x, y) to a tuple containing the object name and category.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This function iterates over the properties of the input JsonElement and categorizes each location based on the naming conventions.
|
||||||
|
/// If a location has a conditional, the function checks if the condition is met before adding it to the respective dictionary.
|
||||||
|
/// If a mod location is specified, the function checks if the mod is loaded before adding it to the respective dictionary.
|
||||||
|
/// </remarks>
|
||||||
|
public static (
|
||||||
|
Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> modConditionalLocations,
|
||||||
|
Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> modLocations,
|
||||||
|
Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> vanillaConditionalLocations,
|
||||||
|
Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> vanillaLocations
|
||||||
|
) SortLocationsByType(JsonElement json)
|
||||||
|
{
|
||||||
|
var modConditionalLocations = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>();
|
||||||
|
var modLocations = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>();
|
||||||
|
var vanillaConditionalLocations = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>();
|
||||||
|
var vanillaLocations = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>();
|
||||||
|
|
||||||
|
var categoryDicts = new Dictionary<LocationCategory, Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>>
|
||||||
|
{
|
||||||
|
{ LocationCategory.ModConditional, modConditionalLocations },
|
||||||
|
{ LocationCategory.Mod, modLocations },
|
||||||
|
{ LocationCategory.VanillaConditional, vanillaConditionalLocations },
|
||||||
|
{ LocationCategory.Vanilla, vanillaLocations }
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var property in json.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (property.Value.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Invalid value type for {property.Name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string propertyName = property.Name;
|
||||||
|
string uniqueModId = "";
|
||||||
|
|
||||||
|
var splitModId = propertyName.Split("||", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (splitModId.Length == 2)
|
||||||
|
{
|
||||||
|
propertyName = splitModId[0];
|
||||||
|
uniqueModId = splitModId[1];
|
||||||
|
|
||||||
|
if (MainClass.ModHelper == null || !MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var category = GetLocationCategory(propertyName);
|
||||||
|
|
||||||
|
if (category == LocationCategory.VanillaConditional || category == LocationCategory.ModConditional)
|
||||||
|
{
|
||||||
|
var splitPropertyName = propertyName.Split("__", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (splitPropertyName.Length == 2)
|
||||||
|
{
|
||||||
|
propertyName = splitPropertyName[0];
|
||||||
|
string conditionalName = splitPropertyName[1];
|
||||||
|
|
||||||
|
if (conditionals.TryGetValue(conditionalName, out var conditionalFunc))
|
||||||
|
{
|
||||||
|
if (!conditionalFunc(conditionalName, uniqueModId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Unknown conditional name: {conditionalName}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var locationDict = CreateLocationTileDict(property.Value);
|
||||||
|
|
||||||
|
if (categoryDicts.TryGetValue(category, out var targetDict))
|
||||||
|
{
|
||||||
|
targetDict.Add(propertyName, locationDict);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Unknown location category for {propertyName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (modConditionalLocations, modLocations, vanillaConditionalLocations, vanillaLocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merges the contents of the source dictionary into the destination dictionary.
|
||||||
|
/// If a key exists in both dictionaries and the associated values are dictionaries, the function merges them recursively.
|
||||||
|
/// If the values are not dictionaries, the value from the source dictionary overwrites the value in the destination dictionary.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">The type of keys in the dictionaries.</typeparam>
|
||||||
|
/// <typeparam name="TValue">The type of values in the dictionaries.</typeparam>
|
||||||
|
/// <param name="destinationDictionary">The destination dictionary to merge the source dictionary into.</param>
|
||||||
|
/// <param name="sourceDictionary">The source dictionary containing the data to merge into the destination dictionary.</param>
|
||||||
|
private static void MergeDicts<TKey, TValue>(
|
||||||
|
Dictionary<TKey, TValue> destinationDictionary,
|
||||||
|
Dictionary<TKey, TValue> sourceDictionary) where TKey : notnull
|
||||||
|
{
|
||||||
|
if (destinationDictionary == null || sourceDictionary == null)
|
||||||
|
{
|
||||||
|
// Log a warning or throw an exception if either dictionary is null
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var sourceEntry in sourceDictionary)
|
||||||
|
{
|
||||||
|
// Try to get the existing value from the destination dictionary
|
||||||
|
if (destinationDictionary.TryGetValue(sourceEntry.Key, out var existingValue))
|
||||||
|
{
|
||||||
|
// If both existing value and the source value are dictionaries,
|
||||||
|
// merge them recursively
|
||||||
|
if (existingValue is Dictionary<TKey, TValue> existingDictionary
|
||||||
|
&& sourceEntry.Value is Dictionary<TKey, TValue> sourceSubDictionary)
|
||||||
|
{
|
||||||
|
MergeDicts(existingDictionary, sourceSubDictionary);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Overwrite the existing value if it's not a dictionary
|
||||||
|
destinationDictionary[sourceEntry.Key] = sourceEntry.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add a new entry if the key doesn't exist in the destination dictionary
|
||||||
|
destinationDictionary[sourceEntry.Key] = sourceEntry.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a dictionary containing location data and tile information based on the provided JsonElement.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="json">A JsonElement containing the location and tile data.</param>
|
||||||
|
/// <returns>A dictionary containing location data and tile information.</returns>
|
||||||
|
public static Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>> BuildTilesDict(JsonElement json)
|
||||||
|
{
|
||||||
|
// Sort the locations by their types (modConditional, mod, vanillaConditional, vanilla)
|
||||||
|
var (modConditionalLocations, modLocations, vanillaConditionalLocations, vanillaLocations) = SortLocationsByType(json);
|
||||||
|
|
||||||
|
// Create a merged dictionary to store all the location dictionaries
|
||||||
|
var mergedDict = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Merge each category-specific dictionary into the merged dictionary. Prioritize conditional locations whose conditions are true and mod locations where the corresponding mod is loaded. Overwrite their default and vanilla versions, respectively.
|
||||||
|
MergeDicts(mergedDict, modConditionalLocations);
|
||||||
|
MergeDicts(mergedDict, modLocations);
|
||||||
|
MergeDicts(mergedDict, vanillaConditionalLocations);
|
||||||
|
MergeDicts(mergedDict, vanillaLocations);
|
||||||
|
|
||||||
|
return mergedDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up the tile dictionaries (staticTilesDataDict and customTilesDataDict) using the data from the loaded JsonElements.
|
||||||
|
/// </summary>
|
||||||
|
public static void SetupTilesDicts()
|
||||||
|
{
|
||||||
|
if (staticTilesData.HasValue && staticTilesData.Value.ValueKind != JsonValueKind.Undefined)
|
||||||
|
{
|
||||||
|
staticTilesDataDict = BuildTilesDict(staticTilesData.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
staticTilesDataDict = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customTilesData.HasValue && customTilesData.Value.ValueKind != JsonValueKind.Undefined)
|
||||||
|
{
|
||||||
|
customTilesDataDict = BuildTilesDict(customTilesData.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
customTilesDataDict = new Dictionary<string, Dictionary<(short x, short y), (string name, CATEGORY category)>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the tile information (name and optionally category) from the dictionaries based on the specified location and coordinates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The x-coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the tile.</param>
|
||||||
|
/// <param name="currentLocationName">The name of the current location. Defaults to Game1.currentLocation.Name.</param>
|
||||||
|
/// <param name="includeCategory">Specifies whether to include the tile's category in the returned tuple.</param>
|
||||||
|
/// <returns>A tuple containing the tile's name and optionally its category. If the tile is not found, the name will be null and the category will be CATEGORY.Others if requested.</returns>
|
||||||
|
private static (string? name, CATEGORY? category) GetTileInfoAt(int x, int y, string? currentLocationName = null, bool includeCategory = false)
|
||||||
|
{
|
||||||
|
currentLocationName ??= Game1.currentLocation.Name;
|
||||||
|
|
||||||
|
if (customTilesDataDict != null && customTilesDataDict.TryGetValue(currentLocationName, out var customLocationDict))
|
||||||
|
{
|
||||||
|
if (customLocationDict != null && customLocationDict.TryGetValue(((short)x, (short)y), out var customTile))
|
||||||
|
{
|
||||||
|
return (customTile.name, includeCategory ? customTile.category : (CATEGORY?)null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staticTilesDataDict != null && staticTilesDataDict.TryGetValue(currentLocationName, out var staticLocationDict))
|
||||||
|
{
|
||||||
|
if (staticLocationDict != null && staticLocationDict.TryGetValue(((short)x, (short)y), out var staticTile))
|
||||||
|
{
|
||||||
|
return (staticTile.name, includeCategory ? staticTile.category : (CATEGORY?)null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, includeCategory ? CATEGORY.Others : (CATEGORY?)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the tile name from the dictionaries based on the specified location and coordinates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The x-coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the tile.</param>
|
||||||
|
/// <param name="currentLocationName">The name of the current location. Defaults to Game1.currentLocation.Name.</param>
|
||||||
|
/// <returns>The name of the tile if found, or null if not found.</returns>
|
||||||
|
public static string GetStaticTileNameAt(int x, int y, string? currentLocationName = null)
|
||||||
|
{
|
||||||
|
var (name, _) = GetTileInfoAt(x, y, currentLocationName, includeCategory: false);
|
||||||
|
return name ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the tile information (name and category) from the dictionaries based on the specified location and coordinates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The x-coordinate of the tile.</param>
|
||||||
|
/// <param name="y">The y-coordinate of the tile.</param>
|
||||||
|
/// <param name="currentLocationName">The name of the current location. Defaults to Game1.currentLocation.Name.</param>
|
||||||
|
/// <returns>A tuple containing the tile's name and category. If the tile is not found, the name will be null and the category will be CATEGORY.Others.</returns>
|
||||||
|
public static (string? name, CATEGORY category) GetStaticTileInfoAtWithCategory(int x, int y, string? currentLocationName = null)
|
||||||
|
{
|
||||||
|
var (name, category) = GetTileInfoAt(x, y, currentLocationName, includeCategory: true);
|
||||||
|
return (name, category ?? CATEGORY.Others);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -173,7 +173,7 @@ namespace stardew_access.Features
|
||||||
if (!tryMoveTileView(delta)) return;
|
if (!tryMoveTileView(delta)) return;
|
||||||
Vector2 position = this.GetTileCursorPosition();
|
Vector2 position = this.GetTileCursorPosition();
|
||||||
Vector2 tile = this.GetViewingTile();
|
Vector2 tile = this.GetViewingTile();
|
||||||
String? name = TileInfo.getNameAtTile(tile);
|
String? name = TileInfo.GetNameAtTile(tile);
|
||||||
|
|
||||||
// Prepend the player's name if the viewing tile is occupied by the player itself
|
// Prepend the player's name if the viewing tile is occupied by the player itself
|
||||||
if (CurrentPlayer.PositionX == tile.X && CurrentPlayer.PositionY == tile.Y)
|
if (CurrentPlayer.PositionX == tile.X && CurrentPlayer.PositionY == tile.Y)
|
||||||
|
@ -184,7 +184,7 @@ namespace stardew_access.Features
|
||||||
if (name == null)
|
if (name == null)
|
||||||
{
|
{
|
||||||
// Report if a tile is empty or blocked if there is nothing on it
|
// Report if a tile is empty or blocked if there is nothing on it
|
||||||
if (TileInfo.isCollidingAtTile((int)tile.X, (int)tile.Y))
|
if (TileInfo.IsCollidingAtTile(Game1.currentLocation, (int)tile.X, (int)tile.Y))
|
||||||
{
|
{
|
||||||
name = "blocked";
|
name = "blocked";
|
||||||
}
|
}
|
||||||
|
@ -278,11 +278,12 @@ namespace stardew_access.Features
|
||||||
|
|
||||||
private static bool isPositionOnMap(Vector2 position)
|
private static bool isPositionOnMap(Vector2 position)
|
||||||
{
|
{
|
||||||
|
var currentLocation = Game1.currentLocation;
|
||||||
// Check whether the position is a warp point, if so then return true, sometimes warp points are 1 tile off the map for example in coops and barns
|
// Check whether the position is a warp point, if so then return true, sometimes warp points are 1 tile off the map for example in coops and barns
|
||||||
if (TileInfo.isWarpPointAtTile((int)(position.X / Game1.tileSize), (int)(position.Y / Game1.tileSize))) return true;
|
if (TileInfo.isWarpPointAtTile(currentLocation, (int)(position.X / Game1.tileSize), (int)(position.Y / Game1.tileSize))) return true;
|
||||||
|
|
||||||
//position does not take viewport into account since the entire map needs to be checked.
|
//position does not take viewport into account since the entire map needs to be checked.
|
||||||
Map map = Game1.currentLocation.map;
|
Map map = currentLocation.map;
|
||||||
if (position.X < 0 || position.X > map.Layers[0].DisplayWidth) return false;
|
if (position.X < 0 || position.X > map.Layers[0].DisplayWidth) return false;
|
||||||
if (position.Y < 0 || position.Y > map.Layers[0].DisplayHeight) return false;
|
if (position.Y < 0 || position.Y > map.Layers[0].DisplayHeight) return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
|
using System.Text.Json;
|
||||||
namespace stardew_access.Features
|
namespace stardew_access.Features
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a custom enum class and contains the name of groups the objects are divided into for the feature
|
/// Represents categories that objects can belong to. This class provides predefined categories
|
||||||
|
/// accessible as static properties and supports adding new categories at runtime. Predefined categories
|
||||||
|
/// can be accessed like enum values, while both static and dynamic categories can be accessed via the
|
||||||
|
/// `Categories` property or the `FromString` method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CATEGORY
|
/// <remarks>
|
||||||
|
/// The CATEGORY.Others is used as a default value by the FromString method.
|
||||||
|
/// Use the FromString method to obtain an existing category.
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// - Access a predefined category like an enum: CATEGORY.Farmers
|
||||||
|
/// - Obtain a category using the FromString method: CATEGORY.FromString("farmer")
|
||||||
|
/// - Add a new category: CATEGORY.AddNewCategory("custom_category")
|
||||||
|
/// - Retrieve a category using the public dictionary: CATEGORY.Categories["custom_category"]
|
||||||
|
/// - Obtain the string representation of a category: CATEGORY.Farmers.ToString()
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class CATEGORY
|
||||||
{
|
{
|
||||||
private string _typeKeyWord;
|
private readonly string _typeKeyWord;
|
||||||
|
|
||||||
private CATEGORY(string typeKeyWord)
|
private CATEGORY(string typeKeyWord)
|
||||||
{
|
{
|
||||||
|
@ -17,83 +32,135 @@ namespace stardew_access.Features
|
||||||
return _typeKeyWord;
|
return _typeKeyWord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<string, CATEGORY> Categories => _categories;
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, CATEGORY> _categories = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{"farmer", new CATEGORY("farmer")},
|
||||||
|
{"animal", new CATEGORY("animal")},
|
||||||
|
{"npc", new CATEGORY("npc")},
|
||||||
|
{"furniture", new CATEGORY("furniture")},
|
||||||
|
{"flooring", new CATEGORY("flooring")},
|
||||||
|
{"debris", new CATEGORY("debris")},
|
||||||
|
{"crop", new CATEGORY("crop")},
|
||||||
|
{"tree", new CATEGORY("tree")},
|
||||||
|
{"bush", new CATEGORY("bush")},
|
||||||
|
{"building", new CATEGORY("building")},
|
||||||
|
{"mine item", new CATEGORY("mine item")},
|
||||||
|
{"resource clump", new CATEGORY("resource clump")},
|
||||||
|
{"container", new CATEGORY("container")},
|
||||||
|
{"bundle", new CATEGORY("bundle")},
|
||||||
|
{"door", new CATEGORY("door")},
|
||||||
|
{"water", new CATEGORY("water")},
|
||||||
|
{"interactable", new CATEGORY("interactable")},
|
||||||
|
{"decoration", new CATEGORY("decoration")},
|
||||||
|
{"machine", new CATEGORY("machine")},
|
||||||
|
{"bridge", new CATEGORY("bridge")},
|
||||||
|
{"dropped item", new CATEGORY("dropped item")},
|
||||||
|
{"other", new CATEGORY("other")}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a CATEGORY instance by its string name.
|
||||||
|
/// Names are case-insensitive. If the name is not found, returns the 'Others' category.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The string name of the category to retrieve.</param>
|
||||||
|
/// <returns>The CATEGORY instance corresponding to the given name or the 'Others' category if not found.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the provided name is null.</exception>
|
||||||
public static CATEGORY FromString(string name)
|
public static CATEGORY FromString(string name)
|
||||||
{
|
{
|
||||||
if (name == "farmer")
|
if (string.IsNullOrEmpty(name))
|
||||||
return CATEGORY.Farmers;
|
{
|
||||||
else if (name == "animal")
|
throw new ArgumentException("Category name cannot be null or empty.", nameof(name));
|
||||||
return CATEGORY.FarmAnimals;
|
|
||||||
else if (name == "npc")
|
|
||||||
return CATEGORY.NPCs;
|
|
||||||
else if (name == "furniture")
|
|
||||||
return CATEGORY.Furnitures;
|
|
||||||
else if (name == "flooring")
|
|
||||||
return CATEGORY.Flooring;
|
|
||||||
else if (name == "debris")
|
|
||||||
return CATEGORY.Debris;
|
|
||||||
else if (name == "crop")
|
|
||||||
return CATEGORY.Crops;
|
|
||||||
else if (name == "tree")
|
|
||||||
return CATEGORY.Trees;
|
|
||||||
else if (name == "bush")
|
|
||||||
return CATEGORY.Bush;
|
|
||||||
else if (name == "building")
|
|
||||||
return CATEGORY.Buildings;
|
|
||||||
else if (name == "mine item")
|
|
||||||
return CATEGORY.MineItems;
|
|
||||||
else if (name == "resource clump")
|
|
||||||
return CATEGORY.ResourceClumps;
|
|
||||||
else if (name == "container")
|
|
||||||
return CATEGORY.Containers;
|
|
||||||
else if (name == "bundle")
|
|
||||||
return CATEGORY.JunimoBundle;
|
|
||||||
else if (name == "door")
|
|
||||||
return CATEGORY.Doors;
|
|
||||||
else if (name == "water")
|
|
||||||
return CATEGORY.WaterTiles;
|
|
||||||
else if (name == "interactable")
|
|
||||||
return CATEGORY.Interactables;
|
|
||||||
else if (name == "decoration")
|
|
||||||
return CATEGORY.Decor;
|
|
||||||
else if (name == "machine")
|
|
||||||
return CATEGORY.Machines;
|
|
||||||
else if (name == "bridge")
|
|
||||||
return CATEGORY.Bridges;
|
|
||||||
else if (name == "dropped item")
|
|
||||||
return CATEGORY.DroppedItems;
|
|
||||||
else if (name == "other")
|
|
||||||
return CATEGORY.Others;
|
|
||||||
|
|
||||||
return Others;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CATEGORY Farmers = new CATEGORY("farmer");
|
return Categories.TryGetValue(name, out CATEGORY? category) ? category ?? CATEGORY.Others : CATEGORY.Others;
|
||||||
public static CATEGORY FarmAnimals = new CATEGORY("animal");
|
}
|
||||||
public static CATEGORY NPCs = new CATEGORY("npc");
|
|
||||||
public static CATEGORY Furnitures = new CATEGORY("furniture");
|
|
||||||
public static CATEGORY Flooring = new CATEGORY("flooring");
|
|
||||||
public static CATEGORY Debris = new CATEGORY("debris");
|
|
||||||
public static CATEGORY Crops = new CATEGORY("crop");
|
|
||||||
public static CATEGORY Trees = new CATEGORY("tree");
|
|
||||||
public static CATEGORY Bush = new CATEGORY("bush");
|
|
||||||
public static CATEGORY Buildings = new CATEGORY("building");
|
|
||||||
public static CATEGORY MineItems = new CATEGORY("mine item");
|
|
||||||
public static CATEGORY ResourceClumps = new CATEGORY("resource clump");
|
|
||||||
public static CATEGORY Containers = new CATEGORY("container");
|
|
||||||
public static CATEGORY JunimoBundle = new CATEGORY("bundle");
|
|
||||||
public static CATEGORY Doors = new CATEGORY("door"); // Also includes ladders and elevators
|
|
||||||
public static CATEGORY WaterTiles = new CATEGORY("water");
|
|
||||||
public static CATEGORY Interactables = new CATEGORY("interactable");
|
|
||||||
public static CATEGORY Decor = new CATEGORY("decoration");
|
|
||||||
public static CATEGORY Machines = new CATEGORY("machine");
|
|
||||||
public static CATEGORY Bridges = new CATEGORY("bridge");
|
|
||||||
public static CATEGORY DroppedItems = new CATEGORY("dropped item");
|
|
||||||
public static CATEGORY Others = new CATEGORY("other");
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new CATEGORY with the specified name.
|
||||||
|
/// Names are case-insensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the new category to be added.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if a new category was added; false if the category already exists.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if the provided name is null or empty.</exception>
|
||||||
|
public static bool AddNewCategory(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Categories.ContainsKey(name))
|
||||||
|
{
|
||||||
|
_categories[name] = new CATEGORY(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CATEGORY Farmers => FromString("farmer");
|
||||||
|
public static CATEGORY FarmAnimals => FromString("animal");
|
||||||
|
public static CATEGORY NPCs => FromString("npc");
|
||||||
|
public static CATEGORY Furnitures => FromString("furniture");
|
||||||
|
public static CATEGORY Flooring => FromString("flooring");
|
||||||
|
public static CATEGORY Debris => FromString("debris");
|
||||||
|
public static CATEGORY Crops => FromString("crop");
|
||||||
|
public static CATEGORY Trees => FromString("tree");
|
||||||
|
public static CATEGORY Bush => FromString("bush");
|
||||||
|
public static CATEGORY Buildings => FromString("building");
|
||||||
|
public static CATEGORY MineItems => FromString("mine item");
|
||||||
|
public static CATEGORY ResourceClumps => FromString("resource clump");
|
||||||
|
public static CATEGORY Containers => FromString("container");
|
||||||
|
public static CATEGORY JunimoBundle => FromString("bundle");
|
||||||
|
public static CATEGORY Doors => FromString("door");
|
||||||
|
public static CATEGORY WaterTiles => FromString("water");
|
||||||
|
public static CATEGORY Interactables => FromString("interactable");
|
||||||
|
public static CATEGORY Decor => FromString("decoration");
|
||||||
|
public static CATEGORY Machines => FromString("machine");
|
||||||
|
public static CATEGORY Bridges => FromString("bridge");
|
||||||
|
public static CATEGORY DroppedItems => FromString("dropped item");
|
||||||
|
public static CATEGORY Others => FromString("other");
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MachineState
|
public enum MachineState
|
||||||
{
|
{
|
||||||
Ready, Busy, Waiting
|
Ready, Busy, Waiting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a JSON file from the specified file name in the assets folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the JSON file to load.</param>
|
||||||
|
/// <returns>A <see cref="JsonElement"/> containing the deserialized JSON data, or default if an error occurs.</returns>
|
||||||
|
public static JsonElement LoadJsonFile(string fileName)
|
||||||
|
{
|
||||||
|
string filePath = Path.Combine(MainClass.ModHelper!.DirectoryPath, "assets", fileName);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(filePath);
|
||||||
|
return JsonSerializer.Deserialize<JsonElement>(json);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex)
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"{fileName} file not found: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Error parsing {fileName}: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"An error occurred while initializing {fileName}: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ using stardew_access.Patches;
|
||||||
using stardew_access.ScreenReader;
|
using stardew_access.ScreenReader;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using StardewValley.Menus;
|
using StardewValley.Menus;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
|
||||||
namespace stardew_access
|
namespace stardew_access
|
||||||
{
|
{
|
||||||
|
@ -20,7 +21,6 @@ namespace stardew_access
|
||||||
private Harmony? harmony;
|
private Harmony? harmony;
|
||||||
private static IMonitor? monitor;
|
private static IMonitor? monitor;
|
||||||
private static Radar? radarFeature;
|
private static Radar? radarFeature;
|
||||||
private static StaticTiles? sTiles;
|
|
||||||
private static IScreenReader? screenReader;
|
private static IScreenReader? screenReader;
|
||||||
private static IModHelper? modHelper;
|
private static IModHelper? modHelper;
|
||||||
private static TileViewer? tileViewer;
|
private static TileViewer? tileViewer;
|
||||||
|
@ -30,17 +30,6 @@ namespace stardew_access
|
||||||
internal static ModConfig Config { get => config; set => config = value; }
|
internal static ModConfig Config { get => config; set => config = value; }
|
||||||
public static IModHelper? ModHelper { get => modHelper; }
|
public static IModHelper? ModHelper { get => modHelper; }
|
||||||
|
|
||||||
public static StaticTiles STiles
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (sTiles == null)
|
|
||||||
sTiles = new StaticTiles();
|
|
||||||
|
|
||||||
return sTiles;
|
|
||||||
}
|
|
||||||
set => sTiles = value;
|
|
||||||
}
|
|
||||||
public static Radar RadarFeature
|
public static Radar RadarFeature
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -141,10 +130,17 @@ namespace stardew_access
|
||||||
|
|
||||||
helper.Events.Input.ButtonPressed += this.OnButtonPressed;
|
helper.Events.Input.ButtonPressed += this.OnButtonPressed;
|
||||||
helper.Events.GameLoop.UpdateTicked += this.onUpdateTicked;
|
helper.Events.GameLoop.UpdateTicked += this.onUpdateTicked;
|
||||||
|
helper.Events.GameLoop.DayStarted += this.onDayStarted;
|
||||||
AppDomain.CurrentDomain.DomainUnload += OnExit;
|
AppDomain.CurrentDomain.DomainUnload += OnExit;
|
||||||
AppDomain.CurrentDomain.ProcessExit += OnExit;
|
AppDomain.CurrentDomain.ProcessExit += OnExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns the Screen Reader class for other mods to use.</summary>
|
||||||
|
public override object GetApi()
|
||||||
|
{
|
||||||
|
return new API();
|
||||||
|
}
|
||||||
|
|
||||||
public void OnExit(object? sender, EventArgs? e)
|
public void OnExit(object? sender, EventArgs? e)
|
||||||
{
|
{
|
||||||
// This closes the connection with the screen reader, important for linux
|
// This closes the connection with the screen reader, important for linux
|
||||||
|
@ -153,10 +149,10 @@ namespace stardew_access
|
||||||
ScreenReader.CloseScreenReader();
|
ScreenReader.CloseScreenReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Returns the Screen Reader class for other mods to use.</summary>
|
private void onDayStarted(object? sender, DayStartedEventArgs? e)
|
||||||
public override object GetApi()
|
|
||||||
{
|
{
|
||||||
return new API();
|
StaticTiles.LoadTilesFiles();
|
||||||
|
StaticTiles.SetupTilesDicts();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUpdateTicked(object? sender, UpdateTickedEventArgs? e)
|
private void onUpdateTicked(object? sender, UpdateTickedEventArgs? e)
|
||||||
|
@ -166,10 +162,8 @@ namespace stardew_access
|
||||||
|
|
||||||
// Narrates currently selected inventory slot
|
// Narrates currently selected inventory slot
|
||||||
Other.narrateCurrentSlot();
|
Other.narrateCurrentSlot();
|
||||||
|
|
||||||
// Narrate current location's name
|
// Narrate current location's name
|
||||||
Other.narrateCurrentLocation();
|
Other.narrateCurrentLocation();
|
||||||
|
|
||||||
//handle TileCursor update logic
|
//handle TileCursor update logic
|
||||||
TileViewerFeature.update();
|
TileViewerFeature.update();
|
||||||
|
|
||||||
|
@ -179,20 +173,36 @@ namespace stardew_access
|
||||||
if (Config.ReadTile)
|
if (Config.ReadTile)
|
||||||
ReadTileFeature.update();
|
ReadTileFeature.update();
|
||||||
|
|
||||||
|
RunRadarFeatureIfEnabled();
|
||||||
|
|
||||||
|
RunHudMessageNarration();
|
||||||
|
|
||||||
|
RefreshBuildListIfRequired();
|
||||||
|
|
||||||
|
async void RunRadarFeatureIfEnabled()
|
||||||
|
{
|
||||||
if (!RadarFeature.isRunning && Config.Radar)
|
if (!RadarFeature.isRunning && Config.Radar)
|
||||||
{
|
{
|
||||||
RadarFeature.isRunning = true;
|
RadarFeature.isRunning = true;
|
||||||
RadarFeature.Run();
|
RadarFeature.Run();
|
||||||
Task.Delay(RadarFeature.delay).ContinueWith(_ => { RadarFeature.isRunning = false; });
|
await Task.Delay(RadarFeature.delay);
|
||||||
|
RadarFeature.isRunning = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async void RunHudMessageNarration()
|
||||||
|
{
|
||||||
if (!isNarratingHudMessage)
|
if (!isNarratingHudMessage)
|
||||||
{
|
{
|
||||||
isNarratingHudMessage = true;
|
isNarratingHudMessage = true;
|
||||||
Other.narrateHudMessages();
|
Other.narrateHudMessages();
|
||||||
Task.Delay(300).ContinueWith(_ => { isNarratingHudMessage = false; });
|
await Task.Delay(300);
|
||||||
|
isNarratingHudMessage = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RefreshBuildListIfRequired()
|
||||||
|
{
|
||||||
if (Game1.player != null)
|
if (Game1.player != null)
|
||||||
{
|
{
|
||||||
if (Game1.timeOfDay >= 600 && prevDate != CurrentPlayer.Date)
|
if (Game1.timeOfDay >= 600 && prevDate != CurrentPlayer.Date)
|
||||||
|
@ -203,55 +213,63 @@ namespace stardew_access
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnButtonPressed(object? sender, ButtonPressedEventArgs? e)
|
private void OnButtonPressed(object? sender, ButtonPressedEventArgs? e)
|
||||||
{
|
{
|
||||||
if (e == null)
|
if (e == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
void SimulateMouseClicks(Action<int, int> leftClickHandler, Action<int, int> rightClickHandler)
|
||||||
|
{
|
||||||
|
int mouseX = Game1.getMouseX(true);
|
||||||
|
int mouseY = Game1.getMouseY(true);
|
||||||
|
|
||||||
|
if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed())
|
||||||
|
{
|
||||||
|
leftClickHandler(mouseX, mouseY);
|
||||||
|
}
|
||||||
|
else if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed())
|
||||||
|
{
|
||||||
|
rightClickHandler(mouseX, mouseY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Simulate left and right clicks
|
#region Simulate left and right clicks
|
||||||
if (Game1.activeClickableMenu != null && !TextBoxPatch.isAnyTextBoxActive)
|
if (!TextBoxPatch.isAnyTextBoxActive)
|
||||||
{
|
{
|
||||||
bool isCustomizingCharacter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization);
|
if (Game1.activeClickableMenu != null)
|
||||||
|
|
||||||
#region Mouse Click Simulation
|
|
||||||
if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed())
|
|
||||||
{
|
{
|
||||||
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
|
SimulateMouseClicks(
|
||||||
|
(x, y) => Game1.activeClickableMenu.receiveLeftClick(x, y),
|
||||||
|
(x, y) => Game1.activeClickableMenu.receiveRightClick(x, y)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
else if (Game1.currentMinigame != null)
|
||||||
if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed())
|
|
||||||
{
|
{
|
||||||
Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
|
SimulateMouseClicks(
|
||||||
|
(x, y) => Game1.currentMinigame.receiveLeftClick(x, y),
|
||||||
|
(x, y) => Game1.currentMinigame.receiveRightClick(x, y)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game1.currentMinigame != null && !TextBoxPatch.isAnyTextBoxActive)
|
|
||||||
{
|
|
||||||
#region Mouse Click Simulation
|
|
||||||
if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed())
|
|
||||||
{
|
|
||||||
Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed())
|
|
||||||
{
|
|
||||||
Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
if (!Context.IsPlayerFree)
|
if (!Context.IsPlayerFree)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
void Narrate(string message) => MainClass.ScreenReader.Say(message, true);
|
||||||
|
|
||||||
|
bool IsMovementKey(SButton button)
|
||||||
|
{
|
||||||
|
return button.Equals(SButtonExtensions.ToSButton(Game1.options.moveUpButton[0]))
|
||||||
|
|| button.Equals(SButtonExtensions.ToSButton(Game1.options.moveDownButton[0]))
|
||||||
|
|| button.Equals(SButtonExtensions.ToSButton(Game1.options.moveLeftButton[0]))
|
||||||
|
|| button.Equals(SButtonExtensions.ToSButton(Game1.options.moveRightButton[0]));
|
||||||
|
}
|
||||||
|
|
||||||
// Stops the auto walk controller if any movement key(WASD) is pressed
|
// Stops the auto walk controller if any movement key(WASD) is pressed
|
||||||
if (TileViewerFeature.isAutoWalking &&
|
if (TileViewerFeature.isAutoWalking && IsMovementKey(e.Button))
|
||||||
(e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveUpButton[0]))
|
|
||||||
|| e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveDownButton[0]))
|
|
||||||
|| e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveLeftButton[0]))
|
|
||||||
|| e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveRightButton[0]))))
|
|
||||||
{
|
{
|
||||||
TileViewerFeature.stopAutoWalking(wasForced: true);
|
TileViewerFeature.stopAutoWalking(wasForced: true);
|
||||||
}
|
}
|
||||||
|
@ -259,25 +277,17 @@ namespace stardew_access
|
||||||
// Narrate Current Location
|
// Narrate Current Location
|
||||||
if (Config.LocationKey.JustPressed())
|
if (Config.LocationKey.JustPressed())
|
||||||
{
|
{
|
||||||
string toSpeak = $"{Game1.currentLocation.Name}";
|
Narrate(Game1.currentLocation.Name);
|
||||||
MainClass.ScreenReader.Say(toSpeak, true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Narrate Position
|
// Narrate Position
|
||||||
if (Config.PositionKey.JustPressed())
|
if (Config.PositionKey.JustPressed())
|
||||||
{
|
{
|
||||||
string toSpeak;
|
string toSpeak = Config.VerboseCoordinates
|
||||||
if (Config.VerboseCoordinates)
|
? $"X: {CurrentPlayer.PositionX}, Y: {CurrentPlayer.PositionY}"
|
||||||
{
|
: $"{CurrentPlayer.PositionX}, {CurrentPlayer.PositionY}";
|
||||||
toSpeak = $"X: {CurrentPlayer.PositionX}, Y: {CurrentPlayer.PositionY}";
|
Narrate(toSpeak);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
toSpeak = $"{CurrentPlayer.PositionX}, {CurrentPlayer.PositionY}";
|
|
||||||
}
|
|
||||||
|
|
||||||
MainClass.ScreenReader.Say(toSpeak, true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,29 +297,25 @@ namespace stardew_access
|
||||||
if (ModHelper == null)
|
if (ModHelper == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string toSpeak;
|
string toSpeak = Config.HealthNStaminaInPercentage
|
||||||
if (Config.HealthNStaminaInPercentage)
|
? ModHelper.Translation.Get("manuallytriggered.healthnstamina.percent", new { health = CurrentPlayer.PercentHealth, stamina = CurrentPlayer.PercentStamina })
|
||||||
toSpeak = ModHelper.Translation.Get("manuallytriggered.healthnstamina.percent", new { health = CurrentPlayer.PercentHealth, stamina = CurrentPlayer.PercentStamina });
|
: ModHelper.Translation.Get("manuallytriggered.healthnstamina.normal", new { health = CurrentPlayer.CurrentHealth, stamina = CurrentPlayer.CurrentStamina });
|
||||||
else
|
|
||||||
toSpeak = ModHelper.Translation.Get("manuallytriggered.healthnstamina.normal", new { health = CurrentPlayer.CurrentHealth, stamina = CurrentPlayer.CurrentStamina });
|
|
||||||
|
|
||||||
MainClass.ScreenReader.Say(toSpeak, true);
|
Narrate(toSpeak);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Narrate money at hand
|
// Narrate money at hand
|
||||||
if (Config.MoneyKey.JustPressed())
|
if (Config.MoneyKey.JustPressed())
|
||||||
{
|
{
|
||||||
string toSpeak = $"You have {CurrentPlayer.Money}g";
|
Narrate($"You have {CurrentPlayer.Money}g");
|
||||||
MainClass.ScreenReader.Say(toSpeak, true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Narrate time and season
|
// Narrate time and season
|
||||||
if (Config.TimeNSeasonKey.JustPressed())
|
if (Config.TimeNSeasonKey.JustPressed())
|
||||||
{
|
{
|
||||||
string toSpeak = $"Time is {CurrentPlayer.TimeOfDay} and it is {CurrentPlayer.Day} {CurrentPlayer.Date} of {CurrentPlayer.Season}";
|
Narrate($"Time is {CurrentPlayer.TimeOfDay} and it is {CurrentPlayer.Day} {CurrentPlayer.Date} of {CurrentPlayer.Season}");
|
||||||
MainClass.ScreenReader.Say(toSpeak, true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,28 +337,27 @@ namespace stardew_access
|
||||||
TileViewerFeature.HandleInput();
|
TileViewerFeature.HandleInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ErrorLog(string message)
|
private static void LogMessage(string message, LogLevel logLevel)
|
||||||
{
|
{
|
||||||
if (monitor == null)
|
if (monitor == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
monitor.Log(message, LogLevel.Error);
|
monitor.Log(message, logLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ErrorLog(string message)
|
||||||
|
{
|
||||||
|
LogMessage(message, LogLevel.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InfoLog(string message)
|
public static void InfoLog(string message)
|
||||||
{
|
{
|
||||||
if (monitor == null)
|
LogMessage(message, LogLevel.Info);
|
||||||
return;
|
|
||||||
|
|
||||||
monitor.Log(message, LogLevel.Info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DebugLog(string message)
|
public static void DebugLog(string message)
|
||||||
{
|
{
|
||||||
if (monitor == null)
|
LogMessage(message, LogLevel.Debug);
|
||||||
return;
|
|
||||||
|
|
||||||
monitor.Log(message, LogLevel.Debug);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace stardew_access.Patches
|
||||||
if (cueName == "grassyStep" || cueName == "sandyStep" || cueName == "snowyStep" || cueName == "stoneStep" || cueName == "thudStep" || cueName == "woodyStep")
|
if (cueName == "grassyStep" || cueName == "sandyStep" || cueName == "snowyStep" || cueName == "stoneStep" || cueName == "thudStep" || cueName == "woodyStep")
|
||||||
{
|
{
|
||||||
Vector2 nextTile = CurrentPlayer.FacingTile;
|
Vector2 nextTile = CurrentPlayer.FacingTile;
|
||||||
if (TileInfo.isCollidingAtTile((int)nextTile.X, (int)nextTile.Y))
|
if (TileInfo.IsCollidingAtTile(Game1.currentLocation, (int)nextTile.X, (int)nextTile.Y))
|
||||||
{
|
{
|
||||||
if (prevTile != nextTile)
|
if (prevTile != nextTile)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace stardew_access.Patches
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
narrateCallendar(__instance);
|
narrateCalendar(__instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -27,7 +27,7 @@ namespace stardew_access.Patches
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void narrateCallendar(Billboard __instance)
|
private static void narrateCalendar(Billboard __instance)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < __instance.calendarDays.Count; i++)
|
for (int i = 0; i < __instance.calendarDays.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -35,6 +35,7 @@ namespace stardew_access.Patches
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
string toSpeak = $"Day {i + 1}";
|
string toSpeak = $"Day {i + 1}";
|
||||||
|
string currentYearNMonth = $"of {Game1.CurrentSeasonDisplayName}, {Game1.content.LoadString("Strings\\UI:Billboard_Year", Game1.year)}";
|
||||||
|
|
||||||
if (__instance.calendarDays[i].name.Length > 0)
|
if (__instance.calendarDays[i].name.Length > 0)
|
||||||
{
|
{
|
||||||
|
@ -46,13 +47,16 @@ namespace stardew_access.Patches
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Game1.dayOfMonth == i + 1)
|
if (Game1.dayOfMonth == i + 1)
|
||||||
toSpeak += $", Current";
|
toSpeak = $"Current {toSpeak}";
|
||||||
|
|
||||||
if (billboardQueryKey != toSpeak)
|
if (billboardQueryKey != toSpeak)
|
||||||
{
|
{
|
||||||
billboardQueryKey = toSpeak;
|
billboardQueryKey = toSpeak;
|
||||||
|
if (i == 0) toSpeak = $"{toSpeak} {currentYearNMonth}";
|
||||||
MainClass.ScreenReader.Say(toSpeak, true);
|
MainClass.ScreenReader.Say(toSpeak, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System.Text.Json;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
using StardewValley.Menus;
|
using StardewValley.Menus;
|
||||||
|
using static stardew_access.Features.Utils;
|
||||||
|
|
||||||
namespace stardew_access.Patches
|
namespace stardew_access.Patches
|
||||||
{
|
{
|
||||||
|
@ -7,28 +9,69 @@ namespace stardew_access.Patches
|
||||||
{
|
{
|
||||||
private static bool isRunning = false;
|
private static bool isRunning = false;
|
||||||
private static int saveGameIndex = -1;
|
private static int saveGameIndex = -1;
|
||||||
public static string characterCreationMenuQueryKey = " ";
|
private static string characterCreationMenuQueryKey = " ";
|
||||||
public static string prevPants = " ";
|
private static string prevPants = " ";
|
||||||
public static string prevShirt = " ";
|
private static string prevShirt = " ";
|
||||||
public static string prevHair = " ";
|
private static string prevHair = " ";
|
||||||
public static string prevAccessory = " ";
|
private static string prevAccessory = " ";
|
||||||
public static string prevSkin = " ";
|
private static string prevSkin = " ";
|
||||||
public static string prevEyeColor = " ";
|
private static string prevEyeColor = " ";
|
||||||
public static string prevEyeColorHue = " ";
|
private static string prevEyeColorHue = " ";
|
||||||
public static string prevEyeColorSaturation = " ";
|
private static string prevEyeColorSaturation = " ";
|
||||||
public static string prevEyeColorValue = " ";
|
private static string prevEyeColorValue = " ";
|
||||||
public static string prevHairColor = " ";
|
private static string prevHairColor = " ";
|
||||||
public static string prevHairColorHue = " ";
|
private static string prevHairColorHue = " ";
|
||||||
public static string prevHairColorSaturation = " ";
|
private static string prevHairColorSaturation = " ";
|
||||||
public static string prevHairColorValue = " ";
|
private static string prevHairColorValue = " ";
|
||||||
public static string prevPantsColor = " ";
|
private static string prevPantsColor = " ";
|
||||||
public static string prevPantsColorHue = " ";
|
private static string prevPantsColorHue = " ";
|
||||||
public static string prevPantsColorSaturation = " ";
|
private static string prevPantsColorSaturation = " ";
|
||||||
public static string prevPantsColorValue = " ";
|
private static string prevPantsColorValue = " ";
|
||||||
public static string prevPetName = " ";
|
private static string prevPet = " ";
|
||||||
public static bool characterDesignToggle = false;
|
private static bool characterDesignToggle = false;
|
||||||
public static bool characterDesignToggleShouldSpeak = true;
|
private static bool characterDesignToggleShouldSpeak = true;
|
||||||
public static ClickableComponent? currentComponent = null;
|
private static ClickableComponent? currentComponent = null;
|
||||||
|
private static Dictionary<string, Dictionary<int, string>> descriptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_descriptions == null)
|
||||||
|
{
|
||||||
|
_descriptions = LoadDescriptionJson();
|
||||||
|
}
|
||||||
|
return _descriptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static Dictionary<string, Dictionary<int, string>>? _descriptions;
|
||||||
|
|
||||||
|
private static Dictionary<string, Dictionary<int, string>> LoadDescriptionJson()
|
||||||
|
{
|
||||||
|
MainClass.DebugLog("Attempting to load json");
|
||||||
|
JsonElement jsonElement = LoadJsonFile("new-character-appearance-descriptions.json");
|
||||||
|
|
||||||
|
if (jsonElement.ValueKind == JsonValueKind.Undefined)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Dictionary<int, string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, Dictionary<int, string>> result = new Dictionary<string, Dictionary<int, string>>();
|
||||||
|
|
||||||
|
foreach (JsonProperty category in jsonElement.EnumerateObject())
|
||||||
|
{
|
||||||
|
Dictionary<int, string> innerDictionary = new Dictionary<int, string>();
|
||||||
|
|
||||||
|
foreach (JsonProperty item in category.Value.EnumerateObject())
|
||||||
|
{
|
||||||
|
int index = int.Parse(item.Name);
|
||||||
|
innerDictionary[index] = item.Value.GetString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
result[category.Name] = innerDictionary;
|
||||||
|
MainClass.InfoLog($"Loaded key '{category.Name}' with {innerDictionary.Count} entries in the sub dictionary.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
internal static void DrawPatch(CharacterCustomization __instance, bool ___skipIntro,
|
internal static void DrawPatch(CharacterCustomization __instance, bool ___skipIntro,
|
||||||
ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox,
|
ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox,
|
||||||
|
@ -126,24 +169,24 @@ namespace stardew_access.Patches
|
||||||
private static string getChangesToSpeak(CharacterCustomization __instance)
|
private static string getChangesToSpeak(CharacterCustomization __instance)
|
||||||
{
|
{
|
||||||
string toSpeak = "";
|
string toSpeak = "";
|
||||||
string currentPetName = getCurrentPetName();
|
string currentPet = GetCurrentPet();
|
||||||
string currentSkin = getCurrentSkin();
|
string currentSkin = GetCurrentSkin();
|
||||||
string currentHair = getCurrentHair();
|
string currentHair = GetCurrentHair();
|
||||||
string currentShirt = getCurrentShirt();
|
string currentShirt = GetCurrentShirt();
|
||||||
string currentPants = getCurrentPants();
|
string currentPants = GetCurrentPants();
|
||||||
string currentAccessory = getCurrentAccessory();
|
string currentAccessory = GetCurrentAccessory();
|
||||||
string currentEyeColor = getCurrentEyeColor();
|
string currentEyeColor = GetCurrentEyeColor();
|
||||||
string currentEyeColorHue = getCurrentEyeColorHue(__instance);
|
string currentEyeColorHue = GetCurrentEyeColorHue(__instance);
|
||||||
string currentEyeColorSaturation = getCurrentEyeColorSaturation(__instance);
|
string currentEyeColorSaturation = GetCurrentEyeColorSaturation(__instance);
|
||||||
string currentEyeColorValue = getCurrentEyeColorValue(__instance);
|
string currentEyeColorValue = GetCurrentEyeColorValue(__instance);
|
||||||
string currentHairColor = getCurrentHairColor();
|
string currentHairColor = GetCurrentHairColor();
|
||||||
string currentHairColorHue = getCurrentHairColorHue(__instance);
|
string currentHairColorHue = GetCurrentHairColorHue(__instance);
|
||||||
string currentHairColorSaturation = getCurrentHairColorSaturation(__instance);
|
string currentHairColorSaturation = GetCurrentHairColorSaturation(__instance);
|
||||||
string currentHairColorValue = getCurrentHairColorValue(__instance);
|
string currentHairColorValue = GetCurrentHairColorValue(__instance);
|
||||||
string currentPantsColor = getCurrentPantsColor();
|
string currentPantsColor = GetCurrentPantsColor();
|
||||||
string currentPantsColorHue = getCurrentPantsColorHue(__instance);
|
string currentPantsColorHue = GetCurrentPantsColorHue(__instance);
|
||||||
string currentPantsColorSaturation = getCurrentPantsColorSaturation(__instance);
|
string currentPantsColorSaturation = GetCurrentPantsColorSaturation(__instance);
|
||||||
string currentPantsColorValue = getCurrentPantsColorValue(__instance);
|
string currentPantsColorValue = GetCurrentPantsColorValue(__instance);
|
||||||
|
|
||||||
if (characterDesignToggle)
|
if (characterDesignToggle)
|
||||||
{
|
{
|
||||||
|
@ -339,11 +382,11 @@ namespace stardew_access.Patches
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevPetName != currentPetName)
|
if (prevPet != currentPet)
|
||||||
{
|
{
|
||||||
prevPetName = currentPetName;
|
prevPet = currentPet;
|
||||||
if (currentPetName != "")
|
if (currentPet != "")
|
||||||
toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}";
|
toSpeak = $"{toSpeak} \n Current Pet: {currentPet}";
|
||||||
}
|
}
|
||||||
return toSpeak.Trim();
|
return toSpeak.Trim();
|
||||||
}
|
}
|
||||||
|
@ -693,145 +736,99 @@ namespace stardew_access.Patches
|
||||||
}
|
}
|
||||||
|
|
||||||
// Most values (exception noted below) are 0 indexed internally but visually start from 1. Thus we increment before returning.
|
// Most values (exception noted below) are 0 indexed internally but visually start from 1. Thus we increment before returning.
|
||||||
private static string getCurrentSkin()
|
private static string GetCurrentPet(bool lessInfo = false)
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Skin"))
|
|
||||||
return $"Skin tone: {Game1.player.skin.Value + 1}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentHair()
|
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Hair"))
|
|
||||||
return $"hair style: {Game1.player.hair.Value + 1}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentShirt()
|
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Shirt"))
|
|
||||||
return $"Shirt: {Game1.player.shirt.Value + 1}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentPants()
|
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Pants Style"))
|
|
||||||
return $"Pants: {Game1.player.pants.Value + 1}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentAccessory()
|
|
||||||
{
|
|
||||||
// Internally accessory starts from -1 while displaying +1 on screen.
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Acc"))
|
|
||||||
return $"accessory: {Game1.player.accessory.Value + 2}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentEyeColor()
|
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524)))
|
|
||||||
return $"Eye color: {Game1.player.newEyeColor.R}, {Game1.player.newEyeColor.G}, {Game1.player.newEyeColor.B}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentEyeColorHue(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(522, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentEyeColorSaturation(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(523, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentEyeColorValue(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(524, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentHairColor()
|
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527)))
|
|
||||||
return $"Hair color: {Game1.player.hairstyleColor.R}, {Game1.player.hairstyleColor.G}, {Game1.player.hairstyleColor.B}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentHairColorHue(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(525, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentHairColorSaturation(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(526, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentHairColorValue(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(527, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentPantsColor()
|
|
||||||
{
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530)))
|
|
||||||
return $"Pants color: {Game1.player.pantsColor.R}, {Game1.player.pantsColor.G}, {Game1.player.pantsColor.B}";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentPantsColorHue(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(528, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentPantsColorSaturation(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(529, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentPantsColorValue(CharacterCustomization __instance)
|
|
||||||
{
|
|
||||||
SliderBar sb = getCurrentSliderBar(530, __instance)!;
|
|
||||||
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530)))
|
|
||||||
return sb.value!.ToString();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getCurrentPetName()
|
|
||||||
{
|
{
|
||||||
if (currentComponent != null && currentComponent.name == "Pet")
|
if (currentComponent != null && currentComponent.name == "Pet")
|
||||||
{
|
{
|
||||||
return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed;
|
int whichPetBreed = Game1.player.whichPetBreed + 1;
|
||||||
|
|
||||||
|
if (!lessInfo)
|
||||||
|
{
|
||||||
|
string petType = Game1.player.catPerson ? "Cat" : "Dog";
|
||||||
|
if (descriptions.TryGetValue(petType, out var innerDict) && innerDict.TryGetValue(whichPetBreed, out var description))
|
||||||
|
{
|
||||||
|
return description;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
MainClass.ErrorLog($"Warning: Description for {petType} with index {whichPetBreed} not found in the dictionary.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{(Game1.player.catPerson ? "Cat" : "Dog")} #{whichPetBreed + 1}";
|
||||||
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetCurrentAttributeValue(string componentName, Func<int> getValue, bool lessInfo = false)
|
||||||
|
{
|
||||||
|
if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == componentName))
|
||||||
|
{
|
||||||
|
int index = getValue();
|
||||||
|
|
||||||
|
if (!lessInfo)
|
||||||
|
{
|
||||||
|
if (descriptions.TryGetValue(componentName, out var innerDict))
|
||||||
|
{
|
||||||
|
if (innerDict.TryGetValue(index, out var description))
|
||||||
|
{
|
||||||
|
return description;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Warning: Description for {componentName} with index {index} not found in the inner dictionary.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainClass.ErrorLog($"Warning: Description for {componentName} not found in the outer dictionary.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $"{componentName}: {index}";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCurrentSkin(bool lessInfo = false) => GetCurrentAttributeValue("Skin", () => Game1.player.skin.Value + 1, lessInfo);
|
||||||
|
|
||||||
|
private static string GetCurrentHair(bool lessInfo = false) => GetCurrentAttributeValue("Hair", () => Game1.player.hair.Value + 1, lessInfo);
|
||||||
|
|
||||||
|
private static string GetCurrentShirt(bool lessInfo = false) => GetCurrentAttributeValue("Shirt", () => Game1.player.shirt.Value + 1, lessInfo);
|
||||||
|
|
||||||
|
private static string GetCurrentPants(bool lessInfo = false) => GetCurrentAttributeValue("Pants Style", () => Game1.player.pants.Value + 1, lessInfo);
|
||||||
|
|
||||||
|
private static string GetCurrentAccessory(bool lessInfo = false) => GetCurrentAttributeValue("Accessory", () => Game1.player.accessory.Value + 2, lessInfo);
|
||||||
|
|
||||||
|
private static string GetCurrentColorAttributeValue(string componentName, int minID, int maxID, Func<string> getValue)
|
||||||
|
{
|
||||||
|
if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= minID && currentComponent.myID <= maxID)))
|
||||||
|
{
|
||||||
|
return $"{componentName}: {getValue()}";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCurrentEyeColor() => GetCurrentColorAttributeValue("Eye color", 522, 524, () => $"{Game1.player.newEyeColor.R}, {Game1.player.newEyeColor.G}, {Game1.player.newEyeColor.B}");
|
||||||
|
|
||||||
|
private static string GetCurrentEyeColorHue(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Eye color hue", 522, 524, () => (getCurrentSliderBar(522, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentEyeColorSaturation(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Eye color saturation", 522, 524, () => (getCurrentSliderBar(523, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentEyeColorValue(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Eye color value", 522, 524, () => (getCurrentSliderBar(524, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentHairColor() => GetCurrentColorAttributeValue("Hair color", 525, 527, () => $"{Game1.player.hairstyleColor.R}, {Game1.player.hairstyleColor.G}, {Game1.player.hairstyleColor.B}");
|
||||||
|
|
||||||
|
private static string GetCurrentHairColorHue(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Hair color hue", 525, 527, () => (getCurrentSliderBar(525, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentHairColorSaturation(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Hair color saturation", 525, 527, () => (getCurrentSliderBar(526, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentHairColorValue(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Hair color value", 525, 527, () => (getCurrentSliderBar(527, __instance)!.value!.ToString()));
|
||||||
|
private static string GetCurrentPantsColor() => GetCurrentColorAttributeValue("Pants color", 528, 530, () => $"{Game1.player.pantsColor.R}, {Game1.player.pantsColor.G}, {Game1.player.pantsColor.B}");
|
||||||
|
|
||||||
|
private static string GetCurrentPantsColorHue(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Pants color hue", 528, 530, () => (getCurrentSliderBar(528, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentPantsColorSaturation(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Pants color saturation", 528, 530, () => (getCurrentSliderBar(529, __instance)!.value!.ToString()));
|
||||||
|
|
||||||
|
private static string GetCurrentPantsColorValue(CharacterCustomization __instance) => GetCurrentColorAttributeValue("Pants color value", 528, 530, () => (getCurrentSliderBar(530, __instance)!.value!.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public static class NativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
public static extern bool SetDllDirectory(string lpPathName);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"Egg Festival": {
|
||||||
|
"21,55": "Egg Festival Shop"
|
||||||
|
},
|
||||||
|
"Flower Dance": {
|
||||||
|
"28,37": "Flower Dance Shop"
|
||||||
|
},
|
||||||
|
"Luau": {
|
||||||
|
"35,13": "Soup Pot"
|
||||||
|
},
|
||||||
|
"Spirit's Eve": {
|
||||||
|
"25,49": "Spirit's Eve Shop"
|
||||||
|
},
|
||||||
|
"Stardew Valley Fair": {
|
||||||
|
"16,52": "Stardew Valley Fair Shop",
|
||||||
|
"23,62": "Slingshot Game",
|
||||||
|
"34,65": "Purchase Star Tokens",
|
||||||
|
"33,70": "The Wheel",
|
||||||
|
"23,70": "Fishing Challenge",
|
||||||
|
"47,87": "Fortune Teller",
|
||||||
|
"38,59": "Grange Display",
|
||||||
|
"30,56": "Strength Game",
|
||||||
|
"26,33": "Free Burgers"
|
||||||
|
},
|
||||||
|
"Festival of Ice": {
|
||||||
|
"55,31": "Travelling Cart"
|
||||||
|
},
|
||||||
|
"Feast of the Winter Star": {
|
||||||
|
"18,61": "Feast of the Winter Star Shop"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
{
|
||||||
|
"Cat": {
|
||||||
|
"1": "Orange cat",
|
||||||
|
"2": "Gray Tabby cat with white belly",
|
||||||
|
"3": "Yellow cat with purple collar"
|
||||||
|
},
|
||||||
|
"Dog": {
|
||||||
|
"1": "Golden-brown Hound with blue collar",
|
||||||
|
"2": "Brown Shepherd",
|
||||||
|
"3": "Tan body, brown eared, long furred terrier"
|
||||||
|
},
|
||||||
|
"Skin": {
|
||||||
|
"1": "Pale, beige undertone",
|
||||||
|
"2": "Tan, pink undertone",
|
||||||
|
"3": "Pale, pink undertone",
|
||||||
|
"4": "Pale",
|
||||||
|
"5": "Brown, red undertone",
|
||||||
|
"6": "Brown, pink undertone",
|
||||||
|
"7": "Brown, warm tone",
|
||||||
|
"8": "Beige, orange undertone",
|
||||||
|
"9": "Light Brown",
|
||||||
|
"10": "Pale, pink overtone",
|
||||||
|
"11": "Pale, grey undertone",
|
||||||
|
"12": "Tan, warm tone",
|
||||||
|
"13": "Pale Green",
|
||||||
|
"14": "Pale Fuchsia",
|
||||||
|
"15": "Brown, full tone",
|
||||||
|
"16": "Tan, red overtone",
|
||||||
|
"17": "Pale Blue",
|
||||||
|
"18": "Green",
|
||||||
|
"19": "Pale Red",
|
||||||
|
"20": "Pale Purple",
|
||||||
|
"21": "Yellow, full tone",
|
||||||
|
"22": "Gray",
|
||||||
|
"23": "Pale, yellow undertone",
|
||||||
|
"24": "Pale, ivory undertone"
|
||||||
|
},
|
||||||
|
"Hair": {
|
||||||
|
"1": "Right-side part, short and unkempt",
|
||||||
|
"2": "Middle-part, six inches length",
|
||||||
|
"3": "Left-side part, swept bangs, combed back",
|
||||||
|
"4": "Afro",
|
||||||
|
"5": "Right-side part, unkempt with bangs",
|
||||||
|
"6": "Shaved back and sides",
|
||||||
|
"7": "Right-part open pompadour, chin length",
|
||||||
|
"8": "Right-part, short and combed back",
|
||||||
|
"9": "Right-part with bangs, large wayward spikes",
|
||||||
|
"10": "Right-part, side bob",
|
||||||
|
"11": "Pompadour, short and combed back",
|
||||||
|
"12": "Short with faded sides, combed back",
|
||||||
|
"13": "Middle-part, low ponytail",
|
||||||
|
"14": "Wayward dreads, six inches length, undershave",
|
||||||
|
"15": "Left-part with long bang, combed back",
|
||||||
|
"16": "Middle-part, 4 inches length, undercut",
|
||||||
|
"17": "Right-part, high ponytail, swept bangs",
|
||||||
|
"18": "Right-side part, shoulder length, low pigtails",
|
||||||
|
"19": "Right-side part, short with long swept bangs",
|
||||||
|
"20": "Updo, three tight buns on top of head",
|
||||||
|
"21": "Short and combed",
|
||||||
|
"22": "Right-side part, short, high pigtails",
|
||||||
|
"23": "Right-side part with bangs, tight high bun",
|
||||||
|
"24": "Right-side part with bangs, unkempt, six inches",
|
||||||
|
"25": "Right-side part, swept bangs, mid-back length",
|
||||||
|
"26": "Fifties style, teased, curly ended bob",
|
||||||
|
"27": "Middle-part, thigh-length",
|
||||||
|
"28": "Right-side part, swept bangs, chin length",
|
||||||
|
"29": "Middle-part, waist length, low ponytail",
|
||||||
|
"30": "Waist length with bangs, straight, tapered ends",
|
||||||
|
"31": "Right-side part with bangs, low pigtails",
|
||||||
|
"32": "Dual twisted side-buns, Princess Leia style",
|
||||||
|
"33": "Right-side part, swept bangs, short",
|
||||||
|
"34": "Right-side part, hip-length, pigtail braids",
|
||||||
|
"35": "Right-side part, mid-back length, pigtail braids",
|
||||||
|
"36": "High ponytail, mini bangs",
|
||||||
|
"37": "Middle-part, swept over right shoulder",
|
||||||
|
"38": "Right side part with bangs, high pigtails",
|
||||||
|
"39": "Black hairband, chin length",
|
||||||
|
"40": "Black hairband with bangs, shoulder length",
|
||||||
|
"41": "Left-side part, loose curls, shoulder length",
|
||||||
|
"42": "Shoulder length with mini bangs, curly",
|
||||||
|
"43": "Long on top with highlights, combed back",
|
||||||
|
"44": "Right-side part, swept bangs, short",
|
||||||
|
"45": "Middle-part, fade with 4 inches on top",
|
||||||
|
"46": "Cornrows, chin length",
|
||||||
|
"47": "Left-side part, short and combed",
|
||||||
|
"48": "Middle-part, swept bangs, chin length",
|
||||||
|
"49": "Middle-part, unkempt, partial ponytail",
|
||||||
|
"50": "Liberty spike style, shaved sides",
|
||||||
|
"51": "Donut cut, shoulder length",
|
||||||
|
"52": "Donut cut, short",
|
||||||
|
"53": "Bald or shaved",
|
||||||
|
"54": "Shaved, half-inch length, widow's peak",
|
||||||
|
"55": "Shaved, half-inch length, unkempt",
|
||||||
|
"56": "Shaved, half-inch length, straight hairline",
|
||||||
|
"101": "Left-side part with bangs, wavy, waist length",
|
||||||
|
"102": "Right-side part, hip-length, curly",
|
||||||
|
"103": "Right-side part, waist length, straight",
|
||||||
|
"104": "Middle-part, waist length, low ponytail",
|
||||||
|
"105": "Middle-part, waist length, high braid",
|
||||||
|
"106": "Right-side part with bangs, swept to shoulder",
|
||||||
|
"107": "Right-side part, unkempt, swept to shoulder",
|
||||||
|
"108": "Bob with bangs",
|
||||||
|
"109": "Left-side part, short, combed",
|
||||||
|
"110": "Wavy with bangs, 8 inches",
|
||||||
|
"111": "Wavy with bangs, shoulder length",
|
||||||
|
"112": "Dreads, neat, 4 inches length",
|
||||||
|
"113": "Short and unkempt",
|
||||||
|
"114": "Middle-part, six inches length",
|
||||||
|
"115": "Right-side part, shoulder length, unkempt",
|
||||||
|
"116": "Middle-part, teased, shoulder length",
|
||||||
|
"117": "Middle-part with bangs, short",
|
||||||
|
"118": "Left-side part with bangs, unkempt, short"
|
||||||
|
},
|
||||||
|
"Shirt": {
|
||||||
|
"1": "Red, denim overalls",
|
||||||
|
"2": "Brown button up",
|
||||||
|
"3": "Light Green, brown belt",
|
||||||
|
"4": "Black, gray splat design",
|
||||||
|
"5": "Black skull design",
|
||||||
|
"6": "Blue Gray, cloud design",
|
||||||
|
"7": "Cream, light blue horizontal stripe",
|
||||||
|
"8": "Green, denim overalls",
|
||||||
|
"9": "Yellow, brown horizontal zig zag",
|
||||||
|
"10": "Blue Green, cloud design",
|
||||||
|
"11": "Black, white letter A",
|
||||||
|
"12": "Green, collar cinches",
|
||||||
|
"13": "Lime Green, green stripes",
|
||||||
|
"14": "Red, white horizontal stripes",
|
||||||
|
"15": "Black, white ribcage design",
|
||||||
|
"16": "Brown, Tan, Light Brown stripes",
|
||||||
|
"17": "Blue, yellow dots",
|
||||||
|
"18": "Green, brown suspenders",
|
||||||
|
"19": "Brown jacket, Gray tee",
|
||||||
|
"20": "White, blue kerchief",
|
||||||
|
"21": "Green tank, Gray tee",
|
||||||
|
"22": "Ochre, green horizontal stripe",
|
||||||
|
"23": "Red button up",
|
||||||
|
"24": "Green button up",
|
||||||
|
"25": "Light Blue button up",
|
||||||
|
"26": "Blue button up",
|
||||||
|
"27": "Sea Green, horizontal white stripe",
|
||||||
|
"28": "Purple, light equal sign design",
|
||||||
|
"29": "Black, purple heart design",
|
||||||
|
"30": "White vertical gradient",
|
||||||
|
"31": "Brown jacket, Black shirt",
|
||||||
|
"32": "Brown Gray, angled button up",
|
||||||
|
"33": "Red, brown belt",
|
||||||
|
"34": "Green, strung collar",
|
||||||
|
"35": "Green bodice, gold belt, brown sleeves",
|
||||||
|
"36": "Red, white collar, buttoned",
|
||||||
|
"37": "Light Purple, zippered",
|
||||||
|
"38": "Gray to Black vertical gradient",
|
||||||
|
"39": "White, wide collar",
|
||||||
|
"40": "Sea Green and Brown stripes",
|
||||||
|
"41": "Purple vertical gradient",
|
||||||
|
"42": "White, horizontal cream stripe",
|
||||||
|
"43": "Green vertical gradient, belt",
|
||||||
|
"44": "Blue vertical gradient",
|
||||||
|
"45": "Blue, strung collar, white spot",
|
||||||
|
"46": "Brown vertical gradient",
|
||||||
|
"47": "Purple Vertical Gradient",
|
||||||
|
"48": "Brown, silver belt",
|
||||||
|
"49": "Black, gray bat design",
|
||||||
|
"50": "Light Purple, purple stripe",
|
||||||
|
"51": "Light Pink tank, purple shirt",
|
||||||
|
"52": "Pink tank, light purple tee",
|
||||||
|
"53": "Purple, vertical rainbow column",
|
||||||
|
"54": "Black, green belt",
|
||||||
|
"55": "Sea Green, white shoulder stripe",
|
||||||
|
"56": "Red, horizontal yellow stripe",
|
||||||
|
"57": "Lime Green, wide collar",
|
||||||
|
"58": "White and Gray stripes, red vest",
|
||||||
|
"59": "Blue, light blue shoulder stripe",
|
||||||
|
"60": "Ochre, yellow shoulder stripe",
|
||||||
|
"61": "Blue, wide collar",
|
||||||
|
"62": "Tan, stripes and dots",
|
||||||
|
"63": "Blue, white collar and stripe",
|
||||||
|
"64": "Red, silver collar",
|
||||||
|
"65": "Patchwork Blue",
|
||||||
|
"66": "Green, white undershirt",
|
||||||
|
"67": "Gray, mouse face design",
|
||||||
|
"68": "Yellow, low overalls",
|
||||||
|
"69": "Light Green, upper frog face",
|
||||||
|
"70": "Green, brown belt",
|
||||||
|
"71": "Fuchsia, light purple stripe",
|
||||||
|
"72": "White, denim overalls, brown belt",
|
||||||
|
"73": "Cream crop hoodie, blue tank",
|
||||||
|
"74": "Dark Blue and Purple horizontal split",
|
||||||
|
"75": "Blue, red overalls, brown belt",
|
||||||
|
"76": "Black, green mushroom cloud design",
|
||||||
|
"77": "Light Purple, brown belt",
|
||||||
|
"78": "White, tongue out frowny face",
|
||||||
|
"79": "Purple, white kerchief",
|
||||||
|
"80": "Black, blue overalls",
|
||||||
|
"81": "Gray, white shoulder stripe",
|
||||||
|
"82": "Green, light green waist stripe",
|
||||||
|
"83": "Dark Blue",
|
||||||
|
"84": "Black, wide collar",
|
||||||
|
"85": "Black",
|
||||||
|
"86": "Red, button up, open neck",
|
||||||
|
"87": "Teal, brown suspenders",
|
||||||
|
"88": "White button up, red kerchief",
|
||||||
|
"89": "Yellow, green vest",
|
||||||
|
"90": "Purple Bowling Style",
|
||||||
|
"91": "Black Hoodie",
|
||||||
|
"92": "Green, collared, white kerchief",
|
||||||
|
"93": "Pink, light pink shoulder stripe",
|
||||||
|
"94": "White, black spots",
|
||||||
|
"95": "Brown, red and yellow tie",
|
||||||
|
"96": "Yellow, black eyes with blush",
|
||||||
|
"97": "Green, dark green spots",
|
||||||
|
"98": "Gray, button up, dark vertical stripe",
|
||||||
|
"99": "Black peacoat, white shirt collar",
|
||||||
|
"100": "Purple, black overalls",
|
||||||
|
"101": "Light Blue, horizontal dark stripe",
|
||||||
|
"102": "Black, white front",
|
||||||
|
"103": "Canvas, blond leather belt",
|
||||||
|
"104": "Gray stripes, black overalls",
|
||||||
|
"105": "Green and Teal stripes",
|
||||||
|
"106": "Blue, white letter J",
|
||||||
|
"107": "Green and Black Horizontal split",
|
||||||
|
"108": "Fuchsia, white shoulder stripe",
|
||||||
|
"109": "Brown Orange",
|
||||||
|
"110": "Purple button up, dark vertical stripe",
|
||||||
|
"111": "Brown button up, dark vertical stripe",
|
||||||
|
"112": "Olive green, dark vertical stripe"
|
||||||
|
},
|
||||||
|
"Pants Style": {
|
||||||
|
"1": "Long",
|
||||||
|
"2": "Shorts",
|
||||||
|
"3": "Long Skirt",
|
||||||
|
"4": "Skirt"
|
||||||
|
},
|
||||||
|
"Accessory": {
|
||||||
|
"1": "Blank",
|
||||||
|
"2": "Full beard and mustache",
|
||||||
|
"3": "Full mustache",
|
||||||
|
"4": "Full mustache wrinkles",
|
||||||
|
"5": "Goatee",
|
||||||
|
"6": "Mutton chops",
|
||||||
|
"7": "Full beard and mustache, untrimmed",
|
||||||
|
"8": "Gold earrings",
|
||||||
|
"9": "Turquoise earrings",
|
||||||
|
"10": "Black full-frame glasses",
|
||||||
|
"11": "Lipstick",
|
||||||
|
"12": "Top-frame glasses",
|
||||||
|
"13": "Bushy eyebrows",
|
||||||
|
"14": "Robo-visor",
|
||||||
|
"15": "Circular black frame glasses",
|
||||||
|
"16": "Red necklace",
|
||||||
|
"17": "Black sunglasses",
|
||||||
|
"18": "Blue necklace",
|
||||||
|
"19": "Gray sunglasses",
|
||||||
|
"20": "Orange beak"
|
||||||
|
}
|
||||||
|
}
|
|
@ -674,6 +674,7 @@
|
||||||
"type": "decoration"
|
"type": "decoration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"farmhouse": null,
|
||||||
"fishshop": {
|
"fishshop": {
|
||||||
"Shop Counter": {
|
"Shop Counter": {
|
||||||
"x": [4, 5, 6],
|
"x": [4, 5, 6],
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"Name": "Stardew Access",
|
"Name": "Stardew Access",
|
||||||
"Author": "Mohammad Shoaib",
|
"Author": "Mohammad Shoaib",
|
||||||
"Version": "1.3.4",
|
"Version": "1.3.5-beta2",
|
||||||
"Description": "An accessibility mod with screen reader support!",
|
"Description": "An accessibility mod with screen reader support!",
|
||||||
"UniqueID": "shoaib.stardewaccess",
|
"UniqueID": "shoaib.stardewaccess",
|
||||||
"EntryDll": "stardew-access.dll",
|
"EntryDll": "stardew-access.dll",
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Lib.Harmony" Version="2.2.2" />
|
<PackageReference Include="Lib.Harmony" Version="2.2.2" />
|
||||||
<PackageReference Include="newtonsoft.json" Version="13.0.2" />
|
|
||||||
<PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="4.1.0" />
|
<PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="4.1.0" />
|
||||||
<Reference Include="./TolkDotNet.dll" />
|
<Reference Include="./TolkDotNet.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue