Fix: CmdSetAutoReplace didn't validate group type and engine type match (#9950)
[openttd-github.git] / src / command_func.h
blobd78c8229a4a38a9f143c5d5b507aef3f0305e8db
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file command_func.h Functions related to commands. */
10 #ifndef COMMAND_FUNC_H
11 #define COMMAND_FUNC_H
13 #include "command_type.h"
14 #include "network/network_type.h"
15 #include "company_type.h"
16 #include "company_func.h"
17 #include "core/backup_type.hpp"
18 #include "misc/endian_buffer.hpp"
19 #include "tile_map.h"
21 /**
22 * Define a default return value for a failed command.
24 * This variable contains a CommandCost object with is declared as "failed".
25 * Other functions just need to return this error if there is an error,
26 * which doesn't need to specific by a StringID.
28 static const CommandCost CMD_ERROR = CommandCost(INVALID_STRING_ID);
30 /**
31 * Returns from a function with a specific StringID as error.
33 * This macro is used to return from a function. The parameter contains the
34 * StringID which will be returned.
36 * @param errcode The StringID to return
38 #define return_cmd_error(errcode) return CommandCost(errcode);
40 void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, TileIndex location, const CommandDataBuffer &cmd_data);
42 bool IsValidCommand(Commands cmd);
43 CommandFlags GetCommandFlags(Commands cmd);
44 const char *GetCommandName(Commands cmd);
45 Money GetAvailableMoneyForCommand();
46 bool IsCommandAllowedWhilePaused(Commands cmd);
48 template <Commands Tcmd>
49 constexpr CommandFlags GetCommandFlags()
51 return CommandTraits<Tcmd>::flags;
54 /**
55 * Extracts the DC flags needed for DoCommand from the flags returned by GetCommandFlags
56 * @param cmd_flags Flags from GetCommandFlags
57 * @return flags for DoCommand
59 static constexpr inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags)
61 DoCommandFlag flags = DC_NONE;
62 if (cmd_flags & CMD_NO_WATER) flags |= DC_NO_WATER;
63 if (cmd_flags & CMD_AUTO) flags |= DC_AUTO;
64 if (cmd_flags & CMD_ALL_TILES) flags |= DC_ALL_TILES;
65 return flags;
68 /** Helper class to keep track of command nesting level. */
69 struct RecursiveCommandCounter {
70 RecursiveCommandCounter() noexcept { _counter++; }
71 ~RecursiveCommandCounter() noexcept { _counter--; }
73 /** Are we in the top-level command execution? */
74 bool IsTopLevel() const { return _counter == 1; }
75 private:
76 static int _counter;
79 #if defined(__GNUC__) && !defined(__clang__)
81 * We cast specialized function pointers to a generic one, but don't use the
82 * converted value to call the function, which is safe, except that GCC
83 * helpfully thinks it is not.
85 * "Any pointer to function can be converted to a pointer to a different function type.
86 * Calling the function through a pointer to a different function type is undefined,
87 * but converting such pointer back to pointer to the original function type yields
88 * the pointer to the original function." */
89 # pragma GCC diagnostic push
90 # pragma GCC diagnostic ignored "-Wcast-function-type"
91 # define SILENCE_GCC_FUNCTION_POINTER_CAST
92 #endif
94 template<Commands TCmd, typename T, bool THasTile> struct CommandHelper;
96 class CommandHelperBase {
97 protected:
98 static void InternalDoBefore(bool top_level, bool test);
99 static void InternalDoAfter(CommandCost &res, DoCommandFlag flags, bool top_level, bool test);
100 static std::tuple<bool, bool, bool> InternalPostBefore(Commands cmd, CommandFlags flags, TileIndex tile, StringID err_message, bool network_command);
101 static void InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd);
102 static bool InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup<CompanyID> &cur_company);
103 static std::tuple<bool, bool, bool> InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup<CompanyID> &cur_company);
104 static CommandCost InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup<CompanyID> &cur_company);
105 static void LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed);
109 * Templated wrapper that exposes the command parameter arguments
110 * for the various Command::Do/Post calls.
111 * @tparam Tcmd The command-id to execute.
112 * @tparam Tret Return type of the command.
113 * @tparam Targs The command parameter types.
115 template <Commands Tcmd, typename Tret, typename... Targs>
116 struct CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true> : protected CommandHelperBase {
117 private:
118 /** Extract the \c CommandCost from a command proc result. */
119 static inline CommandCost &ExtractCommandCost(Tret &ret)
121 if constexpr (std::is_same_v<Tret, CommandCost>) {
122 return ret;
123 } else {
124 return std::get<0>(ret);
128 /** Make a command proc result from a \c CommandCost. */
129 static inline Tret MakeResult(const CommandCost &cost)
131 Tret ret{};
132 ExtractCommandCost(ret) = cost;
133 return ret;
136 public:
138 * This function executes a given command with the parameters from the #CommandProc parameter list.
139 * Depending on the flags parameter it executes or tests a command.
141 * @note This function is to be called from the StateGameLoop or from within the execution of a Command.
142 * This function must not be called from the context of a "player" (real person, AI, game script).
143 * Use ::Post for commands directly triggered by "players".
145 * @param flags Flags for the command and how to execute the command
146 * @param args Parameters for the command
147 * @see CommandProc
148 * @return the cost
150 static Tret Do(DoCommandFlag flags, Targs... args)
152 if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, std::tuple<Targs...>>>) {
153 /* Do not even think about executing out-of-bounds tile-commands. */
154 TileIndex tile = std::get<0>(std::make_tuple(args...));
155 if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return MakeResult(CMD_ERROR);
158 RecursiveCommandCounter counter{};
160 /* Only execute the test call if it's toplevel, or we're not execing. */
161 if (counter.IsTopLevel() || !(flags & DC_EXEC)) {
162 InternalDoBefore(counter.IsTopLevel(), true);
163 Tret res = CommandTraits<Tcmd>::proc(flags & ~DC_EXEC, args...);
164 InternalDoAfter(ExtractCommandCost(res), flags, counter.IsTopLevel(), true); // Can modify res.
166 if (ExtractCommandCost(res).Failed() || !(flags & DC_EXEC)) return res;
169 /* Execute the command here. All cost-relevant functions set the expenses type
170 * themselves to the cost object at some point. */
171 InternalDoBefore(counter.IsTopLevel(), false);
172 Tret res = CommandTraits<Tcmd>::proc(flags, args...);
173 InternalDoAfter(ExtractCommandCost(res), flags, counter.IsTopLevel(), false);
175 return res;
179 * Shortcut for the long Post when not using a callback.
180 * @param err_message Message prefix to show on error
181 * @param args Parameters for the command
183 static inline bool Post(StringID err_message, Targs... args) { return Post<CommandCallback>(err_message, nullptr, std::forward<Targs>(args)...); }
185 * Shortcut for the long Post when not using an error message.
186 * @param callback A callback function to call after the command is finished
187 * @param args Parameters for the command
189 template <typename Tcallback>
190 static inline bool Post(Tcallback *callback, Targs... args) { return Post((StringID)0, callback, std::forward<Targs>(args)...); }
192 * Shortcut for the long Post when not using a callback or an error message.
193 * @param args Parameters for the command
195 static inline bool Post(Targs... args) { return Post<CommandCallback>((StringID)0, nullptr, std::forward<Targs>(args)...); }
198 * Top-level network safe command execution for the current company.
199 * Must not be called recursively. The callback is called when the
200 * command succeeded or failed.
202 * @param err_message Message prefix to show on error
203 * @param callback A callback function to call after the command is finished
204 * @param args Parameters for the command
205 * @return \c true if the command succeeded, else \c false.
207 template <typename Tcallback>
208 static bool Post(StringID err_message, Tcallback *callback, Targs... args)
210 return InternalPost(err_message, callback, true, false, std::forward_as_tuple(args...));
214 * Execute a command coming from the network.
215 * @param err_message Message prefix to show on error
216 * @param callback A callback function to call after the command is finished
217 * @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
218 * @param location Tile location for user feedback.
219 * @param args Parameters for the command
220 * @return \c true if the command succeeded, else \c false.
222 template <typename Tcallback>
223 static bool PostFromNet(StringID err_message, Tcallback *callback, bool my_cmd, TileIndex location, std::tuple<Targs...> args)
225 if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args)>>) {
226 /* Do not even think about executing out-of-bounds tile-commands. */
227 TileIndex tile = std::get<0>(args);
228 if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (GetCommandFlags<Tcmd>() & CMD_ALL_TILES) == 0))) return false;
231 return InternalPost(err_message, callback, my_cmd, true, location, std::move(args));
235 * Prepare a command to be send over the network
236 * @param cmd The command to execute (a CMD_* value)
237 * @param err_message Message prefix to show on error
238 * @param company The company that wants to send the command
239 * @param args Parameters for the command
241 static void SendNet(StringID err_message, CompanyID company, Targs... args)
243 auto args_tuple = std::forward_as_tuple(args...);
245 TileIndex tile{};
246 if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args_tuple)>>) {
247 tile = std::get<0>(args_tuple);
250 ::NetworkSendCommand(Tcmd, err_message, nullptr, _current_company, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args_tuple));
254 * Top-level network safe command execution without safety checks.
255 * @param err_message Message prefix to show on error
256 * @param callback A callback function to call after the command is finished
257 * @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
258 * @param estimate_only whether to give only the estimate or also execute the command
259 * @param location Tile location for user feedback.
260 * @param args Parameters for the command
261 * @return the command cost of this function.
263 template <typename Tcallback>
264 static Tret Unsafe(StringID err_message, Tcallback *callback, bool my_cmd, bool estimate_only, TileIndex location, std::tuple<Targs...> args)
266 return Execute(err_message, reinterpret_cast<CommandCallback *>(callback), my_cmd, estimate_only, false, location, std::move(args));
269 protected:
270 /** Helper to process a single ClientID argument. */
271 template <class T>
272 static inline void SetClientIdHelper(T &data)
274 if constexpr (std::is_same_v<ClientID, T>) {
275 if (data == INVALID_CLIENT_ID) data = CLIENT_ID_SERVER;
279 /** Set all invalid ClientID's to the proper value. */
280 template<class Ttuple, size_t... Tindices>
281 static inline void SetClientIds(Ttuple &values, std::index_sequence<Tindices...>)
283 ((SetClientIdHelper(std::get<Tindices>(values))), ...);
286 /** Remove the first element of a tuple. */
287 template <template <typename...> typename Tt, typename T1, typename... Ts>
288 static inline Tt<Ts...> RemoveFirstTupleElement(const Tt<T1, Ts...> &tuple)
290 return std::apply([](auto &&, const auto&... args) { return std::tie(args...); }, tuple);
293 template <typename Tcallback>
294 static bool InternalPost(StringID err_message, Tcallback *callback, bool my_cmd, bool network_command, std::tuple<Targs...> args)
296 /* Where to show the message? */
297 TileIndex tile{};
298 if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args)>>) {
299 tile = std::get<0>(args);
302 return InternalPost(err_message, callback, my_cmd, network_command, tile, std::move(args));
305 template <typename Tcallback>
306 static bool InternalPost(StringID err_message, Tcallback *callback, bool my_cmd, bool network_command, TileIndex tile, std::tuple<Targs...> args)
308 /* Do not even think about executing out-of-bounds tile-commands. */
309 if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (GetCommandFlags<Tcmd>() & CMD_ALL_TILES) == 0))) return false;
311 auto [err, estimate_only, only_sending] = InternalPostBefore(Tcmd, GetCommandFlags<Tcmd>(), tile, err_message, network_command);
312 if (err) return false;
314 /* Only set client IDs when the command does not come from the network. */
315 if (!network_command && GetCommandFlags<Tcmd>() & CMD_CLIENT_ID) SetClientIds(args, std::index_sequence_for<Targs...>{});
317 Tret res = Execute(err_message, reinterpret_cast<CommandCallback *>(callback), my_cmd, estimate_only, network_command, tile, args);
318 InternalPostResult(ExtractCommandCost(res), tile, estimate_only, only_sending, err_message, my_cmd);
320 if (!estimate_only && !only_sending && callback != nullptr) {
321 if constexpr (std::is_same_v<Tcallback, CommandCallback>) {
322 /* Callback that doesn't need any command arguments. */
323 callback(Tcmd, ExtractCommandCost(res), tile);
324 } else if constexpr (std::is_same_v<Tcallback, CommandCallbackData>) {
325 /* Generic callback that takes packed arguments as a buffer. */
326 if constexpr (std::is_same_v<Tret, CommandCost>) {
327 callback(Tcmd, ExtractCommandCost(res), tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), {});
328 } else {
329 callback(Tcmd, ExtractCommandCost(res), tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), EndianBufferWriter<CommandDataBuffer>::FromValue(RemoveFirstTupleElement(res)));
331 } else if constexpr (!std::is_same_v<Tret, CommandCost> && std::is_same_v<Tcallback *, typename CommandTraits<Tcmd>::RetCallbackProc>) {
332 std::apply(callback, std::tuple_cat(std::make_tuple(Tcmd), res));
333 } else {
334 /* Callback with arguments. We assume that the tile is only interesting if it actually is in the command arguments. */
335 if constexpr (std::is_same_v<Tret, CommandCost>) {
336 std::apply(callback, std::tuple_cat(std::make_tuple(Tcmd, res), args));
337 } else {
338 std::apply(callback, std::tuple_cat(std::make_tuple(Tcmd), res, args));
343 return ExtractCommandCost(res).Succeeded();
346 /** Helper to process a single ClientID argument. */
347 template <class T>
348 static inline bool ClientIdIsSet(T &data)
350 if constexpr (std::is_same_v<ClientID, T>) {
351 return data != INVALID_CLIENT_ID;
352 } else {
353 return true;
357 /** Check if all ClientID arguments are set to valid values. */
358 template<class Ttuple, size_t... Tindices>
359 static inline bool AllClientIdsSet(Ttuple &values, std::index_sequence<Tindices...>)
361 return (ClientIdIsSet(std::get<Tindices>(values)) && ...);
364 template<class Ttuple>
365 static inline Money ExtractAdditionalMoney(Ttuple &values)
367 if constexpr (std::is_same_v<std::tuple_element_t<1, Tret>, Money>) {
368 return std::get<1>(values);
369 } else {
370 return {};
374 static Tret Execute(StringID err_message, CommandCallback *callback, bool my_cmd, bool estimate_only, bool network_command, TileIndex tile, std::tuple<Targs...> args)
376 /* Prevent recursion; it gives a mess over the network */
377 RecursiveCommandCounter counter{};
378 assert(counter.IsTopLevel());
380 /* Command flags are used internally */
381 constexpr CommandFlags cmd_flags = GetCommandFlags<Tcmd>();
383 if constexpr ((cmd_flags & CMD_CLIENT_ID) != 0) {
384 /* Make sure arguments are properly set to a ClientID also when processing external commands. */
385 assert(AllClientIdsSet(args, std::index_sequence_for<Targs...>{}));
388 Backup<CompanyID> cur_company(_current_company, FILE_LINE);
389 if (!InternalExecutePrepTest(cmd_flags, tile, cur_company)) {
390 cur_company.Trash();
391 return MakeResult(CMD_ERROR);
394 /* Test the command. */
395 DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags);
396 Tret res = std::apply(CommandTraits<Tcmd>::proc, std::tuple_cat(std::make_tuple(flags), args));
398 auto [exit_test, desync_log, send_net] = InternalExecuteValidateTestAndPrepExec(ExtractCommandCost(res), cmd_flags, estimate_only, network_command, cur_company);
399 if (exit_test) {
400 if (desync_log) LogCommandExecution(Tcmd, err_message, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), true);
401 cur_company.Restore();
402 return res;
405 /* If we are in network, and the command is not from the network
406 * send it to the command-queue and abort execution. */
407 if (send_net) {
408 ::NetworkSendCommand(Tcmd, err_message, callback, _current_company, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args));
409 cur_company.Restore();
411 /* Don't return anything special here; no error, no costs.
412 * This way it's not handled by DoCommand and only the
413 * actual execution of the command causes messages. Also
414 * reset the storages as we've not executed the command. */
415 return {};
418 if (desync_log) LogCommandExecution(Tcmd, err_message, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), false);
420 /* Actually try and execute the command. */
421 Tret res2 = std::apply(CommandTraits<Tcmd>::proc, std::tuple_cat(std::make_tuple(flags | DC_EXEC), args));
423 /* Convention: If the second result element is of type Money,
424 * this is the additional cash required for the command. */
425 Money additional_money{};
426 if constexpr (!std::is_same_v<Tret, CommandCost>) { // No short-circuiting for 'if constexpr'.
427 additional_money = ExtractAdditionalMoney(res2);
430 if constexpr (std::is_same_v<Tret, CommandCost>) {
431 return InternalExecuteProcessResult(Tcmd, cmd_flags, res, res2, additional_money, tile, cur_company);
432 } else {
433 std::get<0>(res2) = InternalExecuteProcessResult(Tcmd, cmd_flags, ExtractCommandCost(res), ExtractCommandCost(res2), additional_money, tile, cur_company);
434 return res2;
440 * Overload for #CommandHelper that exposes additional \c Post variants
441 * for commands that don't take a TileIndex themselves.
442 * @tparam Tcmd The command-id to execute.
443 * @tparam Tret Return type of the command.
444 * @tparam Targs The command parameter types.
446 template <Commands Tcmd, typename Tret, typename... Targs>
447 struct CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), false> : CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true>
449 /* Import Post overloads from our base class. */
450 using CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true>::Post;
453 * Shortcut for Post when not using an error message.
454 * @param err_message Message prefix to show on error
455 * @param location Tile location for user feedback.
456 * @param args Parameters for the command
458 static inline bool Post(StringID err_message, TileIndex location, Targs... args) { return Post<CommandCallback>(err_message, nullptr, location, std::forward<Targs>(args)...); }
460 * Shortcut for Post when not using a callback.
461 * @param callback A callback function to call after the command is finished
462 * @param location Tile location for user feedback.
463 * @param args Parameters for the command
465 template <typename Tcallback>
466 static inline bool Post(Tcallback *callback, TileIndex location, Targs... args) { return Post((StringID)0, callback, location, std::forward<Targs>(args)...); }
468 * Shortcut for Post when not using a callback or an error message.
469 * @param location Tile location for user feedback.
470 * @param args Parameters for the command*
472 static inline bool Post(TileIndex location, Targs... args) { return Post<CommandCallback>((StringID)0, nullptr, location, std::forward<Targs>(args)...); }
475 * Post variant that takes a TileIndex (for error window location and text effects) for
476 * commands that don't take a TileIndex by themselves.
477 * @param err_message Message prefix to show on error
478 * @param callback A callback function to call after the command is finished
479 * @param location Tile location for user feedback.
480 * @param args Parameters for the command
481 * @return \c true if the command succeeded, else \c false.
483 template <typename Tcallback>
484 static inline bool Post(StringID err_message, Tcallback *callback, TileIndex location, Targs... args)
486 return CommandHelper<Tcmd, Tret(*)(DoCommandFlag, Targs...), true>::InternalPost(err_message, callback, true, false, location, std::forward_as_tuple(args...));
490 #ifdef SILENCE_GCC_FUNCTION_POINTER_CAST
491 # pragma GCC diagnostic pop
492 #endif
494 template <Commands Tcmd>
495 using Command = CommandHelper<Tcmd, typename CommandTraits<Tcmd>::ProcType, std::is_same_v<TileIndex, std::tuple_element_t<0, typename CommandTraits<Tcmd>::Args>>>;
497 #endif /* COMMAND_FUNC_H */