From f0526826a9dce675a06dc9ac79534b447e08f4e2 Mon Sep 17 00:00:00 2001 From: aedenthorn Date: Sat, 18 Feb 2023 03:02:48 -0500 Subject: [PATCH] etc --- FreeLove/Misc.cs | 4 +- FreeLove/manifest.json | 2 +- HugsAndKisses/HelperEvents.cs | 6 - ImmersiveSigns/CodePatches.cs | 304 ++++++++++++++++ ImmersiveSigns/IGenericModConfigMenuApi.cs | 177 +++++++++ ImmersiveSigns/ImmersiveScarecrows.csproj | 19 + ImmersiveSigns/Methods.cs | 395 +++++++++++++++++++++ ImmersiveSigns/ModConfig.cs | 19 + ImmersiveSigns/ModEntry.cs | 200 +++++++++++ ImmersiveSigns/manifest.json | 22 ++ OmniTools/Methods.cs | 8 +- OmniTools/ToolInfo.cs | 6 +- OmniTools/manifest.json | 2 +- StackedItemIcons/CodePatches.cs | 19 +- _releases/OmniTools 0.5.7.zip | Bin 0 -> 45011 bytes 15 files changed, 1161 insertions(+), 22 deletions(-) create mode 100644 ImmersiveSigns/CodePatches.cs create mode 100644 ImmersiveSigns/IGenericModConfigMenuApi.cs create mode 100644 ImmersiveSigns/ImmersiveScarecrows.csproj create mode 100644 ImmersiveSigns/Methods.cs create mode 100644 ImmersiveSigns/ModConfig.cs create mode 100644 ImmersiveSigns/ModEntry.cs create mode 100644 ImmersiveSigns/manifest.json create mode 100644 _releases/OmniTools 0.5.7.zip diff --git a/FreeLove/Misc.cs b/FreeLove/Misc.cs index d5d33052..2c061629 100644 --- a/FreeLove/Misc.cs +++ b/FreeLove/Misc.cs @@ -164,7 +164,9 @@ public static void PlaceSpousesInFarmhouse(FarmHouse farmHouse) } foreach (NPC spouse in allSpouses) - { + { + if (spouse is null) + continue; SMonitor.Log("placing " + spouse.Name); Point spouseRoomSpot = new Point(-1, -1); diff --git a/FreeLove/manifest.json b/FreeLove/manifest.json index 68049bca..6f29ae8c 100644 --- a/FreeLove/manifest.json +++ b/FreeLove/manifest.json @@ -1,7 +1,7 @@ { "Name": "Free Love", "Author": "aedenthorn", - "Version": "1.2.4", + "Version": "1.2.5", "Description": "Free Love", "UniqueID": "aedenthorn.FreeLove", "EntryDll": "FreeLove.dll", diff --git a/HugsAndKisses/HelperEvents.cs b/HugsAndKisses/HelperEvents.cs index a8a107b7..f48f3802 100644 --- a/HugsAndKisses/HelperEvents.cs +++ b/HugsAndKisses/HelperEvents.cs @@ -100,12 +100,6 @@ public void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e) getValue: () => Config.UnlimitedDailyKisses, setValue: value => Config.UnlimitedDailyKisses = value ); - configMenu.AddBoolOption( - mod: ModManifest, - name: () => "Unlimited Kisses", - getValue: () => Config.UnlimitedDailyKisses, - setValue: value => Config.UnlimitedDailyKisses = value - ); configMenu.AddBoolOption( mod: ModManifest, name: () => "Dating Kisses", diff --git a/ImmersiveSigns/CodePatches.cs b/ImmersiveSigns/CodePatches.cs new file mode 100644 index 00000000..a16cdc20 --- /dev/null +++ b/ImmersiveSigns/CodePatches.cs @@ -0,0 +1,304 @@ +using HarmonyLib; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Graphics.PackedVector; +using Newtonsoft.Json; +using StardewValley; +using StardewValley.Network; +using StardewValley.TerrainFeatures; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using xTile.Dimensions; +using Color = Microsoft.Xna.Framework.Color; +using Object = StardewValley.Object; +using Rectangle = Microsoft.Xna.Framework.Rectangle; +using StardewValley.Objects; +using StardewModdingAPI; + +namespace ImmersiveScarecrows +{ + public partial class ModEntry + { + + [HarmonyPatch(typeof(Object), nameof(Object.placementAction))] + public class Object_placementAction_Patch + { + public static bool Prefix(Object __instance, GameLocation location, int x, int y, Farmer who, ref bool __result) + { + if (!Config.EnableMod || !__instance.IsScarecrow()) + return true; + Vector2 placementTile = new Vector2((float)(x / 64), (float)(y / 64)); + if (!location.terrainFeatures.TryGetValue(placementTile, out var tf) || tf is not HoeDirt) + return true; + int which = GetMouseCorner(); + SMonitor.Log($"Placing {__instance.Name} at {x},{y}:{which}"); + ReturnScarecrow(who, location, tf, placementTile, which); + tf.modData[scarecrowKey + which] = GetScarecrowString(__instance); + tf.modData[guidKey + which] = Guid.NewGuid().ToString(); + tf.modData[scaredKey + which] = "0"; + if (atApi is not null) + { + Object obj = (Object)__instance.getOne(); + SetAltTextureForObject(obj); + foreach (var kvp in obj.modData.Pairs) + { + if (kvp.Key.StartsWith(altTextureKey)) + { + tf.modData[prefixKey + kvp.Key + which] = kvp.Value; + } + } + } + location.playSound("woodyStep", NetAudio.SoundContext.Default); + __result = true; + return false; + } + } + [HarmonyPatch(typeof(GameLocation), nameof(GameLocation.checkAction))] + public class GameLocation_checkAction_Patch + { + public static bool Prefix(GameLocation __instance, Location tileLocation, xTile.Dimensions.Rectangle viewport, Farmer who, ref bool __result) + { + var tile = new Vector2(tileLocation.X, tileLocation.Y); + if (!Config.EnableMod || !Game1.currentLocation.terrainFeatures.TryGetValue(tile, out var tf) || tf is not HoeDirt) + return true; + int which = GetMouseCorner(); + if (!GetScarecrowTileBool(__instance, ref tile, ref which, out string scarecrowString)) + return true; + tf = __instance.terrainFeatures[tile]; + var scareCrow = GetScarecrow(tf, which); + if(scareCrow is null) + return true; + if(scareCrow.ParentSheetIndex == 126 && who.CurrentItem is not null && who.CurrentItem is Hat) + { + if (tf.modData.TryGetValue(hatKey + which, out var hatString)) + { + Game1.createItemDebris(new Hat(int.Parse(hatString)), tf.currentTileLocation * 64f, (who.FacingDirection + 2) % 4, null, -1); + tf.modData.Remove(hatKey + which); + + } + tf.modData[hatKey + which] = (who.CurrentItem as Hat).which.Value + ""; + who.Items[who.CurrentToolIndex] = null; + who.currentLocation.playSound("dirtyHit", NetAudio.SoundContext.Default); + __result = true; + return false; + } + if (Game1.didPlayerJustRightClick(true)) + { + if (!tf.modData.TryGetValue(scaredKey + which, out var scaredString) || !int.TryParse(scaredString, out int scared)) + { + tf.modData[scaredKey + which] = "0"; + scared = 0; + } + if (scared == 0) + { + Game1.drawObjectDialogue(Game1.content.LoadString("Strings\\StringsFromCSFiles:Object.cs.12926")); + } + else + { + Game1.drawObjectDialogue((scared == 1) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:Object.cs.12927") : Game1.content.LoadString("Strings\\StringsFromCSFiles:Object.cs.12929", scared)); + } + } + __result = true; + return false; + } + } + [HarmonyPatch(typeof(HoeDirt), nameof(HoeDirt.DrawOptimized))] + public class HoeDirt_DrawOptimized_Patch + { + public static void Postfix(HoeDirt __instance, SpriteBatch dirt_batch, Vector2 tileLocation) + { + if (!Config.EnableMod) + return; + for (int i = 0; i < 4; i++) + { + if(__instance.modData.ContainsKey(scarecrowKey + i)) + { + if (!__instance.modData.TryGetValue(guidKey + i, out var guid)) + { + guid = Guid.NewGuid().ToString(); + __instance.modData[guidKey + i] = guid; + } + if (!scarecrowDict.TryGetValue(guid, out var obj)) + { + obj = GetScarecrow(__instance, i); + } + if (obj is not null) + { + Vector2 scaleFactor = obj.getScale(); + var globalPosition = tileLocation * 64 + new Vector2(32 - 8 * Config.Scale - scaleFactor.X / 2f + Config.DrawOffsetX, 32 - 8 * Config.Scale - 80 - scaleFactor.Y / 2f + Config.DrawOffsetY) + GetScarecrowCorner(i) * 32; + var position = Game1.GlobalToLocal(globalPosition); + Texture2D texture = null; + Rectangle sourceRect = new Rectangle(); + if (atApi is not null && obj.modData.ContainsKey("AlternativeTextureName")) + { + texture = GetAltTextureForObject(obj, out sourceRect); + } + if (texture is null) + { + texture = Game1.bigCraftableSpriteSheet; + sourceRect = Object.getSourceRectForBigCraftable(obj.ParentSheetIndex); + } + var layerDepth = (globalPosition.Y + 81 + 16 + Config.DrawOffsetZ) / 10000f; + dirt_batch.Draw(texture, position, sourceRect, Color.White * Config.Alpha, 0, Vector2.Zero, Config.Scale, SpriteEffects.None, layerDepth); + if (__instance.modData.TryGetValue(hatKey + i, out string hatString) && int.TryParse(hatString, out var hat)) + { + dirt_batch.Draw(FarmerRenderer.hatsTexture, position + new Vector2(-3f, -6f) * 4f, new Rectangle(hat * 20 % FarmerRenderer.hatsTexture.Width, hat * 20 / FarmerRenderer.hatsTexture.Width * 20 * 4, 20, 20), Color.White * Config.Alpha, 0f, Vector2.Zero, 4f, SpriteEffects.None, layerDepth + 1E-05f); + } + } + } + } + } + + } + [HarmonyPatch(typeof(GameLocation), nameof(GameLocation.isTileOccupiedForPlacement))] + public class GameLocation_isTileOccupiedForPlacement_Patch + { + public static void Postfix(GameLocation __instance, Vector2 tileLocation, Object toPlace, ref bool __result) + { + if (!Config.EnableMod || !__result || toPlace is null || !toPlace.IsScarecrow()) + return; + if (__instance.terrainFeatures.ContainsKey(tileLocation) && __instance.terrainFeatures[tileLocation] is HoeDirt && ((HoeDirt)__instance.terrainFeatures[tileLocation]).crop is not null) + { + __result = false; + } + } + + } + [HarmonyPatch(typeof(Utility), "itemCanBePlaced")] + public class Utility_itemCanBePlaced_Patch + { + public static bool Prefix(GameLocation location, Vector2 tileLocation, Item item, ref bool __result) + { + if (!Config.EnableMod || item is not Object || !(item as Object).IsScarecrow() || !location.terrainFeatures.TryGetValue(tileLocation, out var tf) || tf is not HoeDirt) + return true; + __result = true; + return false; + } + + } + [HarmonyPatch(typeof(Utility), nameof(Utility.playerCanPlaceItemHere))] + public class Utility_playerCanPlaceItemHere_Patch + { + public static bool Prefix(GameLocation location, Item item, int x, int y, Farmer f, ref bool __result) + { + if (!Config.EnableMod || item is not Object || !(item as Object).IsScarecrow() || !location.terrainFeatures.TryGetValue(new Vector2(x / 64, y / 64), out var tf) || tf is not HoeDirt) + return true; + __result = Utility.withinRadiusOfPlayer(x, y, 1, Game1.player); + return false; + } + + } + [HarmonyPatch(typeof(Object), nameof(Object.drawPlacementBounds))] + public class Object_drawPlacementBounds_Patch + { + public static bool Prefix(Object __instance, SpriteBatch spriteBatch, GameLocation location) + { + if (!Config.EnableMod || !Context.IsPlayerFree || !__instance.IsScarecrow() || Game1.currentLocation?.terrainFeatures?.TryGetValue(Game1.currentCursorTile, out var tf) != true || tf is not HoeDirt) + return true; + var which = GetMouseCorner(); + var scarecrowTile = Game1.currentCursorTile; + + GetScarecrowTileBool(Game1.currentLocation, ref scarecrowTile, ref which, out string str); + + Vector2 pos = Game1.GlobalToLocal(scarecrowTile * 64 + GetScarecrowCorner(which) * 32f); + + spriteBatch.Draw(Game1.mouseCursors, pos, new Rectangle(Utility.withinRadiusOfPlayer((int)Game1.currentCursorTile.X * 64, (int)Game1.currentCursorTile.Y * 64, 1, Game1.player) ? 194 : 210, 388, 16, 16), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 0.01f); + + if (Config.ShowRangeWhenPlacing) + { + foreach (var tile in GetScarecrowTiles(scarecrowTile, which, __instance.GetRadiusForScarecrow())) + { + spriteBatch.Draw(Game1.mouseCursors, Game1.GlobalToLocal(tile * 64), new Rectangle(194, 388, 16, 16), Color.White * 0.5f, 0f, Vector2.Zero, 4f, SpriteEffects.None, 0.01f); + } + } + if (__instance.bigCraftable.Value) + pos -= new Vector2(0, 64); + spriteBatch.Draw(__instance.bigCraftable.Value ? Game1.bigCraftableSpriteSheet : Game1.objectSpriteSheet, pos + new Vector2(0, -16), __instance.bigCraftable.Value ? Object.getSourceRectForBigCraftable(__instance.ParentSheetIndex) : GameLocation.getSourceRectForObject(__instance.ParentSheetIndex), Color.White * Config.Alpha, 0, Vector2.Zero, Config.Scale, __instance.Flipped ? SpriteEffects.FlipHorizontally : SpriteEffects.None, 0.02f); + + return false; + } + + } + [HarmonyPatch(typeof(GameLocation), "initNetFields")] + public class GameLocation_initNetFields_Patch + { + public static void Postfix(GameLocation __instance) + { + if (!Config.EnableMod) + return; + __instance.terrainFeatures.OnValueRemoved += delegate (Vector2 tileLocation, TerrainFeature tf) + { + if (tf is not HoeDirt) + return; + for (int i = 0; i < 4; i++) + { + if (tf.modData.TryGetValue(scarecrowKey + i, out var scarecrowString)) + { + try + { + __instance.terrainFeatures.Add(tileLocation, tf); + } + catch { } + } + } + }; + } + } + [HarmonyPatch(typeof(Farm), nameof(Farm.addCrows))] + public class Farm_addCrows_Patch + { + public static IEnumerable Transpiler(IEnumerable instructions) + { + SMonitor.Log($"Transpiling Farm.addCrows"); + + var codes = new List(instructions); + bool found1 = false; + bool found2 = false; + for (int i = 0; i < codes.Count; i++) + { + if (!found1 && i < codes.Count - 7 && codes[i].opcode == OpCodes.Call && codes[i].operand is MethodInfo && (MethodInfo)codes[i].operand == AccessTools.PropertyGetter(typeof(KeyValuePair), nameof(KeyValuePair.Key)) && codes[i + 1].opcode == OpCodes.Stloc_S && codes[i + 7].opcode == OpCodes.Brfalse) + { + SMonitor.Log("Adding check for scarecrow at vector"); + codes.Insert(i + 2, codes[i + 7].Clone()); + codes.Insert(i + 2, new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ModEntry), nameof(ModEntry.IsNoScarecrowInRange)))); + codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldloc_S, codes[i + 1].operand)); + codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldarg_0)); + i += 11; + found1 = true; + } + if (!found2 && i < codes.Count - 5 && codes[i].opcode == OpCodes.Ldloca_S && codes[i + 1].opcode == OpCodes.Call && codes[i + 1].operand is MethodInfo && (MethodInfo)codes[i + 1].operand == AccessTools.PropertyGetter(typeof(KeyValuePair), nameof(KeyValuePair.Value)) && codes[i + 2].opcode == OpCodes.Ldfld && (FieldInfo)codes[i + 2].operand == AccessTools.Field(typeof(Object), nameof(Object.bigCraftable)) && codes[i + 4].opcode == OpCodes.Brfalse_S) + { + SMonitor.Log("Removing big craftable check"); + codes[i].opcode = OpCodes.Nop; + codes[i + 1].opcode = OpCodes.Nop; + codes[i + 2].opcode = OpCodes.Nop; + codes[i + 3].opcode = OpCodes.Nop; + codes[i + 4].opcode = OpCodes.Nop; + codes[i].operand = null; + codes[i + 1].operand = null; + codes[i + 2].operand = null; + codes[i + 3].operand = null; + codes[i + 4].operand = null; + i += 4; + found2 = true; + } + if (found1 && found2) + break; + } + + return codes.AsEnumerable(); + } + } + public static bool Modded_Farm_AddCrows_Prefix(ref bool __result) + { + SMonitor.Log("Disabling addCrows prefix for Prismatic Tools and Radioactive tools"); + __result = true; + return false; + } + + } +} \ No newline at end of file diff --git a/ImmersiveSigns/IGenericModConfigMenuApi.cs b/ImmersiveSigns/IGenericModConfigMenuApi.cs new file mode 100644 index 00000000..fcdb9133 --- /dev/null +++ b/ImmersiveSigns/IGenericModConfigMenuApi.cs @@ -0,0 +1,177 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI; +using StardewModdingAPI.Utilities; +using StardewValley; + +namespace ImmersiveScarecrows +{ + /// The API which lets other mods add a config UI through Generic Mod Config Menu. + public interface IGenericModConfigMenuApi + { + /********* + ** Methods + *********/ + /**** + ** Must be called first + ****/ + /// Register a mod whose config can be edited through the UI. + /// The mod's manifest. + /// Reset the mod's config to its default values. + /// Save the mod's current config to the config.json file. + /// Whether the options can only be edited from the title screen. + /// Each mod can only be registered once, unless it's deleted via before calling this again. + void Register(IManifest mod, Action reset, Action save, bool titleScreenOnly = false); + + + /**** + ** Basic options + ****/ + /// Add a section title at the current position in the form. + /// The mod's manifest. + /// The title text shown in the form. + /// The tooltip text shown when the cursor hovers on the title, or null to disable the tooltip. + void AddSectionTitle(IManifest mod, Func text, Func tooltip = null); + + /// Add a paragraph of text at the current position in the form. + /// The mod's manifest. + /// The paragraph text to display. + void AddParagraph(IManifest mod, Func text); + + /// Add an image at the current position in the form. + /// The mod's manifest. + /// The image texture to display. + /// The pixel area within the texture to display, or null to show the entire image. + /// The zoom factor to apply to the image. + void AddImage(IManifest mod, Func texture, Rectangle? texturePixelArea = null, int scale = Game1.pixelZoom); + + /// Add a boolean option at the current position in the form. + /// The mod's manifest. + /// Get the current value from the mod config. + /// Set a new value in the mod config. + /// The label text to show in the form. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + void AddBoolOption(IManifest mod, Func getValue, Action setValue, Func name, Func tooltip = null, string fieldId = null); + + /// Add an integer option at the current position in the form. + /// The mod's manifest. + /// Get the current value from the mod config. + /// Set a new value in the mod config. + /// The label text to show in the form. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// The minimum allowed value, or null to allow any. + /// The maximum allowed value, or null to allow any. + /// The interval of values that can be selected. + /// Get the display text to show for a value, or null to show the number as-is. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + void AddNumberOption(IManifest mod, Func getValue, Action setValue, Func name, Func tooltip = null, int? min = null, int? max = null, int? interval = null, Func formatValue = null, string fieldId = null); + + /// Add a float option at the current position in the form. + /// The mod's manifest. + /// Get the current value from the mod config. + /// Set a new value in the mod config. + /// The label text to show in the form. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// The minimum allowed value, or null to allow any. + /// The maximum allowed value, or null to allow any. + /// The interval of values that can be selected. + /// Get the display text to show for a value, or null to show the number as-is. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + void AddNumberOption(IManifest mod, Func getValue, Action setValue, Func name, Func tooltip = null, float? min = null, float? max = null, float? interval = null, Func formatValue = null, string fieldId = null); + + /// Add a string option at the current position in the form. + /// The mod's manifest. + /// Get the current value from the mod config. + /// Set a new value in the mod config. + /// The label text to show in the form. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// The values that can be selected, or null to allow any. + /// Get the display text to show for a value from , or null to show the values as-is. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + void AddTextOption(IManifest mod, Func getValue, Action setValue, Func name, Func tooltip = null, string[] allowedValues = null, Func formatAllowedValue = null, string fieldId = null); + + /// Add a key binding at the current position in the form. + /// The mod's manifest. + /// Get the current value from the mod config. + /// Set a new value in the mod config. + /// The label text to show in the form. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + void AddKeybind(IManifest mod, Func getValue, Action setValue, Func name, Func tooltip = null, string fieldId = null); + + /// Add a key binding list at the current position in the form. + /// The mod's manifest. + /// Get the current value from the mod config. + /// Set a new value in the mod config. + /// The label text to show in the form. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + void AddKeybindList(IManifest mod, Func getValue, Action setValue, Func name, Func tooltip = null, string fieldId = null); + + + /**** + ** Multi-page management + ****/ + /// Start a new page in the mod's config UI, or switch to that page if it already exists. All options registered after this will be part of that page. + /// The mod's manifest. + /// The unique page ID. + /// The page title shown in its UI, or null to show the value. + /// You must also call to make the page accessible. This is only needed to set up a multi-page config UI. If you don't call this method, all options will be part of the mod's main config UI instead. + void AddPage(IManifest mod, string pageId, Func pageTitle = null); + + /// Add a link to a page added via at the current position in the form. + /// The mod's manifest. + /// The unique ID of the page to open when the link is clicked. + /// The link text shown in the form. + /// The tooltip text shown when the cursor hovers on the link, or null to disable the tooltip. + void AddPageLink(IManifest mod, string pageId, Func text, Func tooltip = null); + + + /**** + ** Advanced + ****/ + /// Add an option at the current position in the form using custom rendering logic. + /// The mod's manifest. + /// The label text to show in the form. + /// Draw the option in the config UI. This is called with the sprite batch being rendered and the pixel position at which to start drawing. + /// The tooltip text shown when the cursor hovers on the field, or null to disable the tooltip. + /// A callback raised just before the menu containing this option is opened. + /// A callback raised before the form's current values are saved to the config (i.e. before the save callback passed to ). + /// A callback raised after the form's current values are saved to the config (i.e. after the save callback passed to ). + /// A callback raised before the form is reset to its default values (i.e. before the reset callback passed to ). + /// A callback raised after the form is reset to its default values (i.e. after the reset callback passed to ). + /// A callback raised just before the menu containing this option is closed. + /// The pixel height to allocate for the option in the form, or null for a standard input-sized option. This is called and cached each time the form is opened. + /// The unique field ID for use with , or null to auto-generate a randomized ID. + /// The custom logic represented by the callback parameters is responsible for managing its own state if needed. For example, you can store state in a static field or use closures to use a state variable. + void AddComplexOption(IManifest mod, Func name, Action draw, Func tooltip = null, Action beforeMenuOpened = null, Action beforeSave = null, Action afterSave = null, Action beforeReset = null, Action afterReset = null, Action beforeMenuClosed = null, Func height = null, string fieldId = null); + + /// Set whether the options registered after this point can only be edited from the title screen. + /// The mod's manifest. + /// Whether the options can only be edited from the title screen. + /// This lets you have different values per-field. Most mods should just set it once in . + void SetTitleScreenOnlyForNextOptions(IManifest mod, bool titleScreenOnly); + + /// Register a method to notify when any option registered by this mod is edited through the config UI. + /// The mod's manifest. + /// The method to call with the option's unique field ID and new value. + /// Options use a randomized ID by default; you'll likely want to specify the fieldId argument when adding options if you use this. + void OnFieldChanged(IManifest mod, Action onChange); + + /// Open the config UI for a specific mod. + /// The mod's manifest. + void OpenModMenu(IManifest mod); + + /// Get the currently-displayed mod config menu, if any. + /// The manifest of the mod whose config menu is being shown, or null if not applicable. + /// The page ID being shown for the current config menu, or null if not applicable. This may be null even if a mod config menu is shown (e.g. because the mod doesn't have pages). + /// Returns whether a mod config menu is being shown. + bool TryGetCurrentMenu(out IManifest mod, out string page); + + /// Remove a mod from the config UI and delete all its options and pages. + /// The mod's manifest. + void Unregister(IManifest mod); + } +} \ No newline at end of file diff --git a/ImmersiveSigns/ImmersiveScarecrows.csproj b/ImmersiveSigns/ImmersiveScarecrows.csproj new file mode 100644 index 00000000..fd061965 --- /dev/null +++ b/ImmersiveSigns/ImmersiveScarecrows.csproj @@ -0,0 +1,19 @@ + + + 1.0.0 + net5.0 + true + AnyCPU;x64 + + + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/ImmersiveSigns/Methods.cs b/ImmersiveSigns/Methods.cs new file mode 100644 index 00000000..63ee564d --- /dev/null +++ b/ImmersiveSigns/Methods.cs @@ -0,0 +1,395 @@ + +using HarmonyLib; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; +using StardewValley.TerrainFeatures; +using System; +using System.Collections.Generic; +using System.Reflection; +using xTile.Tiles; +using Object = StardewValley.Object; + +namespace ImmersiveScarecrows +{ + public partial class ModEntry + { + private static Object GetScarecrow(TerrainFeature tf, int which) + { + if (!tf.modData.TryGetValue(scarecrowKey + which, out string scarecrowString)) + return null; + + Object obj = null; + foreach (var kvp in Game1.bigCraftablesInformation) + { + if (kvp.Value.Equals(scarecrowString)) + { + obj = new Object(Vector2.Zero, kvp.Key); + break; + } + } + if(obj is null) + { + scarecrowString = scarecrowString.Split('/')[0]; + foreach (var kvp in Game1.bigCraftablesInformation) + { + if (kvp.Value.StartsWith(scarecrowString + "/")) + { + obj = new Object(Vector2.Zero, kvp.Key); + break; + } + } + } + if (obj is null) + return null; + + if (atApi is not null) + { + foreach (var kvp2 in tf.modData.Pairs) + { + if (kvp2.Key.EndsWith(which + "") && kvp2.Key.StartsWith(altTexturePrefix)) + { + var key = kvp2.Key.Substring(prefixKey.Length, kvp2.Key.Length - prefixKey.Length - 1); + obj.modData[key] = kvp2.Value; + } + } + } + if (!tf.modData.TryGetValue(guidKey + which, out var guid)) + { + guid = Guid.NewGuid().ToString(); + tf.modData[guidKey + which] = guid; + } + scarecrowDict[guid] = obj; + + return obj; + } + private static string GetScarecrowString(Object instance) + { + return Game1.bigCraftablesInformation.TryGetValue(instance.ParentSheetIndex, out var str) ? str : instance.Name; + } + private static Vector2 GetScarecrowCorner(int i) + { + switch (i) + { + case 0: + return new Vector2(-1, -1); + case 1: + return new Vector2(1, -1); + case 2: + return new Vector2(-1, 1); + default: + return new Vector2(1, 1); + } + } + + private static int GetMouseCorner() + { + var x = Game1.getMouseX() + Game1.viewport.X; + var y = Game1.getMouseY() + Game1.viewport.Y; + if (x % 64 < 32) + { + if (y % 64 < 32) + { + return 0; + } + else + { + return 2; + } + } + else + { + if (y % 64 < 32) + { + return 1; + } + else + { + return 3; + } + } + } + + private static bool GetScarecrowTileBool(GameLocation location, ref Vector2 tile, ref int which, out string scarecrowString) + { + if ((scarecrowString = TileScarecrowString(location, tile, which)) is not null) + { + return true; + } + else + { + Dictionary dict = new(); + switch (which) + { + case 0: + dict.Add(3, new Vector2(-1, -1)); + dict.Add(2, new Vector2(0, -1)); + dict.Add(1, new Vector2(-1, 0)); + break; + case 1: + dict.Add(3, new Vector2(0, -1)); + dict.Add(2, new Vector2(1, 1)); + dict.Add(0, new Vector2(1, 0)); + break; + case 2: + dict.Add(3, new Vector2(-1, 0)); + dict.Add(1, new Vector2(-1, 1)); + dict.Add(0, new Vector2(0, 1)); + break; + case 3: + dict.Add(2, new Vector2(1, 0)); + dict.Add(1, new Vector2(0, 1)); + dict.Add(0, new Vector2(1, 1)); + break; + } + foreach (var kvp in dict) + { + var newTile = tile + kvp.Value; + if ((scarecrowString = TileScarecrowString(location, newTile, kvp.Key)) is not null) + { + tile = newTile; + which = kvp.Key; + return true; + } + } + } + return false; + } + + private static string TileScarecrowString(GameLocation location, Vector2 tile, int which) + { + return (location.terrainFeatures.TryGetValue(tile, out var tf) && tf.modData.TryGetValue(scarecrowKey + which, out var scarecrowString)) ? scarecrowString : null; + } + + private static bool ReturnScarecrow(Farmer who, GameLocation location, TerrainFeature tf, Vector2 placementTile, int which) + { + if (TryReturnScarecrow(who, location, tf, placementTile, which)) + { + return true; + } + else + { + Dictionary dict = new(); + switch (which) + { + case 0: + dict.Add(3, new Vector2(-1, -1)); + dict.Add(2, new Vector2(0, -1)); + dict.Add(1, new Vector2(-1, 0)); + break; + case 1: + dict.Add(3, new Vector2(0, -1)); + dict.Add(2, new Vector2(1, 1)); + dict.Add(0, new Vector2(1, 0)); + break; + case 2: + dict.Add(3, new Vector2(-1, 0)); + dict.Add(1, new Vector2(-1, 1)); + dict.Add(0, new Vector2(0, 1)); + break; + case 3: + dict.Add(2, new Vector2(1, 0)); + dict.Add(1, new Vector2(0, 1)); + dict.Add(0, new Vector2(1, 1)); + break; + } + foreach (var kvp in dict) + { + if (!location.terrainFeatures.TryGetValue(placementTile + kvp.Value, out var otf)) + continue; + if (TryReturnScarecrow(who, location, otf, placementTile + kvp.Value, kvp.Key)) + return true; + } + } + return false; + } + + private static bool TryReturnScarecrow(Farmer who, GameLocation location, TerrainFeature tf, Vector2 placementTile, int which) + { + Object scarecrow = null; + if (tf.modData.TryGetValue(scarecrowKey + which, out var scarecrowString)) + { + scarecrow = GetScarecrow(tf, which); + tf.modData.Remove(scarecrowKey + which); + tf.modData.Remove(scaredKey + which); + tf.modData.Remove(guidKey + which); + if (scarecrow is not null && !who.addItemToInventoryBool(scarecrow)) + { + who.currentLocation.debris.Add(new Debris(scarecrow, who.Position)); + } + SMonitor.Log($"Returning {scarecrow.Name}"); + return true; + } + return false; + } + + private static List GetScarecrowTiles(Vector2 tileLocation, int which, int radius) + { + Vector2 start = tileLocation + new Vector2(-1, -1) * (radius - 2); + Vector2 position = tileLocation + GetScarecrowCorner(which) * 0.5f; + List list = new(); + switch (which) + { + case 0: + start += new Vector2(-1, -1); + break; + case 1: + start += new Vector2(0, -1); + break; + case 2: + start += new Vector2(-1, 0); + break; + } + var diameter = (radius - 1) * 2; + for (int x = 0; x < diameter; x++) + { + for (int y = 0; y < diameter; y++) + { + Vector2 tile = start + new Vector2(x, y); + if (Vector2.Distance(position, tile) < radius - 1) + list.Add(tile); + } + } + return list; + + } + private static bool IsNoScarecrowInRange(Farm f, Vector2 v) + { + //SMonitor.Log("Checking for scarecrows near crop"); + foreach (var kvp in f.terrainFeatures.Pairs) + { + if (kvp.Value is HoeDirt) + { + for (int i = 0; i < 4; i++) + { + if (kvp.Value.modData.TryGetValue(scarecrowKey + i, out var scarecrowString)) + { + var obj = GetScarecrow(kvp.Value, i); + if (obj is not null) + { + var tiles = GetScarecrowTiles(kvp.Key, i, obj.GetRadiusForScarecrow()); + if(tiles.Contains(v)) + { + return false; + } + } + } + } + } + } + return true; + } + + private static void SetAltTextureForObject(Object obj) + { + if (atApi is null) + return; + + var textureMgr = AccessTools.Field(atApi.GetType().Assembly.GetType("AlternativeTextures.AlternativeTextures"), "textureManager").GetValue(null); + + var modelType = "Craftable"; + var baseName = AccessTools.Method(atApi.GetType().Assembly.GetType("AlternativeTextures.Framework.Patches.PatchTemplate"), "GetObjectName").Invoke(null, new object[] { obj }); + var instanceName = $"{modelType}_{baseName}"; + var instanceSeasonName = $"{instanceName}_{Game1.currentSeason}"; + + bool hasAlt = (bool)AccessTools.Method(textureMgr.GetType(), "DoesObjectHaveAlternativeTexture", new System.Type[] { typeof(string) }).Invoke(textureMgr, new object[] { instanceName }); + bool hasAltSeason = (bool)AccessTools.Method(textureMgr.GetType(), "DoesObjectHaveAlternativeTexture", new System.Type[] { typeof(string) }).Invoke(textureMgr, new object[] { instanceSeasonName }); + MethodInfo assignModData = AccessTools.Method(atApi.GetType().Assembly.GetType("AlternativeTextures.Framework.Patches.PatchTemplate"), "AssignModData").MakeGenericMethod(typeof(Object)); + if ((bool)AccessTools.Method(atApi.GetType().Assembly.GetType("AlternativeTextures.Framework.Patches.PatchTemplate"), "HasCachedTextureName").MakeGenericMethod(typeof(Object)).Invoke(null, new object[] { obj, false })) + { + return; + } + else if (hasAlt && hasAltSeason) + { + var result = Game1.random.Next(2) > 0 ? assignModData.Invoke(null, new object[] { obj, instanceSeasonName, true, obj.bigCraftable.Value }) : assignModData.Invoke(null, new object[] { obj, instanceName, false, obj.bigCraftable.Value }); + return; + } + else + { + if (hasAlt) + { + assignModData.Invoke(null, new object[] { obj, instanceName, false, obj.bigCraftable.Value }); + return; + } + + if (hasAltSeason) + { + assignModData.Invoke(null, new object[] { obj, instanceSeasonName, true, obj.bigCraftable.Value }); + return; + } + } + + AccessTools.Method(atApi.GetType().Assembly.GetType("AlternativeTextures.Framework.Patches.PatchTemplate"), "AssignDefaultModData").MakeGenericMethod(typeof(Object)).Invoke(null, new object[] { obj, instanceSeasonName, true, obj.bigCraftable.Value }); + } + + + private static Texture2D GetAltTextureForObject(Object obj, out Rectangle sourceRect) + { + sourceRect = new Rectangle(); + if (!obj.modData.TryGetValue("AlternativeTextureName", out var str)) + return null; + var textureMgr = AccessTools.Field(atApi.GetType().Assembly.GetType("AlternativeTextures.AlternativeTextures"), "textureManager").GetValue(null); + var textureModel = AccessTools.Method(textureMgr.GetType(), "GetSpecificTextureModel").Invoke(textureMgr, new object[] { str }); + if (textureModel is null) + { + return null; + } + var textureVariation = int.Parse(obj.modData["AlternativeTextureVariation"]); + var modConfig = AccessTools.Field(atApi.GetType().Assembly.GetType("AlternativeTextures.AlternativeTextures"), "modConfig").GetValue(null); + if (textureVariation == -1 || (bool)AccessTools.Method(modConfig.GetType(), "IsTextureVariationDisabled").Invoke(modConfig, new object[] { AccessTools.Method(textureModel.GetType(), "GetId").Invoke(textureModel, new object[] { }), textureVariation })) + { + return null; + } + var textureOffset = (int)AccessTools.Method(textureModel.GetType(), "GetTextureOffset").Invoke(textureModel, new object[] { textureVariation }); + + // Get the current X index for the source tile + var xTileOffset = obj.modData.ContainsKey("AlternativeTextureSheetId") ? obj.ParentSheetIndex - int.Parse(obj.modData["AlternativeTextureSheetId"]) : 0; + if (obj.showNextIndex.Value) + { + xTileOffset += 1; + } + + // Override xTileOffset if AlternativeTextureModel has an animation + if ((bool)AccessTools.Method(textureModel.GetType(), "HasAnimation").Invoke(textureModel, new object[] { textureVariation })) + { + if (!obj.modData.ContainsKey("AlternativeTextureCurrentFrame") || !obj.modData.ContainsKey("AlternativeTextureFrameIndex") || !obj.modData.ContainsKey("AlternativeTextureFrameDuration") || !obj.modData.ContainsKey("AlternativeTextureElapsedDuration")) + { + var animationData = AccessTools.Method(textureModel.GetType(), "GetAnimationDataAtIndex").Invoke(textureModel, new object[] { textureVariation, 0 }); + obj.modData["AlternativeTextureCurrentFrame"] = "0"; + obj.modData["AlternativeTextureFrameIndex"] = "0"; + obj.modData["AlternativeTextureFrameDuration"] = AccessTools.Property(animationData.GetType(), "Duration").GetValue(animationData).ToString();// Animation.ElementAt(0).Duration.ToString(); + obj.modData["AlternativeTextureElapsedDuration"] = "0"; + } + + var currentFrame = int.Parse(obj.modData["AlternativeTextureCurrentFrame"]); + var frameIndex = int.Parse(obj.modData["AlternativeTextureFrameIndex"]); + var frameDuration = int.Parse(obj.modData["AlternativeTextureFrameDuration"]); + var elapsedDuration = int.Parse(obj.modData["AlternativeTextureElapsedDuration"]); + + if (elapsedDuration >= frameDuration) + { + var animationDataList = (List)AccessTools.Method(textureModel.GetType(), "GetAnimationData").Invoke(textureModel, new object[] { textureVariation, 0 }); + frameIndex = frameIndex + 1 >= animationDataList.Count ? 0 : frameIndex + 1; + + var animationData = AccessTools.Method(textureModel.GetType(), "GetAnimationDataAtIndex").Invoke(textureModel, new object[] { textureVariation, frameIndex }); + currentFrame = (int)AccessTools.Property(animationData.GetType(), "Frame").GetValue(animationData); + + obj.modData["AlternativeTextureCurrentFrame"] = currentFrame.ToString(); + obj.modData["AlternativeTextureFrameIndex"] = frameIndex.ToString(); + obj.modData["AlternativeTextureFrameDuration"] = AccessTools.Property(animationData.GetType(), "Duration").GetValue(animationData).ToString(); + obj.modData["AlternativeTextureElapsedDuration"] = "0"; + } + else + { + obj.modData["AlternativeTextureElapsedDuration"] = (elapsedDuration + Game1.currentGameTime.ElapsedGameTime.Milliseconds).ToString(); + } + + xTileOffset = currentFrame; + } + var w = (int)AccessTools.Property(textureModel.GetType(), "TextureWidth").GetValue(textureModel); + var h = (int)AccessTools.Property(textureModel.GetType(), "TextureHeight").GetValue(textureModel); + sourceRect = new Rectangle(xTileOffset * w, textureOffset, w, h); + return (Texture2D)AccessTools.Method(textureModel.GetType(), "GetTexture").Invoke(textureModel, new object[] { textureVariation }); + } + } +} \ No newline at end of file diff --git a/ImmersiveSigns/ModConfig.cs b/ImmersiveSigns/ModConfig.cs new file mode 100644 index 00000000..90b70524 --- /dev/null +++ b/ImmersiveSigns/ModConfig.cs @@ -0,0 +1,19 @@ +using StardewModdingAPI; + +namespace ImmersiveScarecrows +{ + public class ModConfig + { + public bool EnableMod { get; set; } = true; + public bool Debug { get; set; } = false; + public bool ShowRangeWhenPlacing { get; set; } = true; + public float Scale { get; set; } = 4; + public float Alpha { get; set; } = 1; + public int DrawOffsetX { get; set; } = 0; + public int DrawOffsetY { get; set; } = 0; + public int DrawOffsetZ { get; set; } = 0; + public SButton PickupButton { get; set; } = SButton.E; + public SButton ShowRangeButton { get; set; } = SButton.LeftAlt; + + } +} diff --git a/ImmersiveSigns/ModEntry.cs b/ImmersiveSigns/ModEntry.cs new file mode 100644 index 00000000..c9a4f822 --- /dev/null +++ b/ImmersiveSigns/ModEntry.cs @@ -0,0 +1,200 @@ +using HarmonyLib; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI; +using StardewValley; +using StardewValley.TerrainFeatures; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using Object = StardewValley.Object; + +namespace ImmersiveScarecrows +{ + /// The mod entry point. + public partial class ModEntry : Mod + { + + public static IMonitor SMonitor; + public static IModHelper SHelper; + public static ModConfig Config; + + public static ModEntry context; + + public static object atApi; + + public static string prefixKey = "aedenthorn.ImmersiveScarecrows/"; + public static string scarecrowKey = "aedenthorn.ImmersiveScarecrows/scarecrow"; + public static string scaredKey = "aedenthorn.ImmersiveScarecrows/scared"; + public static string hatKey = "aedenthorn.ImmersiveScarecrows/hat"; + public static string guidKey = "aedenthorn.ImmersiveScarecrows/guid"; + public static string altTexturePrefix = "aedenthorn.ImmersiveScarecrows/AlternativeTexture"; + public static string altTextureKey = "AlternativeTexture"; + + public static Dictionary scarecrowDict = new(); + + /// The mod entry point, called after the mod is first loaded. + /// Provides simplified APIs for writing mods. + public override void Entry(IModHelper helper) + { + Config = Helper.ReadConfig(); + + context = this; + + SMonitor = Monitor; + SHelper = helper; + + Helper.Events.GameLoop.GameLaunched += GameLoop_GameLaunched; + Helper.Events.GameLoop.SaveLoaded += GameLoop_SaveLoaded; + Helper.Events.Input.ButtonPressed += Input_ButtonPressed; + Helper.Events.Display.RenderedWorld += Display_RenderedWorld; + + var harmony = new Harmony(ModManifest.UniqueID); + harmony.PatchAll(); + + HarmonyMethod prefix = new(typeof(ModEntry), nameof(ModEntry.Modded_Farm_AddCrows_Prefix)); + Type prismaticPatches = AccessTools.TypeByName("PrismaticTools.Framework.PrismaticPatches"); + MethodInfo prismaticPrefix = AccessTools.Method(prismaticPatches, "Farm_AddCrows"); + + if (prismaticPrefix is not null) + { + harmony.Patch(prismaticPrefix, prefix: prefix); + Monitor.Log("Found Prismatic Tools, patching for compat", LogLevel.Info); + } + + Type radioactivePatches = AccessTools.TypeByName("RadioactiveTools.Framework.RadioactivePatches"); + MethodInfo radioactivePrefix = AccessTools.Method(radioactivePatches, "Farm_AddCrows"); + + if (radioactivePrefix is not null) + { + harmony.Patch(radioactivePrefix, prefix: prefix); + Monitor.Log("Found Radioactive Tools, patching for compat", LogLevel.Info); + } + + } + + private void Display_RenderedWorld(object sender, StardewModdingAPI.Events.RenderedWorldEventArgs e) + { + if (!Config.EnableMod || !Context.IsPlayerFree || !Helper.Input.IsDown(Config.ShowRangeButton) || Game1.currentLocation?.terrainFeatures?.TryGetValue(Game1.currentCursorTile, out var tf) != true || tf is not HoeDirt) + return; + var which = GetMouseCorner(); + var scarecrowTile = Game1.currentCursorTile; + if (!GetScarecrowTileBool(Game1.currentLocation, ref scarecrowTile, ref which, out string str)) + return; + if (!Game1.currentLocation.terrainFeatures.TryGetValue(scarecrowTile, out tf)) + return; + var obj = GetScarecrow(tf, which); + if(obj is not null) + { + var tiles = GetScarecrowTiles(scarecrowTile, which, obj.GetRadiusForScarecrow()); + foreach (var tile in tiles) + { + e.SpriteBatch.Draw(Game1.mouseCursors, Game1.GlobalToLocal(new Vector2(tile.X * 64, tile.Y * 64)), new Rectangle?(new Rectangle(194, 388, 16, 16)), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 0.01f); + } + } + } + + private void Input_ButtonPressed(object sender, StardewModdingAPI.Events.ButtonPressedEventArgs e) + { + if (!Config.EnableMod) + return; + + if(Context.IsPlayerFree && Config.Debug && e.Button == SButton.X) + { + + Game1.getFarm().addCrows(); + Monitor.Log("Adding crows"); + } + if (e.Button == Config.PickupButton && Context.CanPlayerMove && Game1.currentLocation.terrainFeatures.TryGetValue(Game1.currentCursorTile, out var tf) && tf is HoeDirt) + { + int which = GetMouseCorner(); + if (ReturnScarecrow(Game1.player, Game1.currentLocation, tf, Game1.currentCursorTile, which)) + { + Helper.Input.Suppress(e.Button); + } + } + } + + private void GameLoop_SaveLoaded(object sender, StardewModdingAPI.Events.SaveLoadedEventArgs e) + { + scarecrowDict.Clear(); + } + + private void GameLoop_GameLaunched(object sender, StardewModdingAPI.Events.GameLaunchedEventArgs e) + { + atApi = Helper.ModRegistry.GetApi("PeacefulEnd.AlternativeTextures"); + + // get Generic Mod Config Menu's API (if it's installed) + var configMenu = Helper.ModRegistry.GetApi("spacechase0.GenericModConfigMenu"); + if (configMenu is null) + return; + + // register mod + configMenu.Register( + mod: ModManifest, + reset: () => Config = new ModConfig(), + save: () => Helper.WriteConfig(Config) + ); + + configMenu.AddBoolOption( + mod: ModManifest, + name: () => "Mod Enabled", + getValue: () => Config.EnableMod, + setValue: value => Config.EnableMod = value + ); + configMenu.AddBoolOption( + mod: ModManifest, + name: () => "Show Range When Placing", + getValue: () => Config.ShowRangeWhenPlacing, + setValue: value => Config.ShowRangeWhenPlacing = value + ); + configMenu.AddKeybind( + mod: ModManifest, + name: () => "Pickup Key", + getValue: () => Config.PickupButton, + setValue: value => Config.PickupButton = value + ); + + configMenu.AddKeybind( + mod: ModManifest, + name: () => "Show Range Key", + getValue: () => Config.ShowRangeButton, + setValue: value => Config.ShowRangeButton = value + ); + + configMenu.AddTextOption( + mod: ModManifest, + name: () => "Scale", + getValue: () => Config.Scale + "", + setValue: delegate (string value) { if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out float f)) { Config.Scale = f; } } + ); + configMenu.AddTextOption( + mod: ModManifest, + name: () => "Alpha", + getValue: () => Config.Alpha + "", + setValue: delegate (string value) { if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out float f)) { Config.Alpha = f; } } + ); + configMenu.AddNumberOption( + mod: ModManifest, + name: () => "Offset X", + getValue: () => Config.DrawOffsetX, + setValue: value => Config.DrawOffsetX = value + ); + configMenu.AddNumberOption( + mod: ModManifest, + name: () => "Offset Y", + getValue: () => Config.DrawOffsetY, + setValue: value => Config.DrawOffsetY = value + ); + configMenu.AddNumberOption( + mod: ModManifest, + name: () => "Offset Z", + getValue: () => Config.DrawOffsetZ, + setValue: value => Config.DrawOffsetZ = value + ); + + } + + } +} diff --git a/ImmersiveSigns/manifest.json b/ImmersiveSigns/manifest.json new file mode 100644 index 00000000..ca78f323 --- /dev/null +++ b/ImmersiveSigns/manifest.json @@ -0,0 +1,22 @@ +{ + "Name": "Immersive Scarecrows", + "Author": "aedenthorn", + "Version": "0.4.2", + "Description": "Immersive Scarecrows.", + "UniqueID": "aedenthorn.ImmersiveScarecrows", + "EntryDll": "ImmersiveScarecrows.dll", + "MinimumApiVersion": "3.15.0", + "ModUpdater": { + "Repository": "StardewValleyMods", + "User": "aedenthorn", + "Directory": "_releases", + "ModFolder": "ImmersiveScarecrows" + }, + "UpdateKeys": [ "Nexus:15290" ], + "Dependencies": [ + { + "UniqueID": "Platonymous.ModUpdater", + "IsRequired": false + } + ] +} \ No newline at end of file diff --git a/OmniTools/Methods.cs b/OmniTools/Methods.cs index d87b1f82..0150f055 100644 --- a/OmniTools/Methods.cs +++ b/OmniTools/Methods.cs @@ -475,7 +475,13 @@ public static Tool GetToolFromInfo(ToolInfo toolInfo) { try { - t.attachments.Add(oi is not null ? new Object(oi.parentSheetIndex, oi.stack, false, -1, oi.quality) : null); + Object a = null; + if (oi is not null) + { + a = new Object(oi.parentSheetIndex, oi.stack, false, -1, oi.quality); + a.uses.Value = oi.uses; + } + t.attachments.Add(a); } catch(Exception ex) { diff --git a/OmniTools/ToolInfo.cs b/OmniTools/ToolInfo.cs index 990f96ab..0c0e0d9c 100644 --- a/OmniTools/ToolInfo.cs +++ b/OmniTools/ToolInfo.cs @@ -30,7 +30,7 @@ public ToolInfo(Tool tool) } foreach (var o in tool.attachments) { - attachments.Add(o is not null ? new ObjectInfo(o.ParentSheetIndex, o.Stack, o.Quality) : null); + attachments.Add(o is not null ? new ObjectInfo(o.ParentSheetIndex, o.Stack, o.Quality, o.uses.Value) : null); } foreach (var kvp in tool.modData.Pairs) { @@ -50,12 +50,14 @@ public class ObjectInfo public int parentSheetIndex; public int stack; public int quality; + public int uses; - public ObjectInfo(int parentSheetIndex, int stack, int quality) + public ObjectInfo(int parentSheetIndex, int stack, int quality, int uses) { this.parentSheetIndex = parentSheetIndex; this.stack = stack; this.quality = quality; + this.uses = uses; } } } \ No newline at end of file diff --git a/OmniTools/manifest.json b/OmniTools/manifest.json index ae9b9a25..a3329673 100644 --- a/OmniTools/manifest.json +++ b/OmniTools/manifest.json @@ -1,7 +1,7 @@ { "Name": "Omni Tools", "Author": "aedenthorn", - "Version": "0.5.6", + "Version": "0.5.7", "Description": "Omni Tools.", "UniqueID": "aedenthorn.OmniTools", "EntryDll": "OmniTools.dll", diff --git a/StackedItemIcons/CodePatches.cs b/StackedItemIcons/CodePatches.cs index ed41585b..10da069b 100644 --- a/StackedItemIcons/CodePatches.cs +++ b/StackedItemIcons/CodePatches.cs @@ -7,6 +7,7 @@ using System; using xTile.Dimensions; using Object = StardewValley.Object; +using Rectangle = Microsoft.Xna.Framework.Rectangle; namespace StackedItemIcons { @@ -20,20 +21,18 @@ public static void Prefix(Object __instance, SpriteBatch spriteBatch, ref Vector if (!Config.EnableMod || __instance.Stack < Config.MinForDoubleStack || __instance.bigCraftable.Value) return; + var trans = (float)Math.Pow(transparency, 1.5); //jiggle = (float)Math.Sin(Game1.ticks / 10f) / 10f; float offset = Config.Spacing / 4f; - if(__instance.Stack < Config.MinForTripleStack) - { - spriteBatch.Draw(Game1.objectSpriteSheet, location + new Vector2(-offset, -offset) * scaleSize + new Vector2((float)((int)(32f * scaleSize)), (float)((int)(32f * scaleSize))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, __instance.ParentSheetIndex, 16, 16)), color * transparency, 0, new Vector2(8f, 8f) * scaleSize, 4f * scaleSize, SpriteEffects.None, 0); + var sourceRect = Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, __instance.ParentSheetIndex, 16, 16); + var pos1 = location + new Vector2(-offset, -offset) * scaleSize * (__instance.Stack >= Config.MinForTripleStack ? 2 : 1) + new Vector2(32f * scaleSize, 32f * scaleSize); + spriteBatch.Draw(Game1.objectSpriteSheet, pos1, sourceRect, color * trans, 0, new Vector2(8f, 8f) * scaleSize, 4f * scaleSize, SpriteEffects.None, 0); - location += new Vector2(offset, offset) * scaleSize; - } - else + location += new Vector2(offset, offset) * scaleSize; + if (__instance.Stack >= Config.MinForTripleStack) { - spriteBatch.Draw(Game1.objectSpriteSheet, location + new Vector2(-offset, -offset) * 2 * scaleSize + new Vector2((float)((int)(32f * scaleSize)), (float)((int)(32f * scaleSize))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, __instance.ParentSheetIndex, 16, 16)), color * transparency, 0, new Vector2(8f, 8f) * scaleSize, 4f * scaleSize, SpriteEffects.None, 0); - spriteBatch.Draw(Game1.objectSpriteSheet, location + new Vector2((float)((int)(32f * scaleSize)), (float)((int)(32f * scaleSize))), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, __instance.ParentSheetIndex, 16, 16)), color * transparency, 0, new Vector2(8f, 8f) * scaleSize, 4f * scaleSize, SpriteEffects.None, 0); - location += new Vector2(offset, offset) * 2 * scaleSize; - + spriteBatch.Draw(Game1.objectSpriteSheet, pos1 + new Vector2(offset, offset) * 2, sourceRect, color * trans, 0, new Vector2(8f, 8f) * scaleSize, 4f * scaleSize, SpriteEffects.None, 0); + location += new Vector2(offset, offset) * scaleSize; } } } diff --git a/_releases/OmniTools 0.5.7.zip b/_releases/OmniTools 0.5.7.zip new file mode 100644 index 0000000000000000000000000000000000000000..6fbff51c0cb36d71523de874773864e7b80c33e7 GIT binary patch literal 45011 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