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)