Skip to content

Commit

Permalink
DeluxeAutoPetter v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Hunter-Chambers committed Apr 25, 2024
1 parent 4439dd1 commit 35b7772
Show file tree
Hide file tree
Showing 12 changed files with 695 additions and 0 deletions.
154 changes: 154 additions & 0 deletions DeluxeAutoPetter/DeluxeAutoPetter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using DeluxeAutoPetter.helpers;
using HarmonyLib;
using Microsoft.Xna.Framework;
using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;

namespace DeluxeAutoPetter
{
internal sealed class DeluxeAutoPetter : Mod
{
private static bool IS_DATA_LOADED = false;

public override void Entry(IModHelper helper)
{
I18n.Init(helper.Translation);

helper.Events.Multiplayer.ModMessageReceived += OnModMessageReceived;
helper.Events.Multiplayer.PeerConnected += OnPeerConnected;

helper.Events.GameLoop.GameLaunched += OnGameLaunched;
helper.Events.GameLoop.ReturnedToTitle += OnReturnedToTitle;

helper.Events.GameLoop.UpdateTicked += OnUpdateTicked;
helper.Events.Input.ButtonPressed += OnButtonPressed;
helper.Events.GameLoop.SaveLoaded += OnSaveLoaded;
helper.Events.Player.InventoryChanged += OnPlayerInventoryChanged;
helper.Events.GameLoop.DayStarted += OnDayStarted;
helper.Events.GameLoop.DayEnding += OnDayEnding;
}

private void OnModMessageReceived(object? sender, ModMessageReceivedEventArgs e)
{
if (e.FromModID.Equals(ModManifest.UniqueID))
{
if (e.Type.Equals(nameof(MultiplayerHandler.QuestData)))
{
if (Context.IsMainPlayer)
{
MultiplayerHandler.SetPlayerQuestData(e.FromPlayerID, e.ReadAs<MultiplayerHandler.QuestData>());
MultiplayerHandler.SavePerPlayerQuestData(Helper);
}
else
{
MultiplayerHandler.SetPlayerQuestData(Game1.player.UniqueMultiplayerID, e.ReadAs<MultiplayerHandler.QuestData>());
QuestDetails.LoadQuestData(Game1.player.UniqueMultiplayerID);
}
}
}
}

private void OnPeerConnected(object? sender, PeerConnectedEventArgs e)
{
if (!Context.IsMainPlayer) return;

Helper.Multiplayer.SendMessage(MultiplayerHandler.GetPlayerQuestData(e.Peer.PlayerID), nameof(MultiplayerHandler.QuestData), new[] { ModManifest.UniqueID }, new[] { e.Peer.PlayerID });
}

private void OnReturnedToTitle(object? sender, ReturnedToTitleEventArgs e)
{
IS_DATA_LOADED = false;
}

private void OnGameLaunched(object? sender, GameLaunchedEventArgs e)
{
DeluxeAutoPetterDrawPatcher.Initialize(Monitor);
Harmony harmony = new(ModManifest.UniqueID);
DeluxeAutoPetterDrawPatcher.ApplyPatch(harmony);
}

private void OnUpdateTicked(object? sender, UpdateTickedEventArgs e)
{
if (!Context.IsWorldReady) return;

if (Game1.player.hasQuest(QuestDetails.GetQuestID()))
{
QuestDetails.ShowDropboxLocator(true);
if (Game1.activeClickableMenu is null && QuestDetails.IsMouseOverDropbox(Game1.currentCursorTile)) Game1.mouseCursor = Game1.cursor_grab;
}
}

private void OnButtonPressed(object? sender, ButtonPressedEventArgs e)
{
if (!(Context.IsWorldReady &&
Game1.currentLocation.Equals(Game1.getLocationFromName(QuestDetails.GetDropBoxGameLocationString())) &&
Game1.player.hasQuest(QuestDetails.GetQuestID()) &&
e.Button.IsActionButton()))
return;

Vector2 distanceVector = QuestDetails.GetInteractionDistanceFromDropboxVector(Game1.player.GetToolLocation());

if (Math.Abs(distanceVector.X) < (Game1.tileSize * 1.5) && Math.Abs(distanceVector.Y) < Game1.tileSize / 2)
Game1.activeClickableMenu ??= QuestDetails.CreateQuestContainerMenu();
}

private void OnSaveLoaded(object? sender, SaveLoadedEventArgs e)
{
QuestDetails.Initialize(ModManifest.UniqueID);
QuestMail.Initialize(ModManifest.UniqueID);
ObjectDetails.Initialize(ModManifest.UniqueID, Helper.DirectoryPath);
ShopDetails.Initialize();
MultiplayerHandler.Initialize(ModManifest.UniqueID);

QuestDetails.LoadQuest();
QuestMail.LoadMail(QuestMail.GetQuestMailID(), QuestMail.GetQuestMailDetails());
QuestMail.LoadMail(QuestMail.GetQuestRewardMailID(), QuestMail.GetQuestRewardMailDetails());
ObjectDetails.LoadObject();
ShopDetails.LoadShopItem();

if (Context.IsMainPlayer)
{
MultiplayerHandler.LoadPerPlayerQuestData(Helper, Game1.player.UniqueMultiplayerID);
QuestDetails.LoadQuestData(Game1.player.UniqueMultiplayerID);
IS_DATA_LOADED = true;
}
else Monitor.Log("You are not the host. If the host does not have this mod, then all data for this mod will be ignored.", LogLevel.Info);
}

private void OnPlayerInventoryChanged(object? sender, InventoryChangedEventArgs e)
{
if (QuestDetails.IsQuestDataNull() && !Context.IsMainPlayer) return;

if (!QuestDetails.GetIsTriggered() && e.Added.Any(item => item.QualifiedItemId.Equals(QuestDetails.GetAutoPetterID())))
{
e.Player.mailForTomorrow.Add(QuestMail.GetQuestMailID());
QuestDetails.SetIsTriggered(true);
}
}

private void OnDayStarted(object? sender, DayStartedEventArgs e)
{
if (!Context.IsMainPlayer) return;

foreach (StardewValley.Buildings.Building building in Game1.getFarm().buildings)
{
if (building.GetIndoors()?.Objects.Values.FirstOrDefault(sObject => sObject?.QualifiedItemId.Equals($"(BC){ObjectDetails.GetDeluxeAutoPetterID()}") ?? false, null) is not null)
{
foreach (FarmAnimal animal in building.GetIndoors().Animals.Values)
{
animal.pet(Game1.getFarmer(animal.ownerID.Value), true);
animal.pet(Game1.getFarmer(animal.ownerID.Value));
}
}
}
}

private void OnDayEnding(object? sender, DayEndingEventArgs e)
{
if (!IS_DATA_LOADED && Context.IsMainPlayer) return;
else if (Context.IsMainPlayer) MultiplayerHandler.SavePerPlayerQuestData(Helper);
else Helper.Multiplayer.SendMessage(MultiplayerHandler.GetPlayerQuestData(Game1.player.UniqueMultiplayerID), nameof(MultiplayerHandler.QuestData), new[] { ModManifest.UniqueID });
}
}
}
19 changes: 19 additions & 0 deletions DeluxeAutoPetter/DeluxeAutoPetter.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnableHarmony>true</EnableHarmony>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="4.1.1" />
<PackageReference Include="Pathoschild.Stardew.ModTranslationClassBuilder" Version="2.0.1" />
</ItemGroup>

