diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-11-30 17:14:03 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-11-30 17:14:03 -0500 |
commit | 3342502993c39efec6734c68e4800d29073eeeec (patch) | |
tree | a96f1b62b3aeba3d5b12ad7496de06a94d39c977 /src/SMAPI.Mods.ConsoleCommands | |
parent | d578345cfd53df8a91ae8e0e1346b711332a999a (diff) | |
parent | b294ac1203aa78575f8c72f0be1ee9d3edff15ab (diff) | |
download | SMAPI-3342502993c39efec6734c68e4800d29073eeeec.tar.gz SMAPI-3342502993c39efec6734c68e4800d29073eeeec.tar.bz2 SMAPI-3342502993c39efec6734c68e4800d29073eeeec.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Mods.ConsoleCommands')
3 files changed, 227 insertions, 26 deletions
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs new file mode 100644 index 00000000..6b3b27cd --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs @@ -0,0 +1,221 @@ +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 +{ + /// <summary>A command which changes the player's farm type.</summary> + internal class SetFarmTypeCommand : ConsoleCommand + { + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + public SetFarmTypeCommand() + : base("set_farm_type", "Sets the current player's farm type.\n\nUsage: set_farm_type <farm type>\n- farm type: the farm type to set. Enter `set_farm_type list` for a list of available farm types.") { } + + /// <summary>Handle the command.</summary> + /// <param name="monitor">Writes messages to the console and log file.</param> + /// <param name="command">The command name.</param> + /// <param name="args">The command arguments.</param> + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!Context.IsWorldReady) + { + monitor.Log("You must load a save to use this command.", LogLevel.Error); + return; + } + + // 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 argument + if (farmType == "list") + this.HandleList(monitor); + else if (isVanillaId) + this.HandleVanillaFarmType(vanillaId, monitor); + else + this.HandleCustomFarmType(farmType, monitor); + } + + + /********* + ** Private methods + *********/ + /**** + ** Handlers + ****/ + /// <summary>Print a list of available farm types.</summary> + /// <param name="monitor">Writes messages to the console and log file.</param> + private void HandleList(IMonitor monitor) + { + 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); + } + + /// <summary>Set a vanilla farm type.</summary> + /// <param name="type">The farm type.</param> + /// <param name="monitor">Writes messages to the console and log file.</param> + 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)}"); + } + + /// <summary>Set a custom farm type.</summary> + /// <param name="id">The farm type ID.</param> + /// <param name="monitor">Writes messages to the console and log file.</param> + 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)})"); + } + + /// <summary>Change the farm type.</summary> + /// <param name="type">The farm type ID.</param> + /// <param name="customFarmData">The custom farm type data, if applicable.</param> + 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 + ****/ + /// <summary>Get the display name for a vanilla farm type.</summary> + /// <param name="type">The farm type.</param> + private string GetVanillaName(int type) + { + string translationKey = type switch + { + Farm.default_layout => "Character_FarmStandard", + Farm.riverlands_layout => "Character_FarmFishing", + Farm.forest_layout => "Character_FarmForaging", + Farm.mountains_layout => "Character_FarmMining", + Farm.combat_layout => "Character_FarmCombat", + Farm.fourCorners_layout => "Character_FarmFourCorners", + Farm.beach_layout => "Character_FarmBeach", + _ => null + }; + + return translationKey != null + ? Game1.content.LoadString(@$"Strings\UI:{translationKey}").Split('_')[0] + : type.ToString(); + } + + /// <summary>Get the available vanilla farm types by ID.</summary> + private IDictionary<int, string> GetVanillaFarmTypes() + { + IDictionary<int, string> farmTypes = new Dictionary<int, string>(); + + foreach (int id in Enumerable.Range(0, Farm.layout_max)) + farmTypes[id] = this.GetVanillaName(id); + + return farmTypes; + } + + /**** + ** Custom farm types + ****/ + /// <summary>Get the display name for a custom farm type.</summary> + /// <param name="farmType">The custom farm type.</param> + private string GetCustomName(ModFarmType farmType) + { + if (string.IsNullOrWhiteSpace(farmType?.TooltipStringPath)) + return farmType?.ID; + + return Game1.content.LoadString(farmType.TooltipStringPath)?.Split('_')[0] ?? farmType.ID; + } + + /// <summary>Get the available custom farm types by ID.</summary> + private IDictionary<string, ModFarmType> GetCustomFarmTypes() + { + IDictionary<string, ModFarmType> farmTypes = new Dictionary<string, ModFarmType>(StringComparer.OrdinalIgnoreCase); + + foreach (ModFarmType farmType in Game1.content.Load<List<ModFarmType>>("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 528348a0..e3db8b47 100644 --- a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj +++ b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <AssemblyName>ConsoleCommands</AssemblyName> <RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace> - <TargetFramework>net452</TargetFramework> + <TargetFramework>net5.0</TargetFramework> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> @@ -13,32 +13,12 @@ </ItemGroup> <ItemGroup> - <Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" /> + <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" /> + <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" /> <Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" /> + <Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" /> </ItemGroup> - <!-- Windows only --> - <ItemGroup Condition="'$(OS)' == 'Windows_NT'"> - <Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" /> - </ItemGroup> - - <!-- Game framework --> - <Choose> - <When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))"> - <ItemGroup> - <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" /> - <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" /> - <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" /> - <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" /> - </ItemGroup> - </When> - <Otherwise> - <ItemGroup> - <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" /> - </ItemGroup> - </Otherwise> - </Choose> - <ItemGroup> <None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 9af3ce5d..85653a7d 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.12.8", + "Version": "3.13.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.12.8" + "MinimumApiVersion": "3.13.0" } |