diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs
index 0bebd2b7..6b3b27cd 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs
@@ -1,27 +1,23 @@
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
+using System.Text;
using StardewValley;
+using StardewValley.GameData;
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
{
/// A command which changes the player's farm type.
internal class SetFarmTypeCommand : ConsoleCommand
{
- /*********
- ** Fields
- *********/
- /// The vanilla farm type IDs.
- private static readonly ISet VanillaFarmTypes = new HashSet(
- Enumerable.Range(0, Farm.layout_max + 1)
- );
-
-
/*********
** Public methods
*********/
/// Construct an instance.
public SetFarmTypeCommand()
- : base("set_farm_type", $"Sets the current player's farm type.\n\nUsage: set_farm_type \n- farm type: one of {string.Join(", ", SetFarmTypeCommand.VanillaFarmTypes.Select(id => $"{id} ({SetFarmTypeCommand.GetFarmLabel(id)})"))}.") { }
+ : base("set_farm_type", "Sets the current player's farm type.\n\nUsage: set_farm_type \n- farm type: the farm type to set. Enter `set_farm_type list` for a list of available farm types.") { }
/// Handle the command.
/// Writes messages to the console and log file.
@@ -29,47 +25,141 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
/// The command arguments.
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
{
- // validation checks
+ // validate
if (!Context.IsWorldReady)
{
monitor.Log("You must load a save to use this command.", LogLevel.Error);
return;
}
- // parse argument
- if (!args.TryGetInt(0, "farm type", out int farmType, min: 0, max: Farm.layout_max))
+ // parse arguments
+ if (!args.TryGet(0, "farm type", out string farmType))
return;
+ bool isVanillaId = int.TryParse(farmType, out int vanillaId) && vanillaId is (>= 0 and < Farm.layout_max);
- // handle
- if (Game1.whichFarm == farmType)
- {
- monitor.Log($"Your current farm is already set to {farmType} ({SetFarmTypeCommand.GetFarmLabel(farmType)}).", LogLevel.Info);
- return;
- }
-
- this.SetFarmType(farmType);
- monitor.Log($"Your current farm has been converted to {farmType} ({SetFarmTypeCommand.GetFarmLabel(farmType)}).", LogLevel.Warn);
- monitor.Log("Saving and reloading is recommended to make sure everything is updated for the change.", LogLevel.Warn);
+ // handle argument
+ if (farmType == "list")
+ this.HandleList(monitor);
+ else if (isVanillaId)
+ this.HandleVanillaFarmType(vanillaId, monitor);
+ else
+ this.HandleCustomFarmType(farmType, monitor);
}
/*********
** Private methods
*********/
- /// Change the farm type to the given value.
- /// The farm type ID.
- private void SetFarmType(int type)
+ /****
+ ** Handlers
+ ****/
+ /// Print a list of available farm types.
+ /// Writes messages to the console and log file.
+ private void HandleList(IMonitor monitor)
{
- Game1.whichFarm = type;
+ StringBuilder result = new();
+ // list vanilla types
+ result.AppendLine("The farm type can be one of these vanilla types:");
+ foreach (var type in this.GetVanillaFarmTypes())
+ result.AppendLine($" - {type.Key} ({type.Value})");
+ result.AppendLine();
+
+ // list custom types
+ {
+ var customTypes = this.GetCustomFarmTypes();
+ if (customTypes.Any())
+ {
+ result.AppendLine("Or one of these custom farm types:");
+ foreach (var type in customTypes.Values.OrderBy(p => p.ID))
+ result.AppendLine($" - {type.ID} ({this.GetCustomName(type)})");
+ }
+ else
+ result.AppendLine("Or a custom farm type (though none is loaded currently).");
+ }
+
+ // print
+ monitor.Log(result.ToString(), LogLevel.Info);
+ }
+
+ /// Set a vanilla farm type.
+ /// The farm type.
+ /// Writes messages to the console and log file.
+ private void HandleVanillaFarmType(int type, IMonitor monitor)
+ {
+ if (Game1.whichFarm == type)
+ {
+ monitor.Log($"Your current farm is already set to {type} ({this.GetVanillaName(type)}).", LogLevel.Info);
+ return;
+ }
+
+ this.SetFarmType(type, null);
+ this.PrintSuccess(monitor, $"{type} ({this.GetVanillaName(type)}");
+ }
+
+ /// Set a custom farm type.
+ /// The farm type ID.
+ /// Writes messages to the console and log file.
+ private void HandleCustomFarmType(string id, IMonitor monitor)
+ {
+ if (Game1.whichModFarm?.ID == id)
+ {
+ monitor.Log($"Your current farm is already set to {id} ({this.GetCustomName(Game1.whichModFarm)}).", LogLevel.Info);
+ return;
+ }
+
+ if (!this.GetCustomFarmTypes().TryGetValue(id, out ModFarmType customFarmType))
+ {
+ monitor.Log($"Invalid farm type '{id}'. Enter `help set_farm_type` for more info.", LogLevel.Error);
+ return;
+ }
+
+ this.SetFarmType(Farm.mod_layout, customFarmType);
+ this.PrintSuccess(monitor, $"{id} ({this.GetCustomName(customFarmType)})");
+ }
+
+ /// Change the farm type.
+ /// The farm type ID.
+ /// The custom farm type data, if applicable.
+ private void SetFarmType(int type, ModFarmType customFarmData)
+ {
+ // set flags
+ Game1.whichFarm = type;
+ Game1.whichModFarm = customFarmData;
+
+ // update farm map
Farm farm = Game1.getFarm();
farm.mapPath.Value = $@"Maps\{Farm.getMapNameFromTypeInt(Game1.whichFarm)}";
farm.reloadMap();
+
+ // clear spouse area cache to avoid errors
+ FieldInfo cacheField = farm.GetType().GetField("_baseSpouseAreaTiles", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ if (cacheField == null)
+ throw new InvalidOperationException("Failed to access '_baseSpouseAreaTiles' field to clear spouse area cache.");
+ if (cacheField.GetValue(farm) is not IDictionary cache)
+ throw new InvalidOperationException($"The farm's '_baseSpouseAreaTiles' field didn't match the expected {nameof(IDictionary)} type.");
+ cache.Clear();
}
+ private void PrintSuccess(IMonitor monitor, string label)
+ {
+ StringBuilder result = new();
+ result.AppendLine($"Your current farm has been converted to {label}. Saving and reloading is recommended to make sure everything is updated for the change.");
+ result.AppendLine();
+ result.AppendLine("This doesn't move items that are out of bounds on the new map. If you need to clean up, you can...");
+ result.AppendLine(" - temporarily switch back to the previous farm type;");
+ result.AppendLine(" - or use a mod like Noclip Mode: https://www.nexusmods.com/stardewvalley/mods/3900 ;");
+ result.AppendLine(" - or use the world_clear console command (enter `help world_clear` for details).");
+
+ monitor.Log(result.ToString(), LogLevel.Warn);
+ }
+
+ /****
+ ** Vanilla farm types
+ ****/
/// Get the display name for a vanilla farm type.
/// The farm type.
- private static string GetFarmLabel(int type)
+ private string GetVanillaName(int type)
{
string translationKey = type switch
{
@@ -87,5 +177,45 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
? Game1.content.LoadString(@$"Strings\UI:{translationKey}").Split('_')[0]
: type.ToString();
}
+
+ /// Get the available vanilla farm types by ID.
+ private IDictionary GetVanillaFarmTypes()
+ {
+ IDictionary farmTypes = new Dictionary();
+
+ foreach (int id in Enumerable.Range(0, Farm.layout_max))
+ farmTypes[id] = this.GetVanillaName(id);
+
+ return farmTypes;
+ }
+
+ /****
+ ** Custom farm types
+ ****/
+ /// Get the display name for a custom farm type.
+ /// The custom farm type.
+ private string GetCustomName(ModFarmType farmType)
+ {
+ if (string.IsNullOrWhiteSpace(farmType?.TooltipStringPath))
+ return farmType?.ID;
+
+ return Game1.content.LoadString(farmType.TooltipStringPath)?.Split('_')[0] ?? farmType.ID;
+ }
+
+ /// Get the available custom farm types by ID.
+ private IDictionary GetCustomFarmTypes()
+ {
+ IDictionary farmTypes = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ foreach (ModFarmType farmType in Game1.content.Load>("Data\\AdditionalFarms"))
+ {
+ if (string.IsNullOrWhiteSpace(farmType.ID))
+ continue;
+
+ farmTypes[farmType.ID] = farmType;
+ }
+
+ return farmTypes;
+ }
}
}
diff --git a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
index 52439c41..e3db8b47 100644
--- a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
+++ b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
@@ -16,6 +16,7 @@
+