<ItemGroup>
<Folder Include="assets\TileSheets\" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions DeluxeAutoPetter/DeluxeAutoPetter.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeluxeAutoPetter", "DeluxeAutoPetter.csproj", "{B84F31E5-4B70-4A26-99DC-3DBE25913667}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B84F31E5-4B70-4A26-99DC-3DBE25913667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B84F31E5-4B70-4A26-99DC-3DBE25913667}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B84F31E5-4B70-4A26-99DC-3DBE25913667}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B84F31E5-4B70-4A26-99DC-3DBE25913667}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EA18B832-0783-4A98-8994-1E43C1DDC272}
EndGlobalSection
EndGlobal
Binary file not shown.
65 changes: 65 additions & 0 deletions DeluxeAutoPetter/helpers/DeluxeAutoPetterDrawPatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using HarmonyLib;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI;
using StardewValley;
using StardewValley.ItemTypeDefinitions;

namespace DeluxeAutoPetter.helpers
{
internal class DeluxeAutoPetterDrawPatcher
{
private static IMonitor? MONITOR;

internal static void Initialize(IMonitor monitor)
{
MONITOR = monitor;
}

internal static void ApplyPatch(Harmony harmony)
{
harmony.Patch(
AccessTools.Method(typeof(StardewValley.Object), nameof(StardewValley.Object.draw), new Type[] { typeof(SpriteBatch), typeof(int), typeof(int), typeof(float) }),
new HarmonyMethod(typeof(DeluxeAutoPetterDrawPatcher), nameof(Draw_Prefix)));
}

internal static bool Draw_Prefix(StardewValley.Object __instance, SpriteBatch spriteBatch, int x, int y, float alpha)
{
try
{
// all of this code is taken from the source code for drawing an auto-petter.
// the only difference is the addition of drawing a second set of rotating hands.

if (__instance.isTemporarilyInvisible)
{
return true;
}

if (__instance.QualifiedItemId.Equals($"(BC){ObjectDetails.GetDeluxeAutoPetterID()}"))
{
ParsedItemData dataOrErrorItem = ItemRegistry.GetDataOrErrorItem(__instance.QualifiedItemId);
Texture2D texture = dataOrErrorItem.GetTexture();
Vector2 vector = __instance.getScale();
vector *= 4f;
Vector2 vector2 = Game1.GlobalToLocal(Game1.viewport, new Vector2(x * 64, y * 64 - 64));
Rectangle destinationRectangle = new((int)(vector2.X - vector.X / 2f) + ((__instance.shakeTimer > 0) ? Game1.random.Next(-1, 2) : 0), (int)(vector2.Y - vector.Y / 2f) + ((__instance.shakeTimer > 0) ? Game1.random.Next(-1, 2) : 0), (int)(64f + vector.X), (int)(128f + vector.Y / 2f));
float num = Math.Max(0f, (float)((y + 1) * 64 - 24) / 10000f) + (float)x * 1E-05f;

spriteBatch.Draw(texture, destinationRectangle, dataOrErrorItem.GetSourceRect(1, __instance.ParentSheetIndex), Color.White * alpha, 0f, Vector2.Zero, SpriteEffects.None, num);
spriteBatch.Draw(texture, vector2 + new Vector2(8.5f, 12f) * 4f, dataOrErrorItem.GetSourceRect(2, __instance.ParentSheetIndex), Color.White * alpha, (float)Game1.currentGameTime.TotalGameTime.TotalSeconds * -1.5f, new Vector2(7.5f, 15.5f), 4f, SpriteEffects.None, num + 1E-05f);
// this is the second set of rotating hands
spriteBatch.Draw(texture, vector2 + new Vector2(8.5f, 12f) * 4f, dataOrErrorItem.GetSourceRect(2, __instance.ParentSheetIndex), Color.White * alpha, (float)Game1.currentGameTime.TotalGameTime.TotalSeconds * -1.5f - 1.5f, new Vector2(7.5f, 15.5f), 4f, SpriteEffects.None, num + 1E-05f);

return false;
}

return true;
}
catch (Exception ex)
{
MONITOR?.Log($"Failed in {nameof(Draw_Prefix)}:\n{ex.Message}", LogLevel.Error);
return true;
}
}
}
}
68 changes: 68 additions & 0 deletions DeluxeAutoPetter/helpers/MultiplayerHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using StardewModdingAPI;

