Ui-Info-Suite/SDVModTest/UIElements/LocationOfTownsfolk.cs

523 lines
26 KiB
C#

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using UIInfoSuite.Extensions;
using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;
using StardewValley.Menus;
using StardewValley.Quests;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace UIInfoSuite.UIElements
{
class LocationOfTownsfolk : IDisposable
{
#region Members
private List<NPC> _townsfolk = new List<NPC>();
private List<OptionsCheckbox> _checkboxes = new List<OptionsCheckbox>();
private const int SocialPanelWidth = 190;
private const int SocialPanelXOffset = 160;
private SocialPage _socialPage;
private String[] _friendNames;
private readonly IDictionary<String, String> _options;
private readonly IModHelper _helper;
private static readonly Dictionary<String, KeyValuePair<int, int>> _mapLocations = new Dictionary<string, KeyValuePair<int, int>>()
{
{ "HarveyRoom", new KeyValuePair<int, int>(677, 304) },
{ "BathHouse_Pool", new KeyValuePair<int, int>(576, 60) },
{ "WizardHouseBasement", new KeyValuePair<int, int>(196, 352) },
{ "BugLand", new KeyValuePair<int, int>(0, 0) },
{ "Desert", new KeyValuePair<int, int>(60, 92) },
{ "Cellar", new KeyValuePair<int, int>(0, 0) },
{ "JojaMart", new KeyValuePair<int, int>(872, 280) },
{ "Tent", new KeyValuePair<int, int>(784, 128) },
{ "HaleyHouse", new KeyValuePair<int, int>(652, 408) },
{ "Hospital", new KeyValuePair<int, int>(677, 304) },
{ "FarmHouse", new KeyValuePair<int, int>(470, 260) },
{ "Farm", new KeyValuePair<int, int>(470, 260) },
{ "ScienceHouse", new KeyValuePair<int, int>(732, 148) },
{ "ManorHouse", new KeyValuePair<int, int>(768, 395) },
{ "AdventureGuild", new KeyValuePair<int, int>(0, 0) },
{ "SeedShop", new KeyValuePair<int, int>(696, 296) },
{ "Blacksmith", new KeyValuePair<int, int>(852, 388) },
{ "JoshHouse", new KeyValuePair<int, int>(740, 320) },
{ "SandyHouse", new KeyValuePair<int, int>(40, 40) },
{ "Tunnel", new KeyValuePair<int, int>(0, 0) },
{ "CommunityCenter", new KeyValuePair<int, int>(692, 204) },
{ "Backwoods", new KeyValuePair<int, int>(460, 156) },
{ "ElliottHouse", new KeyValuePair<int, int>(826, 550) },
{ "SebastianRoom", new KeyValuePair<int, int>(732, 148) },
{ "BathHouse_Entry", new KeyValuePair<int, int>(576, 60) },
{ "Greenhouse", new KeyValuePair<int, int>(0, 0) },
{ "Sewer", new KeyValuePair<int, int>(380, 596) },
{ "WizardHouse", new KeyValuePair<int, int>(196, 352) },
{ "Trailer", new KeyValuePair<int, int>(780, 360) },
{ "Trailer_Big", new KeyValuePair<int, int>(780, 360) },
{ "Forest", new KeyValuePair<int, int>(80, 272) },
{ "Woods", new KeyValuePair<int, int>(100, 272) },
{ "WitchSwamp", new KeyValuePair<int, int>(0, 0) },
{ "ArchaeologyHouse", new KeyValuePair<int, int>(892, 416) },
{ "FishShop", new KeyValuePair<int, int>(844, 608) },
{ "Saloon", new KeyValuePair<int, int>(714, 354) },
{ "LeahHouse", new KeyValuePair<int, int>(452, 436) },
{ "Town", new KeyValuePair<int, int>(680, 360) },
{ "Mountain", new KeyValuePair<int, int>(762, 154) },
{ "BusStop", new KeyValuePair<int, int>(516, 224) },
{ "Railroad", new KeyValuePair<int, int>(644, 64) },
{ "SkullCave", new KeyValuePair<int, int>(0, 0) },
{ "BathHouse_WomensLocker", new KeyValuePair<int, int>(576, 60) },
{ "Beach", new KeyValuePair<int, int>(790, 550) },
{ "BathHouse_MensLocker", new KeyValuePair<int, int>(576, 60) },
{ "Mine", new KeyValuePair<int, int>(880, 100) },
{ "WitchHut", new KeyValuePair<int, int>(0, 0) },
{ "AnimalShop", new KeyValuePair<int, int>(420, 392) },
{ "SamHouse", new KeyValuePair<int, int>(612, 396) },
{ "WitchWarpCave", new KeyValuePair<int, int>(0, 0) },
{ "Club", new KeyValuePair<int, int>(60, 92) }
};
#endregion
public LocationOfTownsfolk(IModHelper helper, IDictionary<String, String> options)
{
_helper = helper;
_options = options;
}
public void Dispose()
{
ToggleShowNPCLocationsOnMap(false);
}
public void ToggleShowNPCLocationsOnMap(bool showLocations)
{
ExtendMenuIfNeeded();
_helper.Events.Display.RenderedActiveMenu -= OnRenderedActiveMenu_DrawSocialPageOptions;
_helper.Events.Display.RenderedActiveMenu -= OnRenderedActiveMenu_DrawNPCLocationsOnMap;
_helper.Events.Input.ButtonPressed -= OnButtonPressed_ForSocialPage;
_helper.Events.Display.MenuChanged -= OnMenuChanged;
if (showLocations)
{
_helper.Events.Display.RenderedActiveMenu += OnRenderedActiveMenu_DrawSocialPageOptions;
_helper.Events.Display.RenderedActiveMenu += OnRenderedActiveMenu_DrawNPCLocationsOnMap;
_helper.Events.Input.ButtonPressed += OnButtonPressed_ForSocialPage;
_helper.Events.Display.MenuChanged += OnMenuChanged;
}
}
/// <summary>Raised after a game menu is opened, closed, or replaced.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnMenuChanged(object sender, MenuChangedEventArgs e)
{
ExtendMenuIfNeeded();
}
private void ExtendMenuIfNeeded()
{
if (Game1.activeClickableMenu is GameMenu gameMenu)
{
foreach (var menu in gameMenu.pages)
{
if (menu is SocialPage socialPage)
{
_socialPage = socialPage;
_friendNames = _socialPage.names
.Select(name => name.ToString())
.ToArray();
break;
}
}
_townsfolk.Clear();
foreach (var location in Game1.locations)
{
foreach (var npc in location.characters)
{
if (Game1.player.friendshipData.ContainsKey(npc.Name))
_townsfolk.Add(npc);
}
}
_checkboxes.Clear();
foreach (var friendName in _friendNames)
{
int hashCode = friendName.GetHashCode();
OptionsCheckbox checkbox = new OptionsCheckbox("", hashCode);
_checkboxes.Add(checkbox);
//default to on
bool optionForThisFriend = true;
if (!Game1.player.friendshipData.ContainsKey(friendName))
{
checkbox.greyedOut = true;
optionForThisFriend = false;
}
else
{
String optionValue = _options.SafeGet(hashCode.ToString());
if (String.IsNullOrEmpty(optionValue))
{
_options[hashCode.ToString()] = optionForThisFriend.ToString();
}
else
{
optionForThisFriend = optionValue.SafeParseBool();
}
}
checkbox.isChecked = optionForThisFriend;
}
}
}
/// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnButtonPressed_ForSocialPage(object sender, ButtonPressedEventArgs e)
{
if (Game1.activeClickableMenu is GameMenu && (e.Button == SButton.MouseLeft || e.Button == SButton.ControllerA))
{
CheckSelectedBox();
}
}
private void CheckSelectedBox()
{
if (Game1.activeClickableMenu is GameMenu gameMenu)
{
int slotPosition = (int)typeof(SocialPage)
.GetField("slotPosition", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(_socialPage);
for (int i = slotPosition; i < slotPosition + 5; ++i)
{
OptionsCheckbox checkbox = _checkboxes[i];
if (checkbox.bounds.Contains(Game1.getMouseX(), Game1.getMouseY()) &&
!checkbox.greyedOut)
{
checkbox.isChecked = !checkbox.isChecked;
_options[checkbox.whichOption.ToString()] = checkbox.isChecked.ToString();
Game1.playSound("drumkit6");
}
}
}
}
/// <summary>When a menu is open (<see cref="Game1.activeClickableMenu"/> isn't null), raised after that menu is drawn to the sprite batch but before it's rendered to the screen.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnRenderedActiveMenu_DrawNPCLocationsOnMap(object sender, RenderedActiveMenuEventArgs e)
{
if (Game1.activeClickableMenu is GameMenu gameMenu)
{
if (gameMenu.currentTab == (Constants.TargetPlatform == GamePlatform.Android ? 4 : 3))
{
List<String> namesToShow = new List<string>();
foreach (var character in _townsfolk)
{
try
{
int hashCode = character.Name.GetHashCode();
bool drawCharacter = _options.SafeGet(hashCode.ToString()).SafeParseBool();
if (drawCharacter)
{
KeyValuePair<int, int> location = new KeyValuePair<int, int>((int)character.Position.X, (int)character.position.Y);
String locationName = character.currentLocation?.Name ?? character.DefaultMap;
switch (locationName)
{
case "Town":
case "Forest":
{
int xStart = 0;
int yStart = 0;
int areaWidth = 0;
int areaHeight = 0;
switch (locationName)
{
case "Town":
{
xStart = 595;
yStart = 163;
areaWidth = 345;
areaHeight = 330;
break;
}
case "Forest":
{
xStart = 183;
yStart = 378;
areaWidth = 319;
areaHeight = 261;
break;
}
}
xTile.Map map = character.currentLocation.Map;
float xScale = (float)areaWidth / (float)map.DisplayWidth;
float yScale = (float)areaHeight / (float)map.DisplayHeight;
float scaledX = character.position.X * xScale;
float scaledY = character.position.Y * yScale;
int xPos = (int)scaledX + xStart;
int yPos = (int)scaledY + yStart;
location = new KeyValuePair<int, int>(xPos, yPos);
break;
}
default:
{
_mapLocations.TryGetValue(locationName, out location);
break;
}
}
//if (character.currentLocation.Name == "Town")
//{
// String locationName = character.currentLocation.Name;
// xTile.Map map = character.currentLocation.Map;
// int xStart = 595;
// int yStart = 163;
// int townWidth = 345;
// int townHeight = 330;
// float xScale = (float)townWidth / (float)map.DisplayWidth;
// float yScale = (float)townHeight / (float)map.DisplayHeight;
// float scaledX = character.position.X * xScale;
// float scaledY = character.position.Y * yScale;
// int xPos = (int)scaledX + xStart;
// int yPos = (int)scaledY + yStart;
// location = new KeyValuePair<int, int>(xPos, yPos);
//}
//else
//{
// _mapLocations.TryGetValue(character.currentLocation.name, out location);
//}
Rectangle headShot = character.GetHeadShot();
int xBase = Game1.activeClickableMenu.xPositionOnScreen - 158;
int yBase = Game1.activeClickableMenu.yPositionOnScreen - 40;
int x = xBase + location.Key;
int y = yBase + location.Value;
Color color = character.CurrentDialogue.Count <= 0 ?
Color.Gray : Color.White;
ClickableTextureComponent textureComponent =
new ClickableTextureComponent(
character.Name,
new Rectangle(x, y, 0, 0),
null,
character.Name,
character.Sprite.Texture,
headShot,
2.3f);
float headShotScale = 2f;
Game1.spriteBatch.Draw(
character.Sprite.Texture,
new Vector2(x, y),
new Rectangle?(headShot),
color,
0.0f,
Vector2.Zero,
headShotScale,
SpriteEffects.None,
1f);
int mouseX = Game1.getMouseX();
int mouseY = Game1.getMouseY();
if (mouseX >= x && mouseX <= x + headShot.Width * headShotScale &&
mouseY >= y && mouseY <= y + headShot.Height * headShotScale)
{
namesToShow.Add(character.displayName);
}
foreach (var quest in Game1.player.questLog)
{
if (quest.accepted.Value && quest.dailyQuest.Value && !quest.completed.Value)
{
bool isQuestTarget = false;
switch (quest.questType.Value)
{
case 3: isQuestTarget = (quest as ItemDeliveryQuest).target.Value == character.Name; break;
case 4: isQuestTarget = (quest as SlayMonsterQuest).target.Value == character.Name; break;
case 7: isQuestTarget = (quest as FishingQuest).target.Value == character.Name; break;
case 10: isQuestTarget = (quest as ResourceCollectionQuest).target.Value == character.Name; break;
}
if (isQuestTarget)
Game1.spriteBatch.Draw(
Game1.mouseCursors,
new Vector2(x + 10, y - 12),
new Rectangle(394, 495, 4, 10),
Color.White,
0.0f,
Vector2.Zero,
3f,
SpriteEffects.None,
1f);
}
}
}
}
catch (Exception ex)
{
ModEntry.MonitorObject.Log(ex.Message + Environment.NewLine + ex.StackTrace, LogLevel.Error);
}
}
if (namesToShow.Count > 0)
{
StringBuilder text = new StringBuilder();
int longestLength = 0;
foreach (String name in namesToShow)
{
text.AppendLine(name);
longestLength = Math.Max(longestLength, (int)Math.Ceiling(Game1.smallFont.MeasureString(name).Length()));
}
int windowHeight = Game1.smallFont.LineSpacing * namesToShow.Count + 25;
Vector2 windowPos = new Vector2(Game1.getMouseX() + 40, Game1.getMouseY() - windowHeight);
IClickableMenu.drawTextureBox(
Game1.spriteBatch,
(int)windowPos.X,
(int)windowPos.Y,
longestLength + 30,
Game1.smallFont.LineSpacing * namesToShow.Count + 25,
Color.White);
Game1.spriteBatch.DrawString(
Game1.smallFont,
text,
new Vector2(windowPos.X + 17, windowPos.Y + 17),
Game1.textShadowColor);
Game1.spriteBatch.DrawString(
Game1.smallFont,
text,
new Vector2(windowPos.X + 15, windowPos.Y + 15),
Game1.textColor);
}
//The cursor needs to show up in front of the character faces
Tools.DrawMouseCursor();
String hoverText = (String)typeof(MapPage)
.GetField(
"hoverText",
BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(gameMenu.pages[gameMenu.currentTab]);
IClickableMenu.drawHoverText(
Game1.spriteBatch,
hoverText,
Game1.smallFont);
}
}
}
/// <summary>When a menu is open (<see cref="Game1.activeClickableMenu"/> isn't null), raised after that menu is drawn to the sprite batch but before it's rendered to the screen.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnRenderedActiveMenu_DrawSocialPageOptions(object sender, RenderedActiveMenuEventArgs e)
{
if (Game1.activeClickableMenu is GameMenu gameMenu && gameMenu.currentTab == 2)
{
Game1.drawDialogueBox(
Game1.activeClickableMenu.xPositionOnScreen - SocialPanelXOffset,
Game1.activeClickableMenu.yPositionOnScreen,
SocialPanelWidth,
Game1.activeClickableMenu.height,
false,
true);
int slotPosition = (int)typeof(SocialPage)
.GetField("slotPosition", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(_socialPage);
int yOffset = 0;
for (int i = slotPosition; i < slotPosition + 5 && i < _friendNames.Length; ++i)
{
OptionsCheckbox checkbox = _checkboxes[i];
checkbox.bounds.X = Game1.activeClickableMenu.xPositionOnScreen - 60;
checkbox.bounds.Y = Game1.activeClickableMenu.yPositionOnScreen + 130 + yOffset;
checkbox.draw(Game1.spriteBatch, 0, 0);
yOffset += 112;
Color color = checkbox.isChecked ? Color.White : Color.Gray;
Game1.spriteBatch.Draw(
Game1.mouseCursors,
new Vector2(checkbox.bounds.X - 50, checkbox.bounds.Y),
new Rectangle(80, 0, 16, 16),
color,
0.0f,
Vector2.Zero,
3f,
SpriteEffects.None,
1f);
if (yOffset != 560)
{
Game1.spriteBatch.Draw(
Game1.staminaRect,
new Rectangle(
checkbox.bounds.X - 50,
checkbox.bounds.Y + 72,
SocialPanelWidth / 2 - 6,
4),
Color.SaddleBrown);
Game1.spriteBatch.Draw(
Game1.staminaRect,
new Rectangle(
checkbox.bounds.X - 50,
checkbox.bounds.Y + 76,
SocialPanelWidth / 2 - 6,
4),
Color.BurlyWood);
}
if (!Game1.options.hardwareCursor)
{
Game1.spriteBatch.Draw(
Game1.mouseCursors,
new Vector2(
Game1.getMouseX(),
Game1.getMouseY()),
Game1.getSourceRectForStandardTileSheet(
Game1.mouseCursors,
Game1.mouseCursor,
16,
16),
Color.White,
0.0f,
Vector2.Zero,
Game1.pixelZoom + (Game1.dialogueButtonScale / 150.0f),
SpriteEffects.None,
1f);
}
if (checkbox.bounds.Contains(Game1.getMouseX(), Game1.getMouseY()))
IClickableMenu.drawHoverText(
Game1.spriteBatch,
"Track on map",
Game1.dialogueFont);
}
}
}
}
}