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/>.
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"
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
);
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
, const CommandDataBuffer
&cmd_data
);
42 bool IsValidCommand(Commands cmd
);
43 CommandFlags
GetCommandFlags(Commands cmd
);
44 const char *GetCommandName(Commands cmd
);
45 bool IsCommandAllowedWhilePaused(Commands cmd
);
47 template <Commands Tcmd
>
48 constexpr CommandFlags
GetCommandFlags()
50 return CommandTraits
<Tcmd
>::flags
;
54 * Extracts the DC flags needed for DoCommand from the flags returned by GetCommandFlags
55 * @param cmd_flags Flags from GetCommandFlags
56 * @return flags for DoCommand
58 static constexpr inline DoCommandFlag
CommandFlagsToDCFlags(CommandFlags cmd_flags
)
60 DoCommandFlag flags
= DC_NONE
;
61 if (cmd_flags
& CMD_NO_WATER
) flags
|= DC_NO_WATER
;
62 if (cmd_flags
& CMD_AUTO
) flags
|= DC_AUTO
;
63 if (cmd_flags
& CMD_ALL_TILES
) flags
|= DC_ALL_TILES
;
67 /** Helper class to keep track of command nesting level. */
68 struct RecursiveCommandCounter
{
69 RecursiveCommandCounter() noexcept
{ _counter
++; }
70 ~RecursiveCommandCounter() noexcept
{ _counter
--; }
72 /** Are we in the top-level command execution? */
73 bool IsTopLevel() const { return _counter
== 1; }
78 #if defined(__GNUC__) && !defined(__clang__)
80 * We cast specialized function pointers to a generic one, but don't use the
81 * converted value to call the function, which is safe, except that GCC
82 * helpfully thinks it is not.
84 * "Any pointer to function can be converted to a pointer to a different function type.
85 * Calling the function through a pointer to a different function type is undefined,
86 * but converting such pointer back to pointer to the original function type yields
87 * the pointer to the original function." */
88 # pragma GCC diagnostic push
89 # pragma GCC diagnostic ignored "-Wcast-function-type"
90 # define SILENCE_GCC_FUNCTION_POINTER_CAST
93 template<Commands TCmd
, typename T
, bool THasTile
> struct CommandHelper
;
95 class CommandHelperBase
{
97 static void InternalDoBefore(bool top_level
, bool test
);
98 static void InternalDoAfter(CommandCost
&res
, DoCommandFlag flags
, bool top_level
, bool test
);
99 static std::tuple
<bool, bool, bool> InternalPostBefore(Commands cmd
, CommandFlags flags
, TileIndex tile
, StringID err_message
, bool network_command
);
100 static void InternalPostResult(const CommandCost
&res
, TileIndex tile
, bool estimate_only
, bool only_sending
, StringID err_message
, bool my_cmd
);
101 static bool InternalExecutePrepTest(CommandFlags cmd_flags
, TileIndex tile
, Backup
<CompanyID
> &cur_company
);
102 static std::tuple
<bool, bool, bool> InternalExecuteValidateTestAndPrepExec(CommandCost
&res
, CommandFlags cmd_flags
, bool estimate_only
, bool network_command
, Backup
<CompanyID
> &cur_company
);
103 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
);
104 static void LogCommandExecution(Commands cmd
, StringID err_message
, const CommandDataBuffer
&args
, bool failed
);
108 * Templated wrapper that exposes the command parameter arguments
109 * for the various Command::Do/Post calls.
110 * @tparam Tcmd The command-id to execute.
111 * @tparam Tret Return type of the command.
112 * @tparam Targs The command parameter types.
114 template <Commands Tcmd
, typename Tret
, typename
... Targs
>
115 struct CommandHelper
<Tcmd
, Tret(*)(DoCommandFlag
, Targs
...), true> : protected CommandHelperBase
{
117 /** Extract the \c CommandCost from a command proc result. */
118 static inline CommandCost
&ExtractCommandCost(Tret
&ret
)
120 if constexpr (std::is_same_v
<Tret
, CommandCost
>) {
123 return std::get
<0>(ret
);
127 /** Make a command proc result from a \c CommandCost. */
128 static inline Tret
MakeResult(const CommandCost
&cost
)
131 ExtractCommandCost(ret
) = cost
;
137 * This function executes a given command with the parameters from the #CommandProc parameter list.
138 * Depending on the flags parameter it executes or tests a command.
140 * @note This function is to be called from the StateGameLoop or from within the execution of a Command.
141 * This function must not be called from the context of a "player" (real person, AI, game script).
142 * Use ::Post for commands directly triggered by "players".
144 * @param flags Flags for the command and how to execute the command
145 * @param args Parameters for the command
149 static Tret
Do(DoCommandFlag flags
, Targs
... args
)
151 if constexpr (std::is_same_v
<TileIndex
, std::tuple_element_t
<0, std::tuple
<Targs
...>>>) {
152 /* Do not even think about executing out-of-bounds tile-commands. */
153 TileIndex tile
= std::get
<0>(std::make_tuple(args
...));
154 if (tile
!= 0 && (tile
>= Map::Size() || (!IsValidTile(tile
) && (flags
& DC_ALL_TILES
) == 0))) return MakeResult(CMD_ERROR
);
157 RecursiveCommandCounter counter
{};
159 /* Only execute the test call if it's toplevel, or we're not execing. */
160 if (counter
.IsTopLevel() || !(flags
& DC_EXEC
)) {
161 InternalDoBefore(counter
.IsTopLevel(), true);
162 Tret res
= CommandTraits
<Tcmd
>::proc(flags
& ~DC_EXEC
, args
...);
163 InternalDoAfter(ExtractCommandCost(res
), flags
, counter
.IsTopLevel(), true); // Can modify res.
165 if (ExtractCommandCost(res
).Failed() || !(flags
& DC_EXEC
)) return res
;
168 /* Execute the command here. All cost-relevant functions set the expenses type
169 * themselves to the cost object at some point. */
170 InternalDoBefore(counter
.IsTopLevel(), false);
171 Tret res
= CommandTraits
<Tcmd
>::proc(flags
, args
...);
172 InternalDoAfter(ExtractCommandCost(res
), flags
, counter
.IsTopLevel(), false);
178 * Shortcut for the long Post when not using a callback.
179 * @param err_message Message prefix to show on error
180 * @param args Parameters for the command
182 static inline bool Post(StringID err_message
, Targs
... args
) { return Post
<CommandCallback
>(err_message
, nullptr, std::forward
<Targs
>(args
)...); }
184 * Shortcut for the long Post when not using an error message.
185 * @param callback A callback function to call after the command is finished
186 * @param args Parameters for the command
188 template <typename Tcallback
>
189 static inline bool Post(Tcallback
*callback
, Targs
... args
) { return Post((StringID
)0, callback
, std::forward
<Targs
>(args
)...); }
191 * Shortcut for the long Post when not using a callback or an error message.
192 * @param args Parameters for the command
194 static inline bool Post(Targs
... args
) { return Post
<CommandCallback
>((StringID
)0, nullptr, std::forward
<Targs
>(args
)...); }
197 * Top-level network safe command execution for the current company.
198 * Must not be called recursively. The callback is called when the
199 * command succeeded or failed.
201 * @param err_message Message prefix to show on error
202 * @param callback A callback function to call after the command is finished
203 * @param args Parameters for the command
204 * @return \c true if the command succeeded, else \c false.
206 template <typename Tcallback
>
207 static bool Post(StringID err_message
, Tcallback
*callback
, Targs
... args
)
209 return InternalPost(err_message
, callback
, true, false, std::forward_as_tuple(args
...));
213 * Execute a command coming from the network.
214 * @param err_message Message prefix to show on error
215 * @param callback A callback function to call after the command is finished
216 * @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
217 * @param args Parameters for the command
218 * @return \c true if the command succeeded, else \c false.
220 template <typename Tcallback
>
221 static bool PostFromNet(StringID err_message
, Tcallback
*callback
, bool my_cmd
, std::tuple
<Targs
...> args
)
223 return InternalPost(err_message
, callback
, my_cmd
, true, std::move(args
));
227 * Prepare a command to be send over the network
228 * @param cmd The command to execute (a CMD_* value)
229 * @param err_message Message prefix to show on error
230 * @param company The company that wants to send the command
231 * @param args Parameters for the command
233 static void SendNet(StringID err_message
, CompanyID company
, Targs
... args
)
235 auto args_tuple
= std::forward_as_tuple(args
...);
237 ::NetworkSendCommand(Tcmd
, err_message
, nullptr, company
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args_tuple
));
241 * Top-level network safe command execution without safety checks.
242 * @param err_message Message prefix to show on error
243 * @param callback A callback function to call after the command is finished
244 * @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
245 * @param estimate_only whether to give only the estimate or also execute the command
246 * @param location Tile location for user feedback.
247 * @param args Parameters for the command
248 * @return the command cost of this function.
250 template <typename Tcallback
>
251 static Tret
Unsafe(StringID err_message
, Tcallback
*callback
, bool my_cmd
, bool estimate_only
, TileIndex location
, std::tuple
<Targs
...> args
)
253 return Execute(err_message
, reinterpret_cast<CommandCallback
*>(callback
), my_cmd
, estimate_only
, false, location
, std::move(args
));
257 /** Helper to process a single ClientID argument. */
259 static inline void SetClientIdHelper([[maybe_unused
]] T
&data
)
261 if constexpr (std::is_same_v
<ClientID
, T
>) {
262 if (data
== INVALID_CLIENT_ID
) data
= CLIENT_ID_SERVER
;
266 /** Set all invalid ClientID's to the proper value. */
267 template<class Ttuple
, size_t... Tindices
>
268 static inline void SetClientIds(Ttuple
&values
, std::index_sequence
<Tindices
...>)
270 ((SetClientIdHelper(std::get
<Tindices
>(values
))), ...);
273 /** Remove the first element of a tuple. */
274 template <template <typename
...> typename Tt
, typename T1
, typename
... Ts
>
275 static inline Tt
<Ts
...> RemoveFirstTupleElement(const Tt
<T1
, Ts
...> &tuple
)
277 return std::apply([](auto &&, const auto&... args
) { return std::tie(args
...); }, tuple
);
280 template <typename Tcallback
>
281 static bool InternalPost(StringID err_message
, Tcallback
*callback
, bool my_cmd
, bool network_command
, std::tuple
<Targs
...> args
)
283 /* Where to show the message? */
285 if constexpr (std::is_same_v
<TileIndex
, std::tuple_element_t
<0, decltype(args
)>>) {
286 tile
= std::get
<0>(args
);
289 return InternalPost(err_message
, callback
, my_cmd
, network_command
, tile
, std::move(args
));
292 template <typename Tcallback
>
293 static bool InternalPost(StringID err_message
, Tcallback
*callback
, bool my_cmd
, bool network_command
, TileIndex tile
, std::tuple
<Targs
...> args
)
295 /* Do not even think about executing out-of-bounds tile-commands. */
296 if (tile
!= 0 && (tile
>= Map::Size() || (!IsValidTile(tile
) && (GetCommandFlags
<Tcmd
>() & CMD_ALL_TILES
) == 0))) return false;
298 auto [err
, estimate_only
, only_sending
] = InternalPostBefore(Tcmd
, GetCommandFlags
<Tcmd
>(), tile
, err_message
, network_command
);
299 if (err
) return false;
301 /* Only set client IDs when the command does not come from the network. */
302 if (!network_command
&& GetCommandFlags
<Tcmd
>() & CMD_CLIENT_ID
) SetClientIds(args
, std::index_sequence_for
<Targs
...>{});
304 Tret res
= Execute(err_message
, reinterpret_cast<CommandCallback
*>(callback
), my_cmd
, estimate_only
, network_command
, tile
, args
);
305 InternalPostResult(ExtractCommandCost(res
), tile
, estimate_only
, only_sending
, err_message
, my_cmd
);
307 if (!estimate_only
&& !only_sending
&& callback
!= nullptr) {
308 if constexpr (std::is_same_v
<Tcallback
, CommandCallback
>) {
309 /* Callback that doesn't need any command arguments. */
310 callback(Tcmd
, ExtractCommandCost(res
), tile
);
311 } else if constexpr (std::is_same_v
<Tcallback
, CommandCallbackData
>) {
312 /* Generic callback that takes packed arguments as a buffer. */
313 if constexpr (std::is_same_v
<Tret
, CommandCost
>) {
314 callback(Tcmd
, ExtractCommandCost(res
), EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
), {});
316 callback(Tcmd
, ExtractCommandCost(res
), EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
), EndianBufferWriter
<CommandDataBuffer
>::FromValue(RemoveFirstTupleElement(res
)));
318 } else if constexpr (!std::is_same_v
<Tret
, CommandCost
> && std::is_same_v
<Tcallback
*, typename CommandTraits
<Tcmd
>::RetCallbackProc
>) {
319 std::apply(callback
, std::tuple_cat(std::make_tuple(Tcmd
), res
));
321 /* Callback with arguments. We assume that the tile is only interesting if it actually is in the command arguments. */
322 if constexpr (std::is_same_v
<Tret
, CommandCost
>) {
323 std::apply(callback
, std::tuple_cat(std::make_tuple(Tcmd
, res
), args
));
325 std::apply(callback
, std::tuple_cat(std::make_tuple(Tcmd
), res
, args
));
330 return ExtractCommandCost(res
).Succeeded();
333 /** Helper to process a single ClientID argument. */
335 static inline bool ClientIdIsSet([[maybe_unused
]] T
&data
)
337 if constexpr (std::is_same_v
<ClientID
, T
>) {
338 return data
!= INVALID_CLIENT_ID
;
344 /** Check if all ClientID arguments are set to valid values. */
345 template<class Ttuple
, size_t... Tindices
>
346 static inline bool AllClientIdsSet(Ttuple
&values
, std::index_sequence
<Tindices
...>)
348 return (ClientIdIsSet(std::get
<Tindices
>(values
)) && ...);
351 template<class Ttuple
>
352 static inline Money
ExtractAdditionalMoney([[maybe_unused
]] Ttuple
&values
)
354 if constexpr (std::is_same_v
<std::tuple_element_t
<1, Tret
>, Money
>) {
355 return std::get
<1>(values
);
361 static Tret
Execute(StringID err_message
, CommandCallback
*callback
, bool, bool estimate_only
, bool network_command
, TileIndex tile
, std::tuple
<Targs
...> args
)
363 /* Prevent recursion; it gives a mess over the network */
364 RecursiveCommandCounter counter
{};
365 assert(counter
.IsTopLevel());
367 /* Command flags are used internally */
368 constexpr CommandFlags cmd_flags
= GetCommandFlags
<Tcmd
>();
370 if constexpr ((cmd_flags
& CMD_CLIENT_ID
) != 0) {
371 /* Make sure arguments are properly set to a ClientID also when processing external commands. */
372 assert(AllClientIdsSet(args
, std::index_sequence_for
<Targs
...>{}));
375 Backup
<CompanyID
> cur_company(_current_company
, FILE_LINE
);
376 if (!InternalExecutePrepTest(cmd_flags
, tile
, cur_company
)) {
378 return MakeResult(CMD_ERROR
);
381 /* Test the command. */
382 DoCommandFlag flags
= CommandFlagsToDCFlags(cmd_flags
);
383 Tret res
= std::apply(CommandTraits
<Tcmd
>::proc
, std::tuple_cat(std::make_tuple(flags
), args
));
385 auto [exit_test
, desync_log
, send_net
] = InternalExecuteValidateTestAndPrepExec(ExtractCommandCost(res
), cmd_flags
, estimate_only
, network_command
, cur_company
);
387 if (desync_log
) LogCommandExecution(Tcmd
, err_message
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
), true);
388 cur_company
.Restore();
392 /* If we are in network, and the command is not from the network
393 * send it to the command-queue and abort execution. */
395 ::NetworkSendCommand(Tcmd
, err_message
, callback
, _current_company
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
));
396 cur_company
.Restore();
398 /* Don't return anything special here; no error, no costs.
399 * This way it's not handled by DoCommand and only the
400 * actual execution of the command causes messages. Also
401 * reset the storages as we've not executed the command. */
405 if (desync_log
) LogCommandExecution(Tcmd
, err_message
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
), false);
407 /* Actually try and execute the command. */
408 Tret res2
= std::apply(CommandTraits
<Tcmd
>::proc
, std::tuple_cat(std::make_tuple(flags
| DC_EXEC
), args
));
410 /* Convention: If the second result element is of type Money,
411 * this is the additional cash required for the command. */
412 Money additional_money
{};
413 if constexpr (!std::is_same_v
<Tret
, CommandCost
>) { // No short-circuiting for 'if constexpr'.
414 additional_money
= ExtractAdditionalMoney(res2
);
417 if constexpr (std::is_same_v
<Tret
, CommandCost
>) {
418 return InternalExecuteProcessResult(Tcmd
, cmd_flags
, res
, res2
, additional_money
, tile
, cur_company
);
420 std::get
<0>(res2
) = InternalExecuteProcessResult(Tcmd
, cmd_flags
, ExtractCommandCost(res
), ExtractCommandCost(res2
), additional_money
, tile
, cur_company
);
427 * Overload for #CommandHelper that exposes additional \c Post variants
428 * for commands that don't take a TileIndex themselves.
429 * @tparam Tcmd The command-id to execute.
430 * @tparam Tret Return type of the command.
431 * @tparam Targs The command parameter types.
433 template <Commands Tcmd
, typename Tret
, typename
... Targs
>
434 struct CommandHelper
<Tcmd
, Tret(*)(DoCommandFlag
, Targs
...), false> : CommandHelper
<Tcmd
, Tret(*)(DoCommandFlag
, Targs
...), true>
436 /* Do not allow Post without explicit location. */
437 static inline bool Post(StringID err_message
, Targs
... args
) = delete;
438 template <typename Tcallback
>
439 static inline bool Post(Tcallback
*callback
, Targs
... args
) = delete;
440 static inline bool Post(Targs
... args
) = delete;
441 template <typename Tcallback
>
442 static bool Post(StringID err_message
, Tcallback
*callback
, Targs
... args
) = delete;
445 * Shortcut for Post when not using a callback.
446 * @param err_message Message prefix to show on error
447 * @param location Tile location for user feedback.
448 * @param args Parameters for the command
450 static inline bool Post(StringID err_message
, TileIndex location
, Targs
... args
) { return Post
<CommandCallback
>(err_message
, nullptr, location
, std::forward
<Targs
>(args
)...); }
452 * Shortcut for Post when not using an error message.
453 * @param callback A callback function to call after the command is finished
454 * @param location Tile location for user feedback.
455 * @param args Parameters for the command
457 template <typename Tcallback
>
458 static inline bool Post(Tcallback
*callback
, TileIndex location
, Targs
... args
) { return Post((StringID
)0, callback
, location
, std::forward
<Targs
>(args
)...); }
460 * Shortcut for Post when not using a callback or an error message.
461 * @param location Tile location for user feedback.
462 * @param args Parameters for the command*
464 static inline bool Post(TileIndex location
, Targs
... args
) { return Post
<CommandCallback
>((StringID
)0, nullptr, location
, std::forward
<Targs
>(args
)...); }
467 * Post variant that takes a TileIndex (for error window location and text effects) for
468 * commands that don't take a TileIndex by themselves.
469 * @param err_message Message prefix to show on error
470 * @param callback A callback function to call after the command is finished
471 * @param args Parameters for the command
472 * @return \c true if the command succeeded, else \c false.
474 template <typename Tcallback
>
475 static inline bool Post(StringID err_message
, Tcallback
*callback
, TileIndex location
, Targs
... args
)
477 return CommandHelper
<Tcmd
, Tret(*)(DoCommandFlag
, Targs
...), true>::InternalPost(err_message
, callback
, true, false, location
, std::forward_as_tuple(args
...));
481 #ifdef SILENCE_GCC_FUNCTION_POINTER_CAST
482 # pragma GCC diagnostic pop
485 template <Commands Tcmd
>
486 using Command
= CommandHelper
<Tcmd
, typename CommandTraits
<Tcmd
>::ProcType
, (GetCommandFlags
<Tcmd
>() & CMD_LOCATION
) == 0>;
488 #endif /* COMMAND_FUNC_H */