namespace DeluxeAutoPetter.helpers
{
internal static class MultiplayerHandler
{
public class QuestData
{
public bool IsTriggered { get; set; } = false;
public Dictionary<string, int> DonationCounts { get; set; } = new() {
{ QuestDetails.GetAutoPetterID(), 0 },
{ QuestDetails.GetHardwoodID(), 0 },
{ QuestDetails.GetIridiumBarID(), 0 }
};
}

private static string? PER_PLAYER_QUEST_DATA_KEY;
private static Dictionary<long, QuestData>? PER_PLAYER_QUEST_DATA;

public static void Initialize(string modID)
{
PER_PLAYER_QUEST_DATA_KEY = $"{modID}.Quest.PER_PLAYER_QUEST_DATA_KEY";
}

public static void LoadPerPlayerQuestData(IModHelper helper, long hostID)
{
if (PER_PLAYER_QUEST_DATA_KEY is null) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA_KEY)} has not been initialized! The {nameof(Initialize)} method must be called first!");

PER_PLAYER_QUEST_DATA = helper.Data.ReadSaveData<Dictionary<long, QuestData>>(PER_PLAYER_QUEST_DATA_KEY);
PER_PLAYER_QUEST_DATA ??= new() { { hostID, new() } };
}

public static void SavePerPlayerQuestData(IModHelper helper)
{
if (PER_PLAYER_QUEST_DATA_KEY is null) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA_KEY)} has not been initialized! The {nameof(Initialize)} method must be called first!");
if (PER_PLAYER_QUEST_DATA is null) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA)} has not been initialized! The {nameof(LoadPerPlayerQuestData)} method must be called first!");

