summaryrefslogtreecommitdiff
path: root/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs
blob: 263e126c2dcb55cb423d09899cdc9c55d9e8b5fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System;
using System.Linq;
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
using StardewValley;
using Object = StardewValley.Object;

namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
{
    /// <summary>A command which adds an item to the player inventory.</summary>
    internal class AddCommand : TrainerCommand
    {
        /*********
        ** Fields
        *********/
        /// <summary>Provides methods for searching and constructing items.</summary>
        private readonly ItemRepository Items = new ItemRepository();

        /// <summary>The type names recognised by this command.</summary>
        private readonly string[] ValidTypes = Enum.GetNames(typeof(ItemType)).Concat(new[] { "Name" }).ToArray();


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        public AddCommand()
            : base("player_add", AddCommand.GetDescription()) { }

        /// <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 need to load a save to use this command.", LogLevel.Error);
                return;
            }

            // read arguments
            if (!args.TryGet(0, "item type", out string type, oneOf: this.ValidTypes))
                return;
            if (!args.TryGetInt(2, "count", out int count, min: 1, required: false))
                count = 1;
            if (!args.TryGetInt(3, "quality", out int quality, min: Object.lowQuality, max: Object.bestQuality, required: false))
                quality = Object.lowQuality;

            // find matching item
            SearchableItem match = Enum.TryParse(type, true, out ItemType itemType)
                ? this.FindItemByID(monitor, args, itemType)
                : this.FindItemByName(monitor, args);
            if (match == null)
                return;

            // apply count
            match.Item.Stack = count;

            // apply quality
            if (match.Item is Object obj)
                obj.Quality = quality;
            else if (match.Item is Tool tool)
                tool.UpgradeLevel = quality;

            // add to inventory
            Game1.player.addItemByMenuIfNecessary(match.Item);
            monitor.Log($"OK, added {match.Name} ({match.Type} #{match.ID}) to your inventory.", LogLevel.Info);
        }


        /*********
        ** Private methods
        *********/
        /// <summary>Get a matching item by its ID.</summary>
        /// <param name="monitor">Writes messages to the console and log file.</param>
        /// <param name="args">The command arguments.</param>
        /// <param name="type">The item type.</param>
        private SearchableItem FindItemByID(IMonitor monitor, ArgumentParser args, ItemType type)
        {
            // read arguments
            if (!args.TryGetInt(1, "item ID", out int id, min: 0))
                return null;

            // find matching item
            SearchableItem item = this.Items.GetAll().FirstOrDefault(p => p.Type == type && p.ID == id);
            if (item == null)
                monitor.Log($"There's no {type} item with ID {id}.", LogLevel.Error);
            return item;
        }

        /// <summary>Get a matching item by its name.</summary>
        /// <param name="monitor">Writes messages to the console and log file.</param>
        /// <param name="args">The command arguments.</param>
        private SearchableItem FindItemByName(IMonitor monitor, ArgumentParser args)
        {
            // read arguments
            if (!args.TryGet(1, "item name", out string name))
                return null;

            // find matching items
            SearchableItem[] matches = this.Items.GetAll().Where(p => p.NameContains(name)).ToArray();
            if (!matches.Any())
            {
                monitor.Log($"There's no item with name '{name}'. You can use the 'list_items [name]' command to search for items.", LogLevel.Error);
                return null;
            }

            // handle single exact match
            SearchableItem[] exactMatches = matches.Where(p => p.NameEquivalentTo(name)).ToArray();
            if (exactMatches.Length == 1)
                return exactMatches[0];

            // handle ambiguous results
            string options = this.GetTableString(
                data: matches,
                header: new[] { "type", "name", "command" },
                getRow: item => new[] { item.Type.ToString(), item.DisplayName, $"player_add {item.Type} {item.ID}" }
            );
            monitor.Log($"There's no item with name '{name}'. Do you mean one of these?\n\n{options}", LogLevel.Info);
            return null;
        }

        /// <summary>Get the command description.</summary>
        private static string GetDescription()
        {
            string[] typeValues = Enum.GetNames(typeof(ItemType));
            return "Gives the player an item.\n"
                + "\n"
                + "Usage: player_add <type> <item> [count] [quality]\n"
                + $"- type: the item type (one of {string.Join(", ", typeValues)}).\n"
                + "- item: the item ID (use the 'list_items' command to see a list).\n"
                + "- count (optional): how many of the item to give.\n"
                + $"- quality (optional): one of {Object.lowQuality} (normal), {Object.medQuality} (silver), {Object.highQuality} (gold), or {Object.bestQuality} (iridium).\n"
                + "\n"
                + "Usage: player_add name \"<name>\" [count] [quality]\n"
                + "- name: the item name to search (use the 'list_items' command to see a list). This will add the item immediately if it's an exact match, else show a table of matching items.\n"
                + "- count (optional): how many of the item to give.\n"
                + $"- quality (optional): one of {Object.lowQuality} (normal), {Object.medQuality} (silver), {Object.highQuality} (gold), or {Object.bestQuality} (iridium).\n"
                + "\n"
                + "These examples both add the galaxy sword to your inventory:\n"
                + "  player_add weapon 4\n"
                + "  player_add name \"Galaxy Sword\"";
        }
    }
}