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 00000000..6fbff51c Binary files /dev/null and b/_releases/OmniTools 0.5.7.zip differ