Files
@ r26120:15c912177c88
Branch filter:
Location: cpp/openttd-patchpack/source/src/command_func.h
r26120:15c912177c88
16.7 KiB
text/x-c
Codechange: Un-bitstuff all remaining commands.
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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | /*
* This file is part of OpenTTD.
* 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.
* 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.
* 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/>.
*/
/** @file command_func.h Functions related to commands. */
#ifndef COMMAND_FUNC_H
#define COMMAND_FUNC_H
#include "command_type.h"
#include "network/network_type.h"
#include "company_type.h"
#include "company_func.h"
#include "core/backup_type.hpp"
#include "misc/endian_buffer.hpp"
#include "tile_map.h"
/**
* Define a default return value for a failed command.
*
* This variable contains a CommandCost object with is declared as "failed".
* Other functions just need to return this error if there is an error,
* which doesn't need to specific by a StringID.
*/
static const CommandCost CMD_ERROR = CommandCost(INVALID_STRING_ID);
/**
* Returns from a function with a specific StringID as error.
*
* This macro is used to return from a function. The parameter contains the
* StringID which will be returned.
*
* @param errcode The StringID to return
*/
#define return_cmd_error(errcode) return CommandCost(errcode);
void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, TileIndex location, const CommandDataBuffer &cmd_data);
extern Money _additional_cash_required;
bool IsValidCommand(Commands cmd);
CommandFlags GetCommandFlags(Commands cmd);
const char *GetCommandName(Commands cmd);
Money GetAvailableMoneyForCommand();
bool IsCommandAllowedWhilePaused(Commands cmd);
template <Commands Tcmd>
constexpr CommandFlags GetCommandFlags()
{
return CommandTraits<Tcmd>::flags;
}
/**
* Extracts the DC flags needed for DoCommand from the flags returned by GetCommandFlags
* @param cmd_flags Flags from GetCommandFlags
* @return flags for DoCommand
*/
static constexpr inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags)
{
DoCommandFlag flags = DC_NONE;
if (cmd_flags & CMD_NO_WATER) flags |= DC_NO_WATER;
if (cmd_flags & CMD_AUTO) flags |= DC_AUTO;
if (cmd_flags & CMD_ALL_TILES) flags |= DC_ALL_TILES;
return flags;
}
/** Helper class to keep track of command nesting level. */
struct RecursiveCommandCounter {
RecursiveCommandCounter() noexcept { _counter++; }
~RecursiveCommandCounter() noexcept { _counter--; }
/** Are we in the top-level command execution? */
bool IsTopLevel() const { return _counter == 1; }
private:
static int _counter;
};
template<Commands TCmd, typename T, bool THasTile> struct CommandHelper;
class CommandHelperBase {
protected:
static void InternalDoBefore(bool top_level, bool test);
static void InternalDoAfter(CommandCost &res, DoCommandFlag flags, bool top_level, bool test);
static std::tuple<bool, bool, bool> InternalPostBefore(Commands cmd, CommandFlags flags, TileIndex tile, StringID err_message, bool network_command);
static void InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd);
static bool InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup<CompanyID> &cur_company);
static std::tuple<bool, bool, bool> InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup<CompanyID> &cur_company);
static CommandCost InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, TileIndex tile, Backup<CompanyID> &cur_company);
static void LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed);
};
/**
* Templated wrapper that exposes the command parameter arguments
* for the various Command::Do/Post calls.
* @tparam Tcmd The command-id to execute.
* @tparam Targs The command parameter types.
*/
template <Commands Tcmd, typename... Targs>
struct CommandHelper<Tcmd, CommandCost(*)(DoCommandFlag, Targs...), true> : protected CommandHelperBase {
public:
/**
* This function executes a given command with the parameters from the #CommandProc parameter list.
* Depending on the flags parameter it executes or tests a command.
*
* @note This function is to be called from the StateGameLoop or from within the execution of a Command.
* This function must not be called from the context of a "player" (real person, AI, game script).
* Use ::Post for commands directly triggered by "players".
*
* @param flags Flags for the command and how to execute the command
* @param args Parameters for the command
* @see CommandProc
* @return the cost
*/
static CommandCost Do(DoCommandFlag flags, Targs... args)
{
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, std::tuple<Targs...>>>) {
/* Do not even think about executing out-of-bounds tile-commands. */
TileIndex tile = std::get<0>(std::make_tuple(args...));
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return CMD_ERROR;
}
RecursiveCommandCounter counter{};
/* Only execute the test call if it's toplevel, or we're not execing. */
if (counter.IsTopLevel() || !(flags & DC_EXEC)) {
InternalDoBefore(counter.IsTopLevel(), true);
CommandCost res = CommandTraits<Tcmd>::proc(flags & ~DC_EXEC, args...);
InternalDoAfter(res, flags, counter.IsTopLevel(), true); // Can modify res.
if (res.Failed() || !(flags & DC_EXEC)) return res;
}
/* Execute the command here. All cost-relevant functions set the expenses type
* themselves to the cost object at some point. */
InternalDoBefore(counter.IsTopLevel(), false);
CommandCost res = CommandTraits<Tcmd>::proc(flags, args...);
InternalDoAfter(res, flags, counter.IsTopLevel(), false);
return res;
}
/**
* Shortcut for the long Post when not using a callback.
* @param err_message Message prefix to show on error
* @param args Parameters for the command
*/
static inline bool Post(StringID err_message, Targs... args) { return Post(err_message, nullptr, std::forward<Targs>(args)...); }
/**
* Shortcut for the long Post when not using an error message.
* @param callback A callback function to call after the command is finished
* @param args Parameters for the command
*/
static inline bool Post(CommandCallback *callback, Targs... args) { return Post((StringID)0, callback, std::forward<Targs>(args)...); }
/**
* Shortcut for the long Post when not using a callback or an error message.
* @param args Parameters for the command
*/
static inline bool Post(Targs... args) { return Post((StringID)0, nullptr, std::forward<Targs>(args)...); }
/**
* Top-level network safe command execution for the current company.
* Must not be called recursively. The callback is called when the
* command succeeded or failed.
*
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param args Parameters for the command
* @return \c true if the command succeeded, else \c false.
*/
static bool Post(StringID err_message, CommandCallback *callback, Targs... args)
{
return InternalPost(err_message, callback, true, false, std::forward_as_tuple(args...));
}
/**
* Execute a command coming from the network.
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @param location Tile location for user feedback.
* @param args Parameters for the command
* @return \c true if the command succeeded, else \c false.
*/
static bool PostFromNet(StringID err_message, CommandCallback *callback, bool my_cmd, TileIndex location, std::tuple<Targs...> args)
{
return InternalPost(err_message, callback, my_cmd, true, location, std::move(args));
}
/**
* Prepare a command to be send over the network
* @param cmd The command to execute (a CMD_* value)
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param company The company that wants to send the command
* @param args Parameters for the command
*/
static void SendNet(StringID err_message, CommandCallback *callback, CompanyID company, Targs... args)
{
auto args_tuple = std::forward_as_tuple(args...);
TileIndex tile{};
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args_tuple)>>) {
tile = std::get<0>(args_tuple);
}
::NetworkSendCommand(Tcmd, err_message, callback, _current_company, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args_tuple));
}
/**
* Top-level network safe command execution without safety checks.
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
* @param estimate_only whether to give only the estimate or also execute the command
* @param location Tile location for user feedback.
* @param args Parameters for the command
* @return the command cost of this function.
*/
static CommandCost Unsafe(StringID err_message, CommandCallback *callback, bool my_cmd, bool estimate_only, TileIndex location, std::tuple<Targs...> args)
{
return Execute(err_message, callback, my_cmd, estimate_only, false, location, std::move(args));
}
protected:
/** Helper to process a single ClientID argument. */
template <class T>
static inline void SetClientIdHelper(T &data)
{
if constexpr (std::is_same_v<ClientID, T>) {
if (data == INVALID_CLIENT_ID) data = CLIENT_ID_SERVER;
}
}
/** Set all invalid ClientID's to the proper value. */
template<class Ttuple, size_t... Tindices>
static inline void SetClientIds(Ttuple &values, std::index_sequence<Tindices...>)
{
((SetClientIdHelper(std::get<Tindices>(values))), ...);
}
static bool InternalPost(StringID err_message, CommandCallback *callback, bool my_cmd, bool network_command, std::tuple<Targs...> args)
{
/* Where to show the message? */
TileIndex tile{};
if constexpr (std::is_same_v<TileIndex, std::tuple_element_t<0, decltype(args)>>) {
tile = std::get<0>(args);
}
return InternalPost(err_message, callback, my_cmd, network_command, tile, std::move(args));
}
static bool InternalPost(StringID err_message, CommandCallback *callback, bool my_cmd, bool network_command, TileIndex tile, std::tuple<Targs...> args)
{
auto [err, estimate_only, only_sending] = InternalPostBefore(Tcmd, GetCommandFlags<Tcmd>(), tile, err_message, network_command);
if (err) return false;
/* Only set client IDs when the command does not come from the network. */
if (!network_command && GetCommandFlags<Tcmd>() & CMD_CLIENT_ID) SetClientIds(args, std::index_sequence_for<Targs...>{});
CommandCost res = Execute(err_message, callback, my_cmd, estimate_only, network_command, tile, args);
InternalPostResult(res, tile, estimate_only, only_sending, err_message, my_cmd);
if (!estimate_only && !only_sending && callback != nullptr) {
callback(Tcmd, res, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args));
}
return res.Succeeded();
}
/** Helper to process a single ClientID argument. */
template <class T>
static inline bool ClientIdIsSet(T &data)
{
if constexpr (std::is_same_v<ClientID, T>) {
return data != INVALID_CLIENT_ID;
} else {
return true;
}
}
/** Check if all ClientID arguments are set to valid values. */
template<class Ttuple, size_t... Tindices>
static inline bool AllClientIdsSet(Ttuple &values, std::index_sequence<Tindices...>)
{
return (ClientIdIsSet(std::get<Tindices>(values)) && ...);
}
static CommandCost Execute(StringID err_message, CommandCallback *callback, bool my_cmd, bool estimate_only, bool network_command, TileIndex tile, std::tuple<Targs...> args)
{
/* Prevent recursion; it gives a mess over the network */
RecursiveCommandCounter counter{};
assert(counter.IsTopLevel());
/* Command flags are used internally */
constexpr CommandFlags cmd_flags = GetCommandFlags<Tcmd>();
if constexpr ((cmd_flags & CMD_CLIENT_ID) != 0) {
/* Make sure arguments are properly set to a ClientID also when processing external commands. */
assert(AllClientIdsSet(args, std::index_sequence_for<Targs...>{}));
}
Backup<CompanyID> cur_company(_current_company, FILE_LINE);
if (!InternalExecutePrepTest(cmd_flags, tile, cur_company)) {
cur_company.Trash();
return CMD_ERROR;
}
/* Test the command. */
DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags);
CommandCost res = std::apply(CommandTraits<Tcmd>::proc, std::tuple_cat(std::make_tuple(flags), args));
auto [exit_test, desync_log, send_net] = InternalExecuteValidateTestAndPrepExec(res, cmd_flags, estimate_only, network_command, cur_company);
if (exit_test) {
if (desync_log) LogCommandExecution(Tcmd, err_message, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), true);
cur_company.Restore();
return res;
}
/* If we are in network, and the command is not from the network
* send it to the command-queue and abort execution. */
if (send_net) {
::NetworkSendCommand(Tcmd, err_message, callback, _current_company, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args));
cur_company.Restore();
/* Don't return anything special here; no error, no costs.
* This way it's not handled by DoCommand and only the
* actual execution of the command causes messages. Also
* reset the storages as we've not executed the command. */
return CommandCost();
}
if (desync_log) LogCommandExecution(Tcmd, err_message, tile, EndianBufferWriter<CommandDataBuffer>::FromValue(args), false);
/* Actually try and execute the command. */
CommandCost res2 = std::apply(CommandTraits<Tcmd>::proc, std::tuple_cat(std::make_tuple(flags | DC_EXEC), args));
return InternalExecuteProcessResult(Tcmd, cmd_flags, res, res2, tile, cur_company);
}
};
/**
* Overload for #CommandHelper that exposes additional \c Post variants
* for commands that don't take a TileIndex themselves.
* @tparam Tcmd The command-id to execute.
* @tparam Targs The command parameter types.
*/
template <Commands Tcmd, typename... Targs>
struct CommandHelper<Tcmd, CommandCost(*)(DoCommandFlag, Targs...), false> : CommandHelper<Tcmd, CommandCost(*)(DoCommandFlag, Targs...), true>
{
/* Import Post overloads from our base class. */
using CommandHelper<Tcmd, CommandCost(*)(DoCommandFlag, Targs...), true>::Post;
/**
* Shortcut for Post when not using an error message.
* @param err_message Message prefix to show on error
* @param location Tile location for user feedback.
* @param args Parameters for the command
*/
static inline bool Post(StringID err_message, TileIndex location, Targs... args) { return Post(err_message, nullptr, location, std::forward<Targs>(args)...); }
/**
* Shortcut for Post when not using a callback.
* @param callback A callback function to call after the command is finished
* @param location Tile location for user feedback.
* @param args Parameters for the command
*/
static inline bool Post(CommandCallback *callback, TileIndex location, Targs... args) { return Post((StringID)0, callback, location, std::forward<Targs>(args)...); }
/**
* Shortcut for Post when not using a callback or an error message.
* @param location Tile location for user feedback.
* @param args Parameters for the command*
*/
static inline bool Post(TileIndex location, Targs... args) { return Post((StringID)0, nullptr, location, std::forward<Targs>(args)...); }
/**
* Post variant that takes a TileIndex (for error window location and text effects) for
* commands that don't take a TileIndex by themselves.
* @param err_message Message prefix to show on error
* @param callback A callback function to call after the command is finished
* @param location Tile location for user feedback.
* @param args Parameters for the command
* @return \c true if the command succeeded, else \c false.
*/
static inline bool Post(StringID err_message, CommandCallback *callback, TileIndex location, Targs... args)
{
return CommandHelper<Tcmd, CommandCost(*)(DoCommandFlag, Targs...), true>::InternalPost(err_message, callback, true, false, location, std::forward_as_tuple(args...));
}
};
template <Commands Tcmd>
using Command = CommandHelper<Tcmd, typename CommandTraits<Tcmd>::ProcType, std::is_same_v<TileIndex, std::tuple_element_t<0, typename CommandTraits<Tcmd>::Args>>>;
#endif /* COMMAND_FUNC_H */
|