helper.Data.WriteSaveData(PER_PLAYER_QUEST_DATA_KEY, PER_PLAYER_QUEST_DATA);
}

public static QuestData GetPlayerQuestData(long playerID)
{
if (PER_PLAYER_QUEST_DATA is null) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA)} has not been initialized! The {nameof(LoadPerPlayerQuestData)} method must be called first!");
if (!PER_PLAYER_QUEST_DATA.ContainsKey(playerID) && !Context.IsMainPlayer) throw new ArgumentNullException($"Your {nameof(PER_PLAYER_QUEST_DATA)} does not have any quest data for your player ID!");

if (!PER_PLAYER_QUEST_DATA.ContainsKey(playerID) && Context.IsMainPlayer) CreatePlayerQuestData(playerID);

return PER_PLAYER_QUEST_DATA[playerID];
}

public static void SetPlayerQuestData(long playerID, QuestData questData)
{
if (PER_PLAYER_QUEST_DATA is null && Context.IsMainPlayer) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA)} has not been initialized! The {nameof(LoadPerPlayerQuestData)} method must be called first!");
else if (PER_PLAYER_QUEST_DATA is null) PER_PLAYER_QUEST_DATA = new() { { playerID, questData } };

if (!PER_PLAYER_QUEST_DATA.ContainsKey(playerID)) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA)} has no data associated with the playerID '{playerID}'.");

PER_PLAYER_QUEST_DATA[playerID] = questData;
}

private static void CreatePlayerQuestData(long playerID)
{
if (PER_PLAYER_QUEST_DATA is null) throw new ArgumentNullException($"{nameof(PER_PLAYER_QUEST_DATA)} has not been initialized! The {nameof(LoadPerPlayerQuestData)} method must be called first!");

PER_PLAYER_QUEST_DATA.Add(playerID, new());
}
}
}
47 changes: 47 additions & 0 deletions DeluxeAutoPetter/helpers/ObjectDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using StardewValley;
using StardewValley.GameData.BigCraftables;

namespace DeluxeAutoPetter.helpers
{
internal static class ObjectDetails
{
/** **********************
* Class Variables
********************** **/
private static string? DELUXE_AUTO_PETTER_ID;
private static BigCraftableData? DELUXE_AUTO_PETTER_DETAILS;

/** **********************
* Variable Getters
********************** **/
public static string GetDeluxeAutoPetterID()
{
if (DELUXE_AUTO_PETTER_ID is null) throw new ArgumentNullException($"{nameof(DELUXE_AUTO_PETTER_ID)} has not been initialized! The {nameof(Initialize)} method must be called first!");

return DELUXE_AUTO_PETTER_ID;
}


/** **********************
* Public Methods
********************** **/
public static void Initialize(string modID, string modDirectoryPath)
{
DELUXE_AUTO_PETTER_ID = $"{modID}.DeluxeAutoPetterObject";
DELUXE_AUTO_PETTER_DETAILS = new()
{
Name = "Deluxe Auto-Petter",
DisplayName = I18n.DeluxeAutoPetterDisplayName(),
Description = I18n.DeluxeAutoPetterDescription(),
Texture = Path.Combine(modDirectoryPath, "assets", "TileSheets", "DeluxeAutoPetter.xnb")
};
}

public static void LoadObject()
{
if (DELUXE_AUTO_PETTER_DETAILS is null) throw new ArgumentNullException($"{nameof(DELUXE_AUTO_PETTER_DETAILS)} has not been initialized! The {nameof(Initialize)} method must be called first!");

DataLoader.BigCraftables(Game1.content)[GetDeluxeAutoPetterID()] = DELUXE_AUTO_PETTER_DETAILS;
}
}
}
Loading

0 comments on commit 35b7772

Please sign in to comment.