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
, TileIndex location
, const CommandDataBuffer
&cmd_data
);
42 extern Money _additional_cash_required
;
44 bool IsValidCommand(Commands cmd
);
45 CommandFlags
GetCommandFlags(Commands cmd
);
46 const char *GetCommandName(Commands cmd
);
47 Money
GetAvailableMoneyForCommand();
48 bool IsCommandAllowedWhilePaused(Commands cmd
);
50 template <Commands Tcmd
>
51 constexpr CommandFlags
GetCommandFlags()
53 return CommandTraits
<Tcmd
>::flags
;
57 * Extracts the DC flags needed for DoCommand from the flags returned by GetCommandFlags
58 * @param cmd_flags Flags from GetCommandFlags
59 * @return flags for DoCommand
61 static constexpr inline DoCommandFlag
CommandFlagsToDCFlags(CommandFlags cmd_flags
)
63 DoCommandFlag flags
= DC_NONE
;
64 if (cmd_flags
& CMD_NO_WATER
) flags
|= DC_NO_WATER
;
65 if (cmd_flags
& CMD_AUTO
) flags
|= DC_AUTO
;
66 if (cmd_flags
& CMD_ALL_TILES
) flags
|= DC_ALL_TILES
;
70 /** Helper class to keep track of command nesting level. */
71 struct RecursiveCommandCounter
{
72 RecursiveCommandCounter() noexcept
{ _counter
++; }
73 ~RecursiveCommandCounter() noexcept
{ _counter
--; }
75 /** Are we in the top-level command execution? */
76 bool IsTopLevel() const { return _counter
== 1; }
82 template<Commands TCmd
, typename T
> struct CommandHelper
;
84 class CommandHelperBase
{
86 static void InternalDoBefore(bool top_level
, bool test
);
87 static void InternalDoAfter(CommandCost
&res
, DoCommandFlag flags
, bool top_level
, bool test
);
88 static std::tuple
<bool, bool, bool> InternalPostBefore(Commands cmd
, CommandFlags flags
, TileIndex tile
, StringID err_message
, bool network_command
);
89 static void InternalPostResult(const CommandCost
&res
, TileIndex tile
, bool estimate_only
, bool only_sending
, StringID err_message
, bool my_cmd
);
90 static bool InternalExecutePrepTest(CommandFlags cmd_flags
, TileIndex tile
, Backup
<CompanyID
> &cur_company
);
91 static std::tuple
<bool, bool, bool> InternalExecuteValidateTestAndPrepExec(CommandCost
&res
, CommandFlags cmd_flags
, bool estimate_only
, bool network_command
, Backup
<CompanyID
> &cur_company
);
92 static CommandCost
InternalExecuteProcessResult(Commands cmd
, CommandFlags cmd_flags
, const CommandCost
&res_test
, const CommandCost
&res_exec
, TileIndex tile
, Backup
<CompanyID
> &cur_company
);
93 static void LogCommandExecution(Commands cmd
, StringID err_message
, TileIndex tile
, const CommandDataBuffer
&args
, bool failed
);
97 * Templated wrapper that exposes the command parameter arguments
98 * for the various Command::Do/Post calls.
99 * @tparam Tcmd The command-id to execute.
100 * @tparam Targs The command parameter types.
102 template <Commands Tcmd
, typename
... Targs
>
103 struct CommandHelper
<Tcmd
, CommandCost(*)(DoCommandFlag
, Targs
...)> : protected CommandHelperBase
{
106 * This function executes a given command with the parameters from the #CommandProc parameter list.
107 * Depending on the flags parameter it executes or tests a command.
109 * @note This function is to be called from the StateGameLoop or from within the execution of a Command.
110 * This function must not be called from the context of a "player" (real person, AI, game script).
111 * Use ::Post for commands directly triggered by "players".
113 * @param flags Flags for the command and how to execute the command
114 * @param args Parameters for the command
118 static CommandCost
Do(DoCommandFlag flags
, Targs
... args
)
120 if constexpr (std::is_same_v
<TileIndex
, std::tuple_element_t
<0, std::tuple
<Targs
...>>>) {
121 /* Do not even think about executing out-of-bounds tile-commands. */
122 TileIndex tile
= std::get
<0>(std::make_tuple(args
...));
123 if (tile
!= 0 && (tile
>= MapSize() || (!IsValidTile(tile
) && (flags
& DC_ALL_TILES
) == 0))) return CMD_ERROR
;
126 RecursiveCommandCounter counter
{};
128 /* Only execute the test call if it's toplevel, or we're not execing. */
129 if (counter
.IsTopLevel() || !(flags
& DC_EXEC
)) {
130 InternalDoBefore(counter
.IsTopLevel(), true);
131 CommandCost res
= CommandTraits
<Tcmd
>::proc(flags
& ~DC_EXEC
, args
...);
132 InternalDoAfter(res
, flags
, counter
.IsTopLevel(), true); // Can modify res.
134 if (res
.Failed() || !(flags
& DC_EXEC
)) return res
;
137 /* Execute the command here. All cost-relevant functions set the expenses type
138 * themselves to the cost object at some point. */
139 InternalDoBefore(counter
.IsTopLevel(), false);
140 CommandCost res
= CommandTraits
<Tcmd
>::proc(flags
, args
...);
141 InternalDoAfter(res
, flags
, counter
.IsTopLevel(), false);
147 * Shortcut for the long Post when not using a callback.
148 * @param err_message Message prefix to show on error
149 * @param args Parameters for the command
151 static inline bool Post(StringID err_message
, Targs
... args
) { return Post(err_message
, nullptr, std::forward
<Targs
>(args
)...); }
153 * Shortcut for the long Post when not using an error message.
154 * @param callback A callback function to call after the command is finished
155 * @param args Parameters for the command
157 static inline bool Post(CommandCallback
*callback
, Targs
... args
) { return Post((StringID
)0, callback
, std::forward
<Targs
>(args
)...); }
159 * Shortcut for the long Post when not using a callback or an error message.
160 * @param args Parameters for the command
162 static inline bool Post(Targs
... args
) { return Post((StringID
)0, nullptr, std::forward
<Targs
>(args
)...); }
165 * Top-level network safe command execution for the current company.
166 * Must not be called recursively. The callback is called when the
167 * command succeeded or failed.
169 * @param err_message Message prefix to show on error
170 * @param callback A callback function to call after the command is finished
171 * @param args Parameters for the command
172 * @return \c true if the command succeeded, else \c false.
174 static bool Post(StringID err_message
, CommandCallback
*callback
, Targs
... args
)
176 return InternalPost(err_message
, callback
, true, false, std::forward_as_tuple(args
...));
180 * Execute a command coming from the network.
181 * @param err_message Message prefix to show on error
182 * @param callback A callback function to call after the command is finished
183 * @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
184 * @param location Tile location for user feedback.
185 * @param args Parameters for the command
186 * @return \c true if the command succeeded, else \c false.
188 static bool PostFromNet(StringID err_message
, CommandCallback
*callback
, bool my_cmd
, TileIndex location
, std::tuple
<Targs
...> args
)
190 return InternalPost(err_message
, callback
, my_cmd
, true, location
, std::move(args
));
194 * Prepare a command to be send over the network
195 * @param cmd The command to execute (a CMD_* value)
196 * @param err_message Message prefix to show on error
197 * @param callback A callback function to call after the command is finished
198 * @param company The company that wants to send the command
199 * @param args Parameters for the command
201 static void SendNet(StringID err_message
, CommandCallback
*callback
, CompanyID company
, Targs
... args
)
203 auto args_tuple
= std::forward_as_tuple(args
...);
206 if constexpr (std::is_same_v
<TileIndex
, std::tuple_element_t
<0, decltype(args_tuple
)>>) {
207 tile
= std::get
<0>(args_tuple
);
210 ::NetworkSendCommand(Tcmd
, err_message
, callback
, _current_company
, tile
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args_tuple
));
214 * Top-level network safe command execution without safety checks.
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 estimate_only whether to give only the estimate or also execute the command
219 * @param location Tile location for user feedback.
220 * @param args Parameters for the command
221 * @return the command cost of this function.
223 static CommandCost
Unsafe(StringID err_message
, CommandCallback
*callback
, bool my_cmd
, bool estimate_only
, TileIndex location
, std::tuple
<Targs
...> args
)
225 return Execute(err_message
, callback
, my_cmd
, estimate_only
, false, location
, std::move(args
));
229 /** Helper to process a single ClientID argument. */
231 static inline void SetClientIdHelper(T
&data
)
233 if constexpr (std::is_same_v
<ClientID
, T
>) {
234 if (data
== INVALID_CLIENT_ID
) data
= CLIENT_ID_SERVER
;
238 /** Set all invalid ClientID's to the proper value. */
239 template<class Ttuple
, size_t... Tindices
>
240 static inline void SetClientIds(Ttuple
&values
, std::index_sequence
<Tindices
...>)
242 ((SetClientIdHelper(std::get
<Tindices
>(values
))), ...);
245 static bool InternalPost(StringID err_message
, CommandCallback
*callback
, bool my_cmd
, bool network_command
, std::tuple
<Targs
...> args
)
247 /* Where to show the message? */
249 if constexpr (std::is_same_v
<TileIndex
, std::tuple_element_t
<0, decltype(args
)>>) {
250 tile
= std::get
<0>(args
);
253 return InternalPost(err_message
, callback
, my_cmd
, network_command
, tile
, std::move(args
));
256 static bool InternalPost(StringID err_message
, CommandCallback
*callback
, bool my_cmd
, bool network_command
, TileIndex tile
, std::tuple
<Targs
...> args
)
258 auto [err
, estimate_only
, only_sending
] = InternalPostBefore(Tcmd
, GetCommandFlags
<Tcmd
>(), tile
, err_message
, network_command
);
259 if (err
) return false;
261 /* Only set client IDs when the command does not come from the network. */
262 if (!network_command
&& GetCommandFlags
<Tcmd
>() & CMD_CLIENT_ID
) SetClientIds(args
, std::index_sequence_for
<Targs
...>{});
264 CommandCost res
= Execute(err_message
, callback
, my_cmd
, estimate_only
, network_command
, tile
, args
);
265 InternalPostResult(res
, tile
, estimate_only
, only_sending
, err_message
, my_cmd
);
267 if (!estimate_only
&& !only_sending
&& callback
!= nullptr) {
268 callback(Tcmd
, res
, tile
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
));
271 return res
.Succeeded();
274 /** Helper to process a single ClientID argument. */
276 static inline bool ClientIdIsSet(T
&data
)
278 if constexpr (std::is_same_v
<ClientID
, T
>) {
279 return data
!= INVALID_CLIENT_ID
;
285 /** Check if all ClientID arguments are set to valid values. */
286 template<class Ttuple
, size_t... Tindices
>
287 static inline bool AllClientIdsSet(Ttuple
&values
, std::index_sequence
<Tindices
...>)
289 return (ClientIdIsSet(std::get
<Tindices
>(values
)) && ...);
292 static CommandCost
Execute(StringID err_message
, CommandCallback
*callback
, bool my_cmd
, bool estimate_only
, bool network_command
, TileIndex tile
, std::tuple
<Targs
...> args
)
294 /* Prevent recursion; it gives a mess over the network */
295 RecursiveCommandCounter counter
{};
296 assert(counter
.IsTopLevel());
298 /* Command flags are used internally */
299 constexpr CommandFlags cmd_flags
= GetCommandFlags
<Tcmd
>();
301 if constexpr ((cmd_flags
& CMD_CLIENT_ID
) != 0) {
302 /* Make sure arguments are properly set to a ClientID also when processing external commands. */
303 assert(AllClientIdsSet(args
, std::index_sequence_for
<Targs
...>{}));
306 Backup
<CompanyID
> cur_company(_current_company
, FILE_LINE
);
307 if (!InternalExecutePrepTest(cmd_flags
, tile
, cur_company
)) {
312 /* Test the command. */
313 DoCommandFlag flags
= CommandFlagsToDCFlags(cmd_flags
);
314 CommandCost res
= std::apply(CommandTraits
<Tcmd
>::proc
, std::tuple_cat(std::make_tuple(flags
), args
));
316 auto [exit_test
, desync_log
, send_net
] = InternalExecuteValidateTestAndPrepExec(res
, cmd_flags
, estimate_only
, network_command
, cur_company
);
318 if (desync_log
) LogCommandExecution(Tcmd
, err_message
, tile
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
), true);
319 cur_company
.Restore();
323 /* If we are in network, and the command is not from the network
324 * send it to the command-queue and abort execution. */
326 ::NetworkSendCommand(Tcmd
, err_message
, callback
, _current_company
, tile
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
));
327 cur_company
.Restore();
329 /* Don't return anything special here; no error, no costs.
330 * This way it's not handled by DoCommand and only the
331 * actual execution of the command causes messages. Also
332 * reset the storages as we've not executed the command. */
333 return CommandCost();
336 if (desync_log
) LogCommandExecution(Tcmd
, err_message
, tile
, EndianBufferWriter
<CommandDataBuffer
>::FromValue(args
), false);
338 /* Actually try and execute the command. */
339 CommandCost res2
= std::apply(CommandTraits
<Tcmd
>::proc
, std::tuple_cat(std::make_tuple(flags
| DC_EXEC
), args
));
341 return InternalExecuteProcessResult(Tcmd
, cmd_flags
, res
, res2
, tile
, cur_company
);
345 template <Commands Tcmd
>
346 using Command
= CommandHelper
<Tcmd
, typename CommandTraits
<Tcmd
>::ProcType
>;
348 #endif /* COMMAND_FUNC_H */