From 346f528eb34e542b4732fb9430eed0d029dc0d9a Mon Sep 17 00:00:00 2001 From: b-b-blueberry Date: Mon, 9 Aug 2021 01:01:54 +1000 Subject: [PATCH] Add CanBeArranged property to OutdoorPot/ItemDefinition. Replace duplicated values in OutdoorPot with lambda references to their matching entry in ItemDefinitions. Rename some methods in OutdoorPot for clarity. Add explicit names when referencing ModEntry members from within itself for clarity. --- RaisedGardenBeds/HarmonyPatches.cs | 12 +- RaisedGardenBeds/ItemDefinition.cs | 4 + RaisedGardenBeds/ModEntry.cs | 140 +++++++++-------- RaisedGardenBeds/NewRecipeMenu.cs | 2 +- RaisedGardenBeds/OutdoorPot.cs | 238 ++++++++++++++--------------- 5 files changed, 199 insertions(+), 197 deletions(-) diff --git a/RaisedGardenBeds/HarmonyPatches.cs b/RaisedGardenBeds/HarmonyPatches.cs index 9682ce0..5418e88 100644 --- a/RaisedGardenBeds/HarmonyPatches.cs +++ b/RaisedGardenBeds/HarmonyPatches.cs @@ -105,7 +105,7 @@ public static bool Utility_IsThereAnObjectHereWhichAcceptsThisItem_Prefix( Vector2 tileLocation = new Vector2(x / Game1.tileSize, y / Game1.tileSize); if (location.Objects.TryGetValue(tileLocation, out StardewValley.Object o) && o != null && o is OutdoorPot op) { - if (!OutdoorPot.IsItemPlantable(item) && op.IsOpenForPlacement()) + if (!OutdoorPot.CanAcceptItemOrSeed(item: item) && OutdoorPot.CanAcceptAnything(op: op)) { __result = op.performObjectDropInAction(dropInItem: (StardewValley.Object)item, probe: true, who: Game1.player); } @@ -132,7 +132,7 @@ public static bool Utility_IsViableSeedSpot_Prefix( { if (location.Objects.TryGetValue(tileLocation, out StardewValley.Object o) && o != null && o is OutdoorPot op) { - if (OutdoorPot.IsItemPlantable(item) && op.CanPlantHere(item) && op.IsOpenForPlacement()) + if (OutdoorPot.CanAcceptItemOrSeed(item) && OutdoorPot.CanAcceptSeed(item: item, op: op) && OutdoorPot.CanAcceptAnything(op: op)) { return true; } @@ -155,7 +155,7 @@ public static bool Object_ApplySprinkler_Prefix( if (ModEntry.Config.SprinklersEnabled && location.Objects.TryGetValue(tile, out StardewValley.Object o) && o != null && o is OutdoorPot op) { - if (op.IsOpenForPlacement(ignoreCrops: true)) + if (OutdoorPot.CanAcceptAnything(op: op, ignoreCrops: true)) { op.Water(); } @@ -180,9 +180,9 @@ public static void GameLocation_IsTileOccupiedForPlacement_Postfix( { if (__instance.Objects.TryGetValue(tileLocation, out StardewValley.Object o) && o != null && o is OutdoorPot op) { - bool isPlantable = OutdoorPot.IsItemPlantable(toPlace) + bool isPlantable = OutdoorPot.CanAcceptItemOrSeed(toPlace) && op.hoeDirt.Value.canPlantThisSeedHere(toPlace.ParentSheetIndex, (int)tileLocation.X, (int)tileLocation.Y, toPlace.Category == -19); - if (op.IsOpenForPlacement() && isPlantable) + if (OutdoorPot.CanAcceptAnything(op: op) && isPlantable) { __result = false; } @@ -206,7 +206,7 @@ public static void CraftingPage_LayoutRecipes_Postfix( // Sprite pair.Key.texture = ModEntry.Sprites[ModEntry.ItemDefinitions[variantKey].SpriteKey]; - pair.Key.sourceRect = OutdoorPot.GetSourceRectangle(spriteIndex: ModEntry.ItemDefinitions[variantKey].SpriteIndex); + pair.Key.sourceRect = OutdoorPot.GetSpriteSourceRectangle(spriteIndex: ModEntry.ItemDefinitions[variantKey].SpriteIndex); // Strings pair.Value.DisplayName = OutdoorPot.GetDisplayNameFromName(pair.Value.name); diff --git a/RaisedGardenBeds/ItemDefinition.cs b/RaisedGardenBeds/ItemDefinition.cs index beeb7f6..f557891 100644 --- a/RaisedGardenBeds/ItemDefinition.cs +++ b/RaisedGardenBeds/ItemDefinition.cs @@ -44,6 +44,10 @@ Optional entries /// Value is not given as number of seasons the object will last to afford lenience for late placement. /// public int DaysToBreak { get; set; } = 0; + /// + /// Whether this object will build up with others to form large arrangements. + /// + public bool CanBeArranged { get; set; } = true; /******************** Code-generated values diff --git a/RaisedGardenBeds/ModEntry.cs b/RaisedGardenBeds/ModEntry.cs index 1bf45d0..af2b3ae 100644 --- a/RaisedGardenBeds/ModEntry.cs +++ b/RaisedGardenBeds/ModEntry.cs @@ -35,23 +35,23 @@ public class ModEntry : Mod // others internal static int ModUpdateKey; - internal static int EventRootId => ModUpdateKey * 10000; + internal static int EventRootId => ModEntry.ModUpdateKey * 10000; internal const string CommandPrefix = "rgb."; internal const string EndOfNightState = "blueberry.rgb.endofnightmenu"; public override void Entry(IModHelper helper) { - Instance = this; - Config = helper.ReadConfig(); - ModUpdateKey = int.Parse(ModManifest.UpdateKeys.First().Split(':')[1]); + ModEntry.Instance = this; + ModEntry.Config = helper.ReadConfig(); + ModEntry.ModUpdateKey = int.Parse(this.ModManifest.UpdateKeys.First().Split(':')[1]); helper.Events.GameLoop.GameLaunched += this.GameLoop_GameLaunched; } private void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e) { - Helper.Events.GameLoop.OneSecondUpdateTicked += this.Event_LoadLate; + this.Helper.Events.GameLoop.OneSecondUpdateTicked += this.Event_LoadLate; } private void GameLoop_SaveLoaded(object sender, SaveLoadedEventArgs e) @@ -77,7 +77,7 @@ private void GameLoop_DayStarted(object sender, DayStartedEventArgs e) private void GameLoop_DayEnding(object sender, DayEndingEventArgs e) { // Break ready objects at the start of each season - if (Config.RaisedBedsMayBreakWithAge && Game1.dayOfMonth == 28) + if (ModEntry.Config.RaisedBedsMayBreakWithAge && Game1.dayOfMonth == 28) { Log.T($"Performing end-of-season breakage: Y{Game1.year}/M{1 + Utility.getSeasonNumber(Game1.currentSeason)}/D{Game1.dayOfMonth}"); OutdoorPot.BreakAll(); @@ -90,9 +90,9 @@ private void Specialized_LoadStageChanged(object sender, LoadStageChangedEventAr { Log.T("Invalidating assets on connected for multiplayer peer."); - Helper.Content.InvalidateCache(Path.Combine("Data", "BigCraftablesInformation")); - Helper.Content.InvalidateCache(Path.Combine("Data", "CraftingRecipes")); - Helper.Content.InvalidateCache(Path.Combine("TileSheets", "Craftables")); + this.Helper.Content.InvalidateCache(Path.Combine("Data", "BigCraftablesInformation")); + this.Helper.Content.InvalidateCache(Path.Combine("Data", "CraftingRecipes")); + this.Helper.Content.InvalidateCache(Path.Combine("TileSheets", "Craftables")); } } @@ -102,7 +102,7 @@ private void SpaceEvents_ShowNightEndMenus(object sender, SpaceCore.Events.Event List newVarieties = AddNewAvailableRecipes(); if (newVarieties.Count > 0) { - Log.T($"Unlocked {newVarieties.Count} new recipes:{newVarieties.Aggregate(string.Empty, (str, s) => $"{str}{Environment.NewLine}{s}")}"); + Log.T(newVarieties.Aggregate($"Unlocked {newVarieties.Count} new recipes:", (str, s) => $"{str}{Environment.NewLine}{s}")); NewRecipeMenu.Push(newVarieties); } @@ -110,7 +110,7 @@ private void SpaceEvents_ShowNightEndMenus(object sender, SpaceCore.Events.Event private void Event_LoadLate(object sender, OneSecondUpdateTickedEventArgs e) { - Helper.Events.GameLoop.OneSecondUpdateTicked -= this.Event_LoadLate; + this.Helper.Events.GameLoop.OneSecondUpdateTicked -= this.Event_LoadLate; if (this.LoadAPIs()) { @@ -121,7 +121,7 @@ private void Event_LoadLate(object sender, OneSecondUpdateTickedEventArgs e) private bool LoadAPIs() { Log.T("Loading mod-provided APIs."); - ISpaceCoreAPI spacecoreAPI = Helper.ModRegistry.GetApi("spacechase0.SpaceCore"); + ISpaceCoreAPI spacecoreAPI = this.Helper.ModRegistry.GetApi("spacechase0.SpaceCore"); if (spacecoreAPI == null) { // Skip all mod behaviours if we fail to load the objects @@ -139,9 +139,9 @@ private void Initialise() Log.T("Initialising mod data."); // Assets - AssetManager assetManager = new AssetManager(helper: Helper); - Helper.Content.AssetLoaders.Add(assetManager); - Helper.Content.AssetEditors.Add(assetManager); + AssetManager assetManager = new AssetManager(helper: this.Helper); + this.Helper.Content.AssetLoaders.Add(assetManager); + this.Helper.Content.AssetEditors.Add(assetManager); // Content Translations.LoadTranslationPacks(); @@ -149,47 +149,47 @@ private void Initialise() this.AddGenericModConfigMenu(); // Patches - HarmonyPatches.Patch(id: ModManifest.UniqueID); + HarmonyPatches.Patch(id: this.ModManifest.UniqueID); // Events - Helper.Events.Specialized.LoadStageChanged += this.Specialized_LoadStageChanged; - Helper.Events.GameLoop.SaveLoaded += this.GameLoop_SaveLoaded; - Helper.Events.GameLoop.DayStarted += this.GameLoop_DayStarted; - Helper.Events.GameLoop.DayEnding += this.GameLoop_DayEnding; + this.Helper.Events.Specialized.LoadStageChanged += this.Specialized_LoadStageChanged; + this.Helper.Events.GameLoop.SaveLoaded += this.GameLoop_SaveLoaded; + this.Helper.Events.GameLoop.DayStarted += this.GameLoop_DayStarted; + this.Helper.Events.GameLoop.DayEnding += this.GameLoop_DayEnding; SpaceCore.Events.SpaceEvents.ShowNightEndMenus += this.SpaceEvents_ShowNightEndMenus; // Console commands - Helper.ConsoleCommands.Add( - name: CommandPrefix + "eventget", + this.Helper.ConsoleCommands.Add( + name: ModEntry.CommandPrefix + "eventget", documentation: $"Check if event has been seen.{Environment.NewLine}Provide event ID, default to root event.", callback: Cmd_IsEventSeen); - Helper.ConsoleCommands.Add( - name: CommandPrefix + "eventset", + this.Helper.ConsoleCommands.Add( + name: ModEntry.CommandPrefix + "eventset", documentation: $"Set state for having seen any event.{Environment.NewLine}Provide event ID, default to root event.", callback: Cmd_ToggleEventSeen); - Helper.ConsoleCommands.Add( - name: CommandPrefix + "give", + this.Helper.ConsoleCommands.Add( + name: ModEntry.CommandPrefix + "give", documentation: $"Give several unlocked raised beds.{Environment.NewLine}Has no effect if none are available.", callback: Cmd_Give); - Helper.ConsoleCommands.Add( - name: CommandPrefix + "giveall", + this.Helper.ConsoleCommands.Add( + name: ModEntry.CommandPrefix + "giveall", documentation: "Give several of all varieties of raised beds.", callback: Cmd_GiveAll); } private void AddGenericModConfigMenu() { - IGenericModConfigMenuAPI modconfigAPI = Helper.ModRegistry.GetApi("spacechase0.GenericModConfigMenu"); + IGenericModConfigMenuAPI modconfigAPI = this.Helper.ModRegistry.GetApi("spacechase0.GenericModConfigMenu"); if (modconfigAPI != null) { modconfigAPI.RegisterModConfig( - mod: ModManifest, - revertToDefault: () => Config = new Config(), - saveToFile: () => Helper.WriteConfig(Config)); + mod: this.ModManifest, + revertToDefault: () => ModEntry.Config = new Config(), + saveToFile: () => this.Helper.WriteConfig(ModEntry.Config)); modconfigAPI.SetDefaultIngameOptinValue( - mod: ModManifest, + mod: this.ModManifest, optedIn: true); - System.Reflection.PropertyInfo[] properties = Config + System.Reflection.PropertyInfo[] properties = ModEntry.Config .GetType() .GetProperties() .Where(p => p.PropertyType == typeof(bool)) @@ -199,20 +199,20 @@ private void AddGenericModConfigMenu() string key = property.Name.ToLower(); string description = Translations.GetTranslation($"config.{key}.description"); modconfigAPI.RegisterSimpleOption( - mod: ModManifest, + mod: this.ModManifest, optionName: Translations.GetTranslation($"config.{key}.name"), optionDesc: string.IsNullOrWhiteSpace(description) ? null : description, - optionGet: () => (bool)property.GetValue(Config), - optionSet: (bool value) => property.SetValue(Config, value: value)); + optionGet: () => (bool)property.GetValue(ModEntry.Config), + optionSet: (bool value) => property.SetValue(ModEntry.Config, value: value)); } } } private void SaveLoadedBehaviours() { - Log.T($"Adding endOfNightStatus definition: {EndOfNightState}"); + Log.T($"Adding endOfNightStatus definition: {ModEntry.EndOfNightState}"); Game1.player.team.endOfNightStatus.AddSpriteDefinition( - key: EndOfNightState, + key: ModEntry.EndOfNightState, file: AssetManager.GameContentEndOfNightSpritesPath, x: 48, y: 0, width: 16, height: 16); @@ -223,20 +223,20 @@ private void SaveLoadedBehaviours() // Reinitialise objects to recalculate XmlIgnore values if (Context.IsMainPlayer) { - OutdoorPot.AdjustAll(); + OutdoorPot.ArrangeAll(); } else { - OutdoorPot.AdjustAllOnNextTick(); + OutdoorPot.ArrangeAllOnNextTick(); } } public void LoadContentPacks() { - ItemDefinitions = new Dictionary(); - Sprites = new Dictionary(); + ModEntry.ItemDefinitions = new Dictionary(); + ModEntry.Sprites = new Dictionary(); - List contentPacks = Helper.ContentPacks.GetOwned().ToList(); + List contentPacks = this.Helper.ContentPacks.GetOwned().ToList(); foreach (IContentPack contentPack in contentPacks) { string packKey = contentPack.Manifest.UniqueID; @@ -299,7 +299,13 @@ public void LoadContentPacks() entry.Value.SpriteKey = packKey; entry.Value.SpriteIndex = parentSheetIndex++; - ItemDefinitions.Add(variantKey, entry.Value); + // Set default DaysToBreak values to unbreakable + if (entry.Value.DaysToBreak <= 0) + { + entry.Value.DaysToBreak = 99999; + } + + ModEntry.ItemDefinitions.Add(variantKey, entry.Value); } // To avoid having to keep many separate spritesheet images updated with any changes, @@ -316,7 +322,7 @@ public void LoadContentPacks() // have the variant's unique soil sprite change when watered. if (data.Count > 0) { - IAssetData asset = Helper.Content.GetPatchHelper(sprites); + IAssetData asset = this.Helper.Content.GetPatchHelper(sprites); Rectangle destination = Rectangle.Empty; Rectangle source; int width = Game1.smallestTileSize; @@ -341,27 +347,27 @@ public void LoadContentPacks() targetArea: destination, patchMode: PatchMode.Overlay); } - Sprites.Add(packKey, sprites); + ModEntry.Sprites.Add(packKey, sprites); } - Log.T($"Loaded {contentPacks.Count} content pack(s) containing {ItemDefinitions.Count} valid objects."); + Log.T($"Loaded {contentPacks.Count} content pack(s) containing {ModEntry.ItemDefinitions.Count} valid objects."); } public static void AddDefaultRecipes() { List recipesToAdd = new List(); int[] eventsSeen = Game1.player.eventsSeen.ToArray(); - string precondition = $"{EventRootId}/{EventData[0]["Conditions"]}"; + string precondition = $"{ModEntry.EventRootId}/{ModEntry.EventData[0]["Conditions"]}"; int rootEventReady = Game1.getFarm().checkEventPrecondition(precondition); - bool hasOrWillSeeRootEvent = eventsSeen.Contains(EventRootId) || rootEventReady != -1; - for (int i = 0; i < ItemDefinitions.Count; ++i) + bool hasOrWillSeeRootEvent = eventsSeen.Contains(ModEntry.EventRootId) || rootEventReady != -1; + for (int i = 0; i < ModEntry.ItemDefinitions.Count; ++i) { - string variantKey = ItemDefinitions.Keys.ElementAt(i); + string variantKey = ModEntry.ItemDefinitions.Keys.ElementAt(i); string craftingRecipeName = OutdoorPot.GetNameFromVariantKey(variantKey: variantKey); bool isAlreadyKnown = Game1.player.craftingRecipes.ContainsKey(craftingRecipeName); - bool isDefaultRecipe = ItemDefinitions[variantKey].RecipeIsDefault; - bool isInitialEventRecipe = string.IsNullOrEmpty(ItemDefinitions[variantKey].RecipeConditions); - bool shouldAdd = Config.RecipesAlwaysAvailable || isDefaultRecipe || (hasOrWillSeeRootEvent && isInitialEventRecipe); + bool isDefaultRecipe = ModEntry.ItemDefinitions[variantKey].RecipeIsDefault; + bool isInitialEventRecipe = string.IsNullOrEmpty(ModEntry.ItemDefinitions[variantKey].RecipeConditions); + bool shouldAdd = ModEntry.Config.RecipesAlwaysAvailable || isDefaultRecipe || (hasOrWillSeeRootEvent && isInitialEventRecipe); if (!isAlreadyKnown && shouldAdd) { @@ -382,20 +388,20 @@ public static void AddDefaultRecipes() public static List AddNewAvailableRecipes() { List newVariants = new List(); - for (int i = 0; i < ItemDefinitions.Count; ++i) + for (int i = 0; i < ModEntry.ItemDefinitions.Count; ++i) { - string variantKey = ItemDefinitions.Keys.ElementAt(i); + string variantKey = ModEntry.ItemDefinitions.Keys.ElementAt(i); string itemName = OutdoorPot.GetNameFromVariantKey(variantKey); if (Game1.player.craftingRecipes.ContainsKey(itemName) - || string.IsNullOrEmpty(ItemDefinitions[variantKey].RecipeConditions) - || !Game1.player.eventsSeen.Contains(EventRootId)) + || string.IsNullOrEmpty(ModEntry.ItemDefinitions[variantKey].RecipeConditions) + || !Game1.player.eventsSeen.Contains(ModEntry.EventRootId)) { continue; } - int eventID = EventRootId + i; - string eventKey = $"{eventID.ToString()}/{ItemDefinitions[variantKey].RecipeConditions}"; + int eventID = ModEntry.EventRootId + i; + string eventKey = $"{eventID.ToString()}/{ModEntry.ItemDefinitions[variantKey].RecipeConditions}"; int precondition = Game1.getFarm().checkEventPrecondition(eventKey); if (precondition != -1) { @@ -429,11 +435,11 @@ public static void Cmd_Give(string s, string[] args) if (Game1.player.craftingRecipes.Keys.All(r => !r.StartsWith(OutdoorPot.GenericName))) { - Log.D($"No raised bed recipes are unlocked! Use '{CommandPrefix}giveall' to add all varieties."); + Log.D($"No raised bed recipes are unlocked! Use '{ModEntry.CommandPrefix}giveall' to add all varieties."); return; } - Log.D($"Adding {quantity} of each unlocked raised bed. Use '{CommandPrefix}giveall' to add all varieties."); + Log.D($"Adding {quantity} of each unlocked raised bed. Use '{ModEntry.CommandPrefix}giveall' to add all varieties."); IEnumerable unlockedKeys = Game1.player.craftingRecipes.Keys .Where(recipe => recipe.StartsWith(OutdoorPot.GenericName)); @@ -452,9 +458,9 @@ public static void Cmd_GiveAll(string s, string[] args) ? argQuantity : defaultQuantity; - Log.D($"Adding {quantity} of all raised beds. Use '{CommandPrefix}give' to add unlocked varieties only."); + Log.D($"Adding {quantity} of all raised beds. Use '{ModEntry.CommandPrefix}give' to add unlocked varieties only."); - foreach (string variantKey in ItemDefinitions.Keys) + foreach (string variantKey in ModEntry.ItemDefinitions.Keys) { ModEntry.Give(variantKey: variantKey, quantity: quantity); } @@ -464,7 +470,7 @@ public static void Cmd_IsEventSeen(string s, string[] args) { int eventId = args.Length > 0 && int.TryParse(args[0], out int argId) ? argId - : EventRootId; + : ModEntry.EventRootId; Log.D($"Player {(Game1.player.eventsSeen.Contains(eventId) ? "has" : "has not")} seen event {eventId}."); } @@ -473,7 +479,7 @@ public static void Cmd_ToggleEventSeen(string s, string[] args) { int eventId = args.Length > 0 && int.TryParse(args[0], out int argId) ? argId - : EventRootId; + : ModEntry.EventRootId; if (Game1.player.eventsSeen.Contains(eventId)) { Game1.player.eventsSeen.Remove(eventId); diff --git a/RaisedGardenBeds/NewRecipeMenu.cs b/RaisedGardenBeds/NewRecipeMenu.cs index ba8292b..e167569 100644 --- a/RaisedGardenBeds/NewRecipeMenu.cs +++ b/RaisedGardenBeds/NewRecipeMenu.cs @@ -356,7 +356,7 @@ public override void draw(SpriteBatch b) yOffset -= (Game1.smallestTileSize * Game1.pixelZoom); b.Draw( texture: ModEntry.Sprites[this._itemSprites[variantKey].Key], - sourceRectangle: OutdoorPot.GetSourceRectangle(spriteIndex: this._itemSprites[variantKey].Value), + sourceRectangle: OutdoorPot.GetSpriteSourceRectangle(spriteIndex: this._itemSprites[variantKey].Value), position: new Vector2( x + xOffset - (Game1.smallestTileSize * 1.5f * Game1.pixelZoom), y + yOffset), diff --git a/RaisedGardenBeds/OutdoorPot.cs b/RaisedGardenBeds/OutdoorPot.cs index e6dc6d4..5ea4652 100644 --- a/RaisedGardenBeds/OutdoorPot.cs +++ b/RaisedGardenBeds/OutdoorPot.cs @@ -40,6 +40,22 @@ public override string DisplayName /// [XmlElement("VariantKey")] public readonly NetString VariantKey = new NetString(); + /// + /// Name of key for this object's texture in the spritesheet list. + /// + public string SpriteKey => ModEntry.ItemDefinitions[this.VariantKey.Value].SpriteKey; + /// + /// Index of this object within its texture in the spritesheet list. + /// + public int SpriteIndex => ModEntry.ItemDefinitions[this.VariantKey.Value].SpriteIndex; + /// + /// Visual Y-offset of soil sprites from object tile Y-position. + /// + public int SoilHeightAboveGround => ModEntry.ItemDefinitions[this.VariantKey.Value].SoilHeightAboveGround; + /// + /// Whether this object will form into large arrangements with its . + /// + public bool CanBeArranged => ModEntry.ItemDefinitions[this.VariantKey.Value].CanBeArranged; /****** Breakage @@ -50,6 +66,10 @@ public override string DisplayName /// public NetInt BreakageTimer = new NetInt(); /// + /// Default number of days before the object can be broken at the end of the season. + /// + public int BreakageStart => ModEntry.ItemDefinitions[this.VariantKey.Value].DaysToBreak; + /// /// Whether object breakage is enabled. /// public bool CanBreak => ModEntry.Config.RaisedBedsMayBreakWithAge; @@ -72,33 +92,13 @@ Temp values [XmlIgnore] public static int BaseParentSheetIndex = -1; /// - /// Visual Y-offset of soil sprites from object tile Y-position. - /// - [XmlIgnore] - public NetInt SoilHeightAboveGround = new NetInt(); - /// - /// Default number of days before the object can be broken at the end of the season. - /// - [XmlIgnore] - public NetInt BreakageStart = new NetInt(); - /// - /// Name of key for this object's texture in the spritesheet list. - /// - [XmlIgnore] - public string SpriteKey { get; set; } - /// - /// Index of this object within its texture in the spritesheet list. - /// - [XmlIgnore] - public int SpriteIndex { get; set; } - /// /// Array of axes that contain a neighbouring OutdoorPot object, projecting outwards from each corner of the object tile. /// [XmlIgnore] public readonly NetArray Neighbours = new NetArray(size: 4); /// - /// Temporary one-tick variable used in - /// in order to indirectly provide the locations to check to the method. + /// Temporary one-tick variable used in + /// in order to indirectly provide the locations to check to the method. /// [XmlIgnore] private static GameLocation LocationToIdentifyOutdoorPots; @@ -198,7 +198,7 @@ public OutdoorPot(string variantKey, Vector2 tileLocation) protected override void initNetFields() { base.initNetFields(); - this.NetFields.AddFields(this.VariantKey, this.SoilHeightAboveGround, this.BreakageStart, this.BreakageTimer, this.Neighbours); + this.NetFields.AddFields(this.VariantKey, this.BreakageTimer, this.Neighbours); this.VariantKey.fieldChangeEvent += this.Event_VariantKeyChanged; } @@ -217,19 +217,11 @@ private void Event_VariantKeyChanged(NetString field, string oldValue, string ne ?? oldValue ?? ModEntry.ItemDefinitions.Keys.FirstOrDefault(key => key.StartsWith(ModEntry.Instance.ModManifest.Author)) ?? ModEntry.ItemDefinitions.Keys.First(); - this.SoilHeightAboveGround.Value = ModEntry.ItemDefinitions[this.VariantKey.Value].SoilHeightAboveGround; - this.BreakageStart.Value = ModEntry.ItemDefinitions[this.VariantKey.Value].DaysToBreak; - if (this.BreakageStart.Value <= 0) - { - this.BreakageStart.Value = 99999; - } this.DisplayName = this.loadDisplayName(); if (resetBreakage) { - this.BreakageTimer.Value = this.BreakageStart.Value; + this.BreakageTimer.Value = this.BreakageStart; } - this.SpriteKey = ModEntry.ItemDefinitions[this.VariantKey.Value].SpriteKey; - this.SpriteIndex = ModEntry.ItemDefinitions[this.VariantKey.Value].SpriteIndex; } public static KeyValuePair GetSpriteFromVariantKey(string variantKey) @@ -237,6 +229,11 @@ public static KeyValuePair GetSpriteFromVariantKey(string variantKe return new KeyValuePair(ModEntry.ItemDefinitions[variantKey].SpriteKey, ModEntry.ItemDefinitions[variantKey].SpriteIndex); } + public static Rectangle GetSpriteSourceRectangle(int spriteIndex) + { + return new Rectangle(Game1.smallestTileSize * OutdoorPot.PreviewIndexInSheet, spriteIndex * Game1.smallestTileSize * 2, Game1.smallestTileSize, Game1.smallestTileSize * 2); + } + public static string GetVariantKeyFromName(string name) { int genericNameSplits = OutdoorPot.GenericName.Split('.').Length; @@ -289,7 +286,7 @@ public override bool placementAction(GameLocation location, int x, int y, Farmer { Vector2 tileLocation = new Vector2(x, y) / Game1.tileSize; location.Objects[tileLocation] = new OutdoorPot(variantKey: this.VariantKey.Value, tileLocation: tileLocation); - OutdoorPot.AdjustWithNeighbours(location: location, tileLocation: tileLocation); + OutdoorPot.ArrangeWithNeighbours(location: location, tileLocation: tileLocation); if (Game1.player.ActiveObject == this) { Game1.player.reduceActiveItemByOne(); @@ -300,10 +297,10 @@ public override bool placementAction(GameLocation location, int x, int y, Farmer public override void performRemoveAction(Vector2 tileLocation, GameLocation environment) { - if (this.TossHeldItem()) + if (this.PopHeldItem()) { base.performRemoveAction(tileLocation, environment); - OutdoorPot.AdjustAllOnNextTick(specificLocation: environment); + OutdoorPot.ArrangeAllOnNextTick(specificLocation: environment); } } @@ -325,7 +322,7 @@ public override bool performToolAction(Tool t, GameLocation location) location.playSound("axchop"); // Remove object without adjusting neighbours, as neighbours have already adjusted to ignore the broken object - if (this.TossHeldItem(pop: true)) + if (this.PopHeldItem(force: true)) { // visual debris Game1.createRadialDebris( @@ -373,11 +370,11 @@ public override bool performToolAction(Tool t, GameLocation location) bool isValidAction = base.performToolAction(t, location); if (isValidAction) { - if (this.TossHeldItem() + if (this.PopHeldItem() && Game1.createItemDebris(this, Game1.player.getStandingPosition(), Game1.player.FacingDirection) is Debris debris && debris != null && location.Objects.Remove(this.TileLocation)) { - OutdoorPot.AdjustWithNeighbours(location: location, tileLocation: this.TileLocation); + OutdoorPot.ArrangeWithNeighbours(location: location, tileLocation: this.TileLocation); } } } @@ -407,7 +404,7 @@ public override bool performObjectDropInAction(Item dropInItem, bool probe, Farm return true; } } - else if (this.IsOpenForPlacement(ignoreObjects: true) && OutdoorPot.IsItemPlaceableNoCrops(dropInItem)) + else if (OutdoorPot.CanAcceptAnything(op: this, ignoreObjects: true) && OutdoorPot.CanAcceptItemNoSeeds(dropInItem)) { // Accept objects if not holding any seeds or crops if (!probe) @@ -416,7 +413,7 @@ public override bool performObjectDropInAction(Item dropInItem, bool probe, Farm { return false; } - else if (this.TossHeldItem()) + else if (this.PopHeldItem()) { this.HoldItem(dropInItem); } @@ -427,7 +424,7 @@ public override bool performObjectDropInAction(Item dropInItem, bool probe, Farm } return true; } - return this.IsOpenForPlacement() && base.performObjectDropInAction(dropInItem, probe, who); + return OutdoorPot.CanAcceptAnything(op: this) && base.performObjectDropInAction(dropInItem, probe, who); } public override bool canBePlacedHere(GameLocation l, Vector2 tile) @@ -461,9 +458,9 @@ public override void ApplySprinklerAnimation(GameLocation location) return; } - Vector2 position = (this.TileLocation * Game1.tileSize) - new Vector2(0, this.SoilHeightAboveGround.Value * Game1.pixelZoom); + Vector2 position = (this.TileLocation * Game1.tileSize) - new Vector2(0, this.SoilHeightAboveGround * Game1.pixelZoom); int delay = Game1.random.Next(1000); - float id = (tileLocation.X * 4000) + tileLocation.Y; + float id = (this.tileLocation.X * 4000) + this.tileLocation.Y; Color colour = Color.White * 0.4f; const int frames = 4; const int interval = 60; @@ -595,7 +592,7 @@ public override void drawWhenHeld(SpriteBatch spriteBatch, Vector2 objectPositio spriteBatch.Draw( texture: ModEntry.Sprites[this.SpriteKey], position: objectPosition, - sourceRectangle: OutdoorPot.GetSourceRectangle(spriteIndex: this.SpriteIndex), + sourceRectangle: OutdoorPot.GetSpriteSourceRectangle(spriteIndex: this.SpriteIndex), color: Color.White, rotation: 0f, origin: Vector2.Zero, @@ -615,7 +612,7 @@ public override void drawInMenu(SpriteBatch spriteBatch, Vector2 location, float spriteBatch.Draw( texture: ModEntry.Sprites[this.SpriteKey], position: location + new Vector2(Game1.tileSize / 2, Game1.tileSize / 2), - sourceRectangle: OutdoorPot.GetSourceRectangle(spriteIndex: this.SpriteIndex), + sourceRectangle: OutdoorPot.GetSpriteSourceRectangle(spriteIndex: this.SpriteIndex), color: color * transparency, rotation: 0f, origin: new Vector2(Game1.smallestTileSize / 2, Game1.smallestTileSize), @@ -655,8 +652,8 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 int yOffset = this.SpriteIndex * Game1.smallestTileSize * 2; // Layer depth used in base game calculations for illusion of depth when rendering world objects - float layerDepth = Math.Max(0f, (((y + 1f) * Game1.tileSize) - (this.SoilHeightAboveGround.Value * Game1.pixelZoom)) / 10000f) + (1 / 10000f); - float layerDepth2 = Math.Max(0f, ((y * Game1.tileSize) - (this.SoilHeightAboveGround.Value * Game1.pixelZoom)) / 10000f) + (1 / 10000f); + float layerDepth = Math.Max(0f, (((y + 1f) * Game1.tileSize) - (this.SoilHeightAboveGround * Game1.pixelZoom)) / 10000f) + (1 / 10000f); + float layerDepth2 = Math.Max(0f, ((y * Game1.tileSize) - (this.SoilHeightAboveGround * Game1.pixelZoom)) / 10000f) + (1 / 10000f); // Broken OutdoorPot if (this.IsBroken) @@ -664,7 +661,7 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 spriteBatch.Draw( texture: ModEntry.Sprites[this.SpriteKey], destinationRectangle: destination, - sourceRectangle: OutdoorPot.GetSourceRectangle(spriteIndex: this.SpriteIndex), + sourceRectangle: OutdoorPot.GetSpriteSourceRectangle(spriteIndex: this.SpriteIndex), color: colour, rotation: 0f, origin: Vector2.Zero, @@ -677,7 +674,7 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 spriteBatch.Draw( texture: ModEntry.Sprites[this.SpriteKey], position: position + (new Vector2(Game1.smallestTileSize / 2, Game1.smallestTileSize) * Game1.pixelZoom), - sourceRectangle: OutdoorPot.GetSourceRectangle(spriteIndex: this.SpriteIndex), + sourceRectangle: OutdoorPot.GetSpriteSourceRectangle(spriteIndex: this.SpriteIndex), color: colour, rotation: 0f, origin: new Vector2(Game1.smallestTileSize / 2, Game1.smallestTileSize), @@ -692,7 +689,7 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 // Soil spriteBatch.Draw( texture: ModEntry.Sprites[this.SpriteKey], - destinationRectangle: new Rectangle(destination.X, destination.Y + ((Game1.smallestTileSize - this.SoilHeightAboveGround.Value) * Game1.pixelZoom), Game1.tileSize, Game1.tileSize), + destinationRectangle: new Rectangle(destination.X, destination.Y + ((Game1.smallestTileSize - this.SoilHeightAboveGround) * Game1.pixelZoom), Game1.tileSize, Game1.tileSize), sourceRectangle: new Rectangle(Game1.smallestTileSize * OutdoorPot.SoilIndexInSheet, yOffset + (this.showNextIndex.Value ? Game1.smallestTileSize : 0), Game1.smallestTileSize, Game1.smallestTileSize), color: colour, rotation: 0f, @@ -751,7 +748,7 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 spriteBatch.Draw( texture: Game1.mouseCursors, - position: Game1.GlobalToLocal(Game1.viewport, new Vector2((this.TileLocation.X * Game1.tileSize) + (1 * Game1.pixelZoom), (this.TileLocation.Y * Game1.tileSize) - (this.SoilHeightAboveGround.Value * 2) - (2 * Game1.pixelZoom))), + position: Game1.GlobalToLocal(Game1.viewport, new Vector2((this.TileLocation.X * Game1.tileSize) + (1 * Game1.pixelZoom), (this.TileLocation.Y * Game1.tileSize) - (this.SoilHeightAboveGround * 2) - (2 * Game1.pixelZoom))), sourceRectangle: fertilizer_rect, color: Color.White, rotation: 0f, @@ -781,7 +778,7 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 this.heldObject.Value.draw( spriteBatch, xNonTile: x * Game1.tileSize, - yNonTile: (y * Game1.tileSize) - objectOffset - (this.SoilHeightAboveGround.Value * Game1.pixelZoom), + yNonTile: (y * Game1.tileSize) - objectOffset - (this.SoilHeightAboveGround * Game1.pixelZoom), layerDepth: ((this.TileLocation.Y + 0.66f) * Game1.tileSize / 10000f) + (1 / 10000f), alpha: 1f); } @@ -792,7 +789,7 @@ public override void draw(SpriteBatch spriteBatch, int x, int y, float alpha = 1 this.bush.Value.draw( spriteBatch, tileLocation: new Vector2(x, y), - yDrawOffset: -(this.SoilHeightAboveGround.Value * Game1.pixelZoom)); + yDrawOffset: -(this.SoilHeightAboveGround * Game1.pixelZoom)); } } @@ -801,31 +798,28 @@ public override Item getOne() return new OutdoorPot(variantKey: this.VariantKey.Value, tileLocation: this.TileLocation); } - public static Rectangle GetSourceRectangle(int spriteIndex) - { - return new Rectangle(Game1.smallestTileSize * OutdoorPot.PreviewIndexInSheet, spriteIndex * Game1.smallestTileSize * 2, Game1.smallestTileSize, Game1.smallestTileSize * 2); - } - - public bool CanPlantHere(Item item) + public static bool CanAcceptAnything(OutdoorPot op, bool ignoreCrops = false, bool ignoreObjects = false) { - return this.hoeDirt.Value.canPlantThisSeedHere(item.ParentSheetIndex, (int)this.TileLocation.X, (int)this.TileLocation.Y); + ignoreObjects |= op.heldObject.Value == null; + ignoreCrops |= (op.hoeDirt.Value.crop == null && op.bush.Value == null); + return !op.IsBroken && ignoreObjects && ignoreCrops; } - public bool IsOpenForPlacement(bool ignoreCrops = false, bool ignoreObjects = false) + public static bool CanAcceptItemOrSeed(Item item) { - ignoreObjects |= this.heldObject.Value == null; - ignoreCrops |= (this.hoeDirt.Value.crop == null && this.bush.Value == null); - return !this.IsBroken && ignoreObjects && ignoreCrops; + return item != null && !(item is Tool) + && !StardewValley.Object.isWildTreeSeed(item.ParentSheetIndex) + && (item.Category == -19 || item.Category == -74 || (item is StardewValley.Object o && o.isSapling())); } - public bool IsHoldingSprinkler() + public static bool CanAcceptItemNoSeeds(Item item) { - return this.heldObject.Value != null && this.heldObject.Value.IsSprinkler(); + return !OutdoorPot.CanAcceptItemOrSeed(item) && item is StardewValley.Object o && ModEntry.Config.SprinklersEnabled && o.IsSprinkler(); } - public int GetSprinklerRadius() + public static bool CanAcceptSeed(Item item, OutdoorPot op) { - return this.IsHoldingSprinkler() ? this.heldObject.Value.GetModifiedRadiusForSprinkler() : -1; + return op.hoeDirt.Value.canPlantThisSeedHere(item.ParentSheetIndex, (int)op.TileLocation.X, (int)op.TileLocation.Y); } public void HoldItem(Item item) @@ -834,27 +828,27 @@ public void HoldItem(Item item) this.heldObject.Value.TileLocation = this.TileLocation; } - public bool TossHeldItem(bool pop = false) + public bool PopHeldItem(bool force = false) { - bool tossed = false; + bool popped = false; - // Toss crops + // Pop crops if (this.hoeDirt.Value.crop != null) { - if (pop && this.hoeDirt.Value.crop.harvest(xTile: (int)this.TileLocation.X, yTile: (int)this.TileLocation.Y, soil: this.hoeDirt.Value)) + if (force && this.hoeDirt.Value.crop.harvest(xTile: (int)this.TileLocation.X, yTile: (int)this.TileLocation.Y, soil: this.hoeDirt.Value)) { - tossed = true; + popped = true; } } - - // Toss held objects + + // Pop held objects if (this.heldObject.Value != null) { - if (pop && Game1.createItemDebris(item: heldObject.Value, origin: this.TileLocation * Game1.tileSize, direction: -1) != null) + if (force && Game1.createItemDebris(item: heldObject.Value, origin: this.TileLocation * Game1.tileSize, direction: -1) != null) { this.heldObject.Value.TileLocation = Vector2.Zero; this.heldObject.Value = null; - tossed = true; + popped = true; } else { @@ -862,7 +856,17 @@ public bool TossHeldItem(bool pop = false) } } - return tossed || (this.hoeDirt.Value.crop == null && this.heldObject.Value == null); + return popped || (this.hoeDirt.Value.crop == null && this.heldObject.Value == null); + } + + public bool IsHoldingSprinkler() + { + return this.heldObject.Value != null && this.heldObject.Value.IsSprinkler(); + } + + public int GetSprinklerRadius() + { + return this.IsHoldingSprinkler() ? this.heldObject.Value.GetModifiedRadiusForSprinkler() : -1; } public void Water() @@ -873,10 +877,10 @@ public void Water() public void Unbreak(GameLocation location = null, bool adjust = false) { - this.BreakageTimer.Value = this.BreakageStart.Value; + this.BreakageTimer.Value = this.BreakageStart; if (adjust) { - OutdoorPot.AdjustWithNeighbours(location: location, tileLocation: this.TileLocation); + OutdoorPot.ArrangeWithNeighbours(location: location, tileLocation: this.TileLocation); } } @@ -885,77 +889,74 @@ public void Unbreak(GameLocation location = null, bool adjust = false) /// public static void BreakAll(GameLocation specificLocation = null) { - foreach (GameLocation location in specificLocation != null ? new[] { specificLocation } : OutdoorPot.GetValidLocations()) + foreach (GameLocation location in specificLocation != null ? new[] { specificLocation } : OutdoorPot.GetValidPlacementLocations()) { List pots = location.Objects.Values.OfType().Where(o => o.IsReadyToBreak).ToList(); - pots.ForEach(pot => pot.Break(location: location, adjust: false)); - OutdoorPot.AdjustAll(specificLocation: location); + pots.ForEach(pot => pot.Break(location: location, arrange: false)); + OutdoorPot.ArrangeAll(specificLocation: location); } } /// /// Set values to mark the object as broken, leaving it unable to continue to grow crops. /// - /// Whether to call after breaking. - public void Break(GameLocation location, bool adjust) + /// Whether to call after breaking. + public void Break(GameLocation location, bool arrange) { this.BreakageTimer.Value = OutdoorPot.BreakageDefinite; - if (adjust) + if (arrange) { - OutdoorPot.AdjustWithNeighbours(location: location, tileLocation: this.TileLocation); + OutdoorPot.ArrangeWithNeighbours(location: location, tileLocation: this.TileLocation); } } /// - /// Calls on the next tick. + /// Calls on the next tick. /// Useful when adjusting sprites after some base game checks, or when removing multiple objects simultaneously. /// - public static void AdjustAllOnNextTick(GameLocation specificLocation = null) + public static void ArrangeAllOnNextTick(GameLocation specificLocation = null) { - ModEntry.Instance.Helper.Events.GameLoop.UpdateTicked += OutdoorPot.Event_AdjustAllOnNextTick; + ModEntry.Instance.Helper.Events.GameLoop.UpdateTicked += OutdoorPot.Event_ArrangeAllOnNextTick; OutdoorPot.LocationToIdentifyOutdoorPots = specificLocation; } - private static void Event_AdjustAllOnNextTick(object sender, StardewModdingAPI.Events.UpdateTickedEventArgs e) + private static void Event_ArrangeAllOnNextTick(object sender, StardewModdingAPI.Events.UpdateTickedEventArgs e) { - ModEntry.Instance.Helper.Events.GameLoop.UpdateTicked -= OutdoorPot.Event_AdjustAllOnNextTick; - OutdoorPot.AdjustAll(specificLocation: OutdoorPot.LocationToIdentifyOutdoorPots); + ModEntry.Instance.Helper.Events.GameLoop.UpdateTicked -= OutdoorPot.Event_ArrangeAllOnNextTick; + OutdoorPot.ArrangeAll(specificLocation: OutdoorPot.LocationToIdentifyOutdoorPots); OutdoorPot.LocationToIdentifyOutdoorPots = null; } - public static void ResetAll(GameLocation specificLocation = null) - { - foreach (GameLocation location in specificLocation != null ? new [] { specificLocation } : OutdoorPot.GetValidLocations()) - location.Objects.Values.OfType().ToList().ForEach(o => o.VariantKey.Value = null); - } - /// /// Adjusts sprites of all objects by reconfirming the positions of other nearby objects. /// Required to form complete shapes using the four-corners method of building raised bed areas from objects. /// - public static void AdjustAll(GameLocation specificLocation = null) + public static void ArrangeAll(GameLocation specificLocation = null) { - foreach (GameLocation location in specificLocation != null ? new[] { specificLocation } : OutdoorPot.GetValidLocations()) - location.Objects.Values.OfType().ToList().ForEach(o => o.Adjust(location: location)); + foreach (GameLocation location in specificLocation != null ? new[] { specificLocation } : OutdoorPot.GetValidPlacementLocations()) + location.Objects.Values.OfType().ToList().ForEach(o => o.Arrange(location: location)); } - public void Adjust(GameLocation location) + public void Arrange(GameLocation location) { + if (!this.CanBeArranged) + return; + for (int i = 0; i < 4; ++i) { Axis n = Axis.None; if (new Vector2(this.TileLocation.X, this.TileLocation.Y + (i > 1 ? 1 : -1)) is Vector2 v1 && location.Objects.ContainsKey(v1) && location.Objects[v1] is StardewValley.Object o1 - && OutdoorPot.CheckNeighbour(p: this, o: o1)) + && OutdoorPot.CanBeArrangedWithNeighbour(p: this, o: o1)) n |= Axis.Vertical; if (new Vector2(this.TileLocation.X + (i % 2 == 1 ? 1 : -1), this.TileLocation.Y) is Vector2 v2 && location.Objects.ContainsKey(v2) && location.Objects[v2] is StardewValley.Object o2 - && OutdoorPot.CheckNeighbour(p: this, o: o2)) + && OutdoorPot.CanBeArrangedWithNeighbour(p: this, o: o2)) n |= Axis.Horizontal; if ((n & (Axis.Vertical | Axis.Horizontal)) != Axis.None && new Vector2(this.TileLocation.X + (i % 2 == 1 ? 1 : -1), this.TileLocation.Y + (i > 1 ? 1 : -1)) is Vector2 v3 && location.Objects.ContainsKey(v3) && location.Objects[v3] is StardewValley.Object o3 - && OutdoorPot.CheckNeighbour(p: this, o: o3)) + && OutdoorPot.CanBeArrangedWithNeighbour(p: this, o: o3)) n |= Axis.Diagonal; if (n == (Axis.Diagonal | Axis.Horizontal)) n = Axis.Horizontal; @@ -963,7 +964,7 @@ public void Adjust(GameLocation location) } } - public static void AdjustWithNeighbours(GameLocation location, Vector2 tileLocation, int radius = 1) + public static void ArrangeWithNeighbours(GameLocation location, Vector2 tileLocation, int radius = 1) { if (location == null) location = Game1.currentLocation; @@ -985,31 +986,22 @@ public static void AdjustWithNeighbours(GameLocation location, Vector2 tileLocat Vector2 tile = new Vector2(x, y); if (location.Objects.ContainsKey(tile) && location.Objects[tile] != null && location.Objects[tile] is OutdoorPot op) { - op.Adjust(location: location); + op.Arrange(location: location); } } } } - private static bool CheckNeighbour(OutdoorPot p, StardewValley.Object o) + private static bool CanBeArrangedWithNeighbour(OutdoorPot p, StardewValley.Object o) { - bool facts = o is OutdoorPot op && op != null && op.canStackWith(p) && !op.IsBroken; + bool facts = o is OutdoorPot op && op != null && op.canStackWith(p) && !op.IsBroken && op.CanBeArranged && p.CanBeArranged; return facts; } - public static bool IsItemPlantable(Item item) - { - return item != null && !(item is Tool) - && !StardewValley.Object.isWildTreeSeed(item.ParentSheetIndex) - && (item.Category == -19 || item.Category == -74 || (item is StardewValley.Object o && o.isSapling())); - } - - public static bool IsItemPlaceableNoCrops(Item item) - { - return !IsItemPlantable(item) && item is StardewValley.Object o && ModEntry.Config.SprinklersEnabled && o.IsSprinkler(); - } - - public static IEnumerable GetValidLocations() + /// + /// Returns a list of locations where objects can be placed depending on values. + /// + public static IEnumerable GetValidPlacementLocations() { var locations = new List { Game1.getFarm() }; if (ModEntry.Config.CanBePlacedInFarmHouse)