stardew-access/stardew-access/Features/Radar.cs

402 lines
15 KiB
C#

using Microsoft.Xna.Framework;
using StardewValley;
using StardewValley.Objects;
namespace stardew_access.Features
{
public class Radar
{
private List<Vector2> closed;
private List<Furniture> furnitures;
private List<NPC> npcs;
public List<string> exclusions;
private List<string> temp_exclusions;
public List<string> focus;
public bool isRunning;
public bool radarFocus = false;
public int delay, range;
public Radar()
{
delay = 3000;
range = 5;
isRunning = false;
closed = new List<Vector2>();
furnitures = new List<Furniture>();
npcs = new List<NPC>();
exclusions = new List<string>();
temp_exclusions = new List<string>();
focus = new List<string>();
exclusions.Add("stone");
exclusions.Add("weed");
exclusions.Add("twig");
exclusions.Add("coloured stone");
exclusions.Add("ice crystal");
exclusions.Add("clay stone");
exclusions.Add("fossil stone");
exclusions.Add("street lamp");
exclusions.Add("crop");
exclusions.Add("tree");
exclusions.Add("flooring");
exclusions.Add("water");
exclusions.Add("debris");
exclusions.Add("grass");
exclusions.Add("decoration");
exclusions.Add("bridge");
exclusions.Add("other");
/* Not excluded Categories
*
*
* exclusions.Add("farmer");
* exclusions.Add("animal");
* exclusions.Add("npc");
* exclusions.Add("furniture")
* exclusions.Add("building");
* exclusions.Add("resource clump");
* exclusions.Add("mine item");
* exclusions.Add("chest");
* exclusions.Add("bundle");
* exclusions.Add("door");
* exclusions.Add("machine");
* exclusions.Add("interactable");
*/
}
public void Run()
{
if (MainClass.radarDebug)
MainClass.DebugLog($"\n\nRead Tile started");
Vector2 currPosition = Game1.player.getTileLocation();
closed.Clear();
furnitures.Clear();
npcs.Clear();
SearchNearbyTiles(currPosition, range);
if (MainClass.radarDebug)
MainClass.DebugLog($"\nRead Tile stopped\n\n");
}
/// <summary>
/// Search the area using Breadth First Search algorithm(BFS).
/// </summary>
/// <param name="center">The starting point.</param>
/// <param name="limit">The limiting factor or simply radius of the search area.</param>
/// <param name="playSound">True by default if False then it will not play sound and only return the list of detected tiles(for api).</param>
/// <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)
{
Dictionary<Vector2, (string, string)> detectedTiles = new Dictionary<Vector2, (string, string)>();
Queue<Vector2> toSearch = new Queue<Vector2>();
List<Vector2> searched = new List<Vector2>();
int[] dirX = { -1, 0, 1, 0 };
int[] dirY = { 0, 1, 0, -1 };
toSearch.Enqueue(center);
searched.Add(center);
while (toSearch.Count > 0)
{
Vector2 item = toSearch.Dequeue();
if (playSound)
CheckTileAndPlaySound(item);
else
{
(bool, string?, string) tileInfo = CheckTile(item);
if (tileInfo.Item1 && tileInfo.Item2 != null)
{
// Add detected tile to the dictionary
detectedTiles.Add(item, (tileInfo.Item2, tileInfo.Item3));
}
}
for (int i = 0; i < 4; i++)
{
Vector2 dir = new Vector2(item.X + dirX[i], item.Y + dirY[i]);
if (isValid(dir, center, searched, limit))
{
toSearch.Enqueue(dir);
searched.Add(dir);
}
}
}
return detectedTiles;
}
/// <summary>
/// Search the entire location using Breadth First Search algorithm(BFS).
/// </summary>
/// <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()
{
Dictionary<Vector2, (string, string)> detectedTiles = new Dictionary<Vector2, (string, string)>();
Vector2 position = Vector2.Zero;
(bool, string? name, string category) tileInfo;
Queue<Vector2> toSearch = new Queue<Vector2>();
List<Vector2> searched = new List<Vector2>();
int[] dirX = { -1, 0, 1, 0 };
int[] dirY = { 0, 1, 0, -1 };
int count = 0;
toSearch.Enqueue(Game1.player.getTileLocation());
searched.Add(Game1.player.getTileLocation());
while (toSearch.Count > 0)
{
Vector2 item = toSearch.Dequeue();
tileInfo = CheckTile(item, true);
if (tileInfo.Item1 && tileInfo.name != null)
{
// Add detected tile to the dictionary
detectedTiles.Add(item, (tileInfo.name, tileInfo.category));
}
count++;
for (int i = 0; i < 4; i++)
{
Vector2 dir = new Vector2(item.X + dirX[i], item.Y + dirY[i]);
if (!searched.Contains(dir) && Game1.currentLocation.isTileOnMap(dir))
{
toSearch.Enqueue(dir);
searched.Add(dir);
}
}
}
return detectedTiles;
}
/// <summary>
/// Checks if the provided tile position is within the range/radius and whether the tile has already been checked or not.
/// </summary>
/// <param name="item">The position of the tile to be searched.</param>
/// <param name="center">The starting point of the search.</param>
/// <param name="searched">The list of searched items.</param>
/// <param name="limit">The radius of search</param>
/// <returns>Returns true if the tile is valid for search.</returns>
public bool isValid(Vector2 item, Vector2 center, List<Vector2> searched, int limit)
{
if (Math.Abs(item.X - center.X) > limit)
return false;
if (Math.Abs(item.Y - center.Y) > limit)
return false;
if (searched.Contains(item))
return false;
return true;
}
public (bool, string? name, string category) CheckTile(Vector2 position, bool lessInfo = false)
{
(string? name, CATEGORY? category) tileDetail = TileInfo.getNameWithCategoryAtTile(position, lessInfo);
if (tileDetail.name == null)
return (false, null, CATEGORY.Others.ToString());
if (tileDetail.category == null)
tileDetail.category = CATEGORY.Others;
return (true, tileDetail.name, tileDetail.category.ToString());
}
public void CheckTileAndPlaySound(Vector2 position)
{
try
{
if (Game1.currentLocation.isObjectAtTile((int)position.X, (int)position.Y))
{
(string? name, CATEGORY category) objDetails = TileInfo.getObjectAtTile((int)position.X, (int)position.Y);
string? objectName = objDetails.name;
CATEGORY category = objDetails.category;
StardewValley.Object obj = Game1.currentLocation.getObjectAtTile((int)position.X, (int)position.Y);
if (objectName != null)
{
objectName = objectName.ToLower().Trim();
if (obj is Furniture)
{
if (!furnitures.Contains((Furniture)obj))
{
furnitures.Add((Furniture)obj);
PlaySoundAt(position, objectName, category);
}
}
else
PlaySoundAt(position, objectName, category);
}
}
else
{
(string? name, CATEGORY? category) tileDetail = TileInfo.getNameWithCategoryAtTile(position);
if (tileDetail.name != null)
{
if (tileDetail.category == null)
tileDetail.category = CATEGORY.Others;
PlaySoundAt(position, tileDetail.name, tileDetail.category);
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"{e.Message}\n{e.StackTrace}\n{e.Source}");
}
}
public void PlaySoundAt(Vector2 position, string searchQuery, CATEGORY category)
{
#region Check whether to skip the object or not
// Skip if player is directly looking at the tile
if (CurrentPlayer.FacingTile.Equals(position))
return;
if (!radarFocus)
{
if ((exclusions.Contains(category.ToString().ToLower().Trim()) || exclusions.Contains(searchQuery.ToLower().Trim())))
return;
// Check if a word in searchQuery matches the one in exclusions list
string[] sqArr = searchQuery.ToLower().Trim().Split(" ");
for (int j = 0; j < sqArr.Length; j++)
{
if (exclusions.Contains(sqArr[j]))
return;
}
}
else
{
if (focus.Count >= 0)
{
bool found = false;
// Check if a word in searchQuery matches the one in focus list
string[] sqArr = searchQuery.ToLower().Trim().Split(" ");
for (int j = 0; j < sqArr.Length; j++)
{
if (focus.Contains(sqArr[j]))
{
found = true;
break;
}
}
// This condition has to be after the for loop
if (!found && !(focus.Contains(category.ToString().ToLower().Trim()) || focus.Contains(searchQuery.ToLower().Trim())))
return;
}
else
return;
}
#endregion
if (MainClass.radarDebug)
MainClass.ErrorLog($"{radarFocus}\tObject:{searchQuery.ToLower().Trim()}\tPosition: X={position.X} Y={position.Y}");
int px = (int)Game1.player.getTileX(); // Player's X postion
int py = (int)Game1.player.getTileY(); // Player's Y postion
int ox = (int)position.X; // Object's X postion
int oy = (int)position.Y; // Object's Y postion
int dx = ox - px; // Distance of object's X position
int dy = oy - py; // Distance of object's Y position
if (dy < 0 && (Math.Abs(dy) >= Math.Abs(dx))) // Object is at top
{
Game1.currentLocation.localSoundAt(GetSoundName(category, "top"), position);
}
else if (dx > 0 && (Math.Abs(dx) >= Math.Abs(dy))) // Object is at right
{
Game1.currentLocation.localSoundAt(GetSoundName(category, "right"), position);
}
else if (dx < 0 && (Math.Abs(dx) > Math.Abs(dy))) // Object is at left
{
Game1.currentLocation.localSoundAt(GetSoundName(category, "left"), position);
}
else if (dy > 0 && (Math.Abs(dy) > Math.Abs(dx))) // Object is at bottom
{
Game1.currentLocation.localSoundAt(GetSoundName(category, "bottom"), position);
}
}
public static string GetSoundName(CATEGORY category, string post)
{
string soundName = $"_{post}";
if (!MainClass.Config.RadarStereoSound)
soundName = $"_mono{soundName}";
if (category == CATEGORY.Farmers) // Villagers and farmers
soundName = $"npc{soundName}";
else if (category == CATEGORY.FarmAnimals) // Farm Animals
soundName = $"npc{soundName}";
else if (category == CATEGORY.NPCs) // Other npcs, also includes enemies
soundName = $"npc{soundName}";
else if (category == CATEGORY.WaterTiles) // Water tiles
soundName = $"obj{soundName}";
else if (category == CATEGORY.Furnitures) // Furnitures
soundName = $"obj{soundName}";
else if (category == CATEGORY.Others) // Other Objects
soundName = $"obj{soundName}";
else if (category == CATEGORY.Crops) // Crops
soundName = $"obj{soundName}";
else if (category == CATEGORY.Trees) // Trees
soundName = $"obj{soundName}";
else if (category == CATEGORY.Buildings) // Buildings
soundName = $"obj{soundName}";
else if (category == CATEGORY.MineItems) // Mine items
soundName = $"obj{soundName}";
else if (category == CATEGORY.Chests) // Chests
soundName = $"obj{soundName}";
else if (category == CATEGORY.Debris) // Grass and debris
soundName = $"obj{soundName}";
else if (category == CATEGORY.Flooring) // Flooring
soundName = $"obj{soundName}";
else // Default
soundName = $"obj{soundName}";
return soundName;
}
public bool ToggleFocus()
{
radarFocus = !radarFocus;
if (radarFocus)
EnableFocus();
else
DisableFocus();
return radarFocus;
}
public void EnableFocus()
{
temp_exclusions = exclusions.ToList();
exclusions.Clear();
}
public void DisableFocus()
{
exclusions = temp_exclusions.ToList();
temp_exclusions.Clear();
}
}
}