summaryrefslogtreecommitdiff
path: root/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs
blob: 3d55b42542cc9883edaa62504d211c59d39221c4 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System;
using System.Collections.Generic;
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
    {
        /*********
        ** Properties
        *********/
        /// <summary>Provides methods for searching and constructing items.</summary>
        private readonly ItemRepository Items = new ItemRepository();

        private readonly string[] ItemTypeAndName = Enum.GetNames(typeof(ItemType)).Union(new string[] { "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;
            }

            SearchableItem match;

            //read arguments
            if (!args.TryGet(0, "item type", out string typeOrName, oneOf: this.ItemTypeAndName))
                return;
            if (Enum.GetNames(typeof(ItemType)).Contains(typeOrName, StringComparer.InvariantCultureIgnoreCase))
                this.FindItemByTypeAndId(monitor, args, typeOrName, out match);
            else
                this.FindItemByName(monitor, args, out match);

            if (match == null)
                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;

            // 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>
        /// Finds a matching item by item type and id.
        /// </summary>
        /// <param name="monitor">Writes messages to the console and log file.</param>
        /// <param name="args">The command arguments.</param>
        /// <param name="rawType">The raw item type.</param>
        /// <param name="match">The matching item.</param>
        private void FindItemByTypeAndId(IMonitor monitor, ArgumentParser args, string rawType, out SearchableItem match)
        {
            match = null;

            // read arguments
            if (!args.TryGetInt(1, "item ID", out int id, min: 0))
                return;

            ItemType type = (ItemType)Enum.Parse(typeof(ItemType), rawType, ignoreCase: true);

            // find matching item
            match = this.Items.GetAll().FirstOrDefault(p => p.Type == type && p.ID == id);

            if (match == null)
            {
                monitor.Log($"There's no {type} item with ID {id}.", LogLevel.Error);
            }
        }

        /// <summary>
        /// Finds a matching item by name.
        /// </summary>
        /// <param name="monitor">Writes messages to the console and log file.</param>
        /// <param name="args">The command arguments.</param>
        /// <param name="name">The item name.</param>
        /// <param name="match">The matching item.</param>
        private void FindItemByName(IMonitor monitor, ArgumentParser args, out SearchableItem match)
        {
            match = null;

            // read arguments
            if (!args.TryGet(1, "item name", out string name))
                return;

            // find matching items
            IEnumerable<SearchableItem> matching = this.Items.GetAll().Where(p => p.DisplayName.IndexOf(name, StringComparison.InvariantCultureIgnoreCase) != -1);
            SearchableItem exactMatch = matching.FirstOrDefault(item => item.DisplayName.Equals(name, StringComparison.InvariantCultureIgnoreCase));

            int numberOfMatches = matching.Count();

            // handle unique requirement
            if (exactMatch != null)
            {
                match = matching.ElementAt(0);
            }
            else if (numberOfMatches == 0)
            {
                monitor.Log($"There's no item with name '{name}'. You can use the 'list_items [name]' command to search for items.", LogLevel.Error);
            }
            else if (numberOfMatches == 1)
            {
                monitor.Log($"There's no item with name '{name}'. Did you mean '{matching.ElementAt(0).DisplayName}'? If so, type 'player_add name {matching.ElementAt(0).DisplayName}'.", LogLevel.Error);
            }
            else
            {
                string options = this.GetTableString(matching, new string[] { "type", "name", "command" }, item => new string[] { item.Type.ToString(), item.DisplayName, $"player_add {item.Type} {item.ID}" });

                monitor.Log($"Found multiple item names containing '{name}'. Type one of these commands for the one you want:", LogLevel.Error);
                monitor.Log($"\n{options}", LogLevel.Info);
            }
        }

        private static string GetDescription()
        {
            string[] typeValues = Enum.GetNames(typeof(ItemType));
            return "Gives the player an item.\n"
                + "\n"
                + "Usage: player_add <type> (<item>|<name>) [count] [quality]\n"
                + $"- type: the item type (either Name or one of {string.Join(", ", typeValues)}).\n"
                + "- item: the item ID (use the 'list_items' command to see a list).\n"
                + "- name: the display name of the item (when using type Name).\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"
                + "This example adds the galaxy sword to your inventory:\n"
                + "  player_add weapon 4";
        }
    }
}