/* $Id$ */
/*
* 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 .
*/
/** @file console_cmds.cpp Implementation of the console hooks. */
#include "stdafx.h"
#include "console_internal.h"
#include "debug.h"
#include "engine_func.h"
#include "landscape.h"
#include "saveload/saveload.h"
#include "network/network.h"
#include "network/network_func.h"
#include "network/network_base.h"
#include "network/network_admin.h"
#include "network/network_client.h"
#include "command_func.h"
#include "settings_func.h"
#include "fios.h"
#include "fileio_func.h"
#include "screenshot.h"
#include "genworld.h"
#include "strings_func.h"
#include "viewport_func.h"
#include "window_func.h"
#include "date_func.h"
#include "company_func.h"
#include "gamelog.h"
#include "ai/ai.hpp"
#include "ai/ai_config.hpp"
#include "newgrf.h"
#include "console_func.h"
#include "engine_base.h"
#include "game/game.hpp"
#include "table/strings.h"
#include "safeguards.h"
/* scriptfile handling */
static bool _script_running; ///< Script is running (used to abort execution when #ConReturn is encountered).
/** File list storage for the console, for caching the last 'ls' command. */
class ConsoleFileList : public FileList {
public:
ConsoleFileList() : FileList()
{
this->file_list_valid = false;
}
/** Declare the file storage cache as being invalid, also clears all stored files. */
void InvalidateFileList()
{
this->Clear();
this->file_list_valid = false;
}
/**
* (Re-)validate the file storage cache. Only makes a change if the storage was invalid, or if \a force_reload.
* @param force_reload Always reload the file storage cache.
*/
void ValidateFileList(bool force_reload = false)
{
if (force_reload || !this->file_list_valid) {
this->BuildFileList(FT_SAVEGAME, SLO_LOAD);
this->file_list_valid = true;
}
}
bool file_list_valid; ///< If set, the file list is valid.
};
static ConsoleFileList _console_file_list; ///< File storage cache for the console.
/* console command defines */
#define DEF_CONSOLE_CMD(function) static bool function(byte argc, char *argv[])
#define DEF_CONSOLE_HOOK(function) static ConsoleHookResult function(bool echo)
/****************
* command hooks
****************/
/**
* Check network availability and inform in console about failure of detection.
* @return Network availability.
*/
static inline bool NetworkAvailable(bool echo)
{
if (!_network_available) {
if (echo) IConsoleError("You cannot use this command because there is no network available.");
return false;
}
return true;
}
/**
* Check whether we are a server.
* @return Are we a server? True when yes, false otherwise.
*/
DEF_CONSOLE_HOOK(ConHookServerOnly)
{
if (!NetworkAvailable(echo)) return CHR_DISALLOW;
if (!_network_server) {
if (echo) IConsoleError("This command is only available to a network server.");
return CHR_DISALLOW;
}
return CHR_ALLOW;
}
/**
* Check whether we are a client in a network game.
* @return Are we a client in a network game? True when yes, false otherwise.
*/
DEF_CONSOLE_HOOK(ConHookClientOnly)
{
if (!NetworkAvailable(echo)) return CHR_DISALLOW;
if (_network_server) {
if (echo) IConsoleError("This command is not available to a network server.");
return CHR_DISALLOW;
}
return CHR_ALLOW;
}
/**
* Check whether we are in a multiplayer game.
* @return True when we are client or server in a network game.
*/
DEF_CONSOLE_HOOK(ConHookNeedNetwork)
{
if (!NetworkAvailable(echo)) return CHR_DISALLOW;
if (!_networking || (!_network_server && !MyClient::IsConnected())) {
if (echo) IConsoleError("Not connected. This command is only available in multiplayer.");
return CHR_DISALLOW;
}
return CHR_ALLOW;
}
/**
* Check whether we are in single player mode.
* @return True when no network is active.
*/
DEF_CONSOLE_HOOK(ConHookNoNetwork)
{
if (_networking) {
if (echo) IConsoleError("This command is forbidden in multiplayer.");
return CHR_DISALLOW;
}
return CHR_ALLOW;
}
DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool)
{
if (_settings_client.gui.newgrf_developer_tools) {
if (_game_mode == GM_MENU) {
if (echo) IConsoleError("This command is only available in game and editor.");
return CHR_DISALLOW;
}
return ConHookNoNetwork(echo);
}
return CHR_HIDE;
}
/**
* Show help for the console.
* @param str String to print in the console.
*/
static void IConsoleHelp(const char *str)
{
IConsolePrintF(CC_WARNING, "- %s", str);
}
/**
* Reset status of all engines.
* @return Will always succeed.
*/
DEF_CONSOLE_CMD(ConResetEngines)
{
if (argc == 0) {
IConsoleHelp("Reset status data of all engines. This might solve some issues with 'lost' engines. Usage: 'resetengines'");
return true;
}
StartupEngines();
return true;
}
/**
* Reset status of the engine pool.
* @return Will always return true.
* @note Resetting the pool only succeeds when there are no vehicles ingame.
*/
DEF_CONSOLE_CMD(ConResetEnginePool)
{
if (argc == 0) {
IConsoleHelp("Reset NewGRF allocations of engine slots. This will remove invalid engine definitions, and might make default engines available again.");
return true;
}
if (_game_mode == GM_MENU) {
IConsoleError("This command is only available in game and editor.");
return true;
}
if (!EngineOverrideManager::ResetToCurrentNewGRFConfig()) {
IConsoleError("This can only be done when there are no vehicles in the game.");
return true;
}
return true;
}
#ifdef _DEBUG
/**
* Reset a tile to bare land in debug mode.
* param tile number.
* @return True when the tile is reset or the help on usage was printed (0 or two parameters).
*/
DEF_CONSOLE_CMD(ConResetTile)
{
if (argc == 0) {
IConsoleHelp("Reset a tile to bare land. Usage: 'resettile '");
IConsoleHelp("Tile can be either decimal (34161) or hexadecimal (0x4a5B)");
return true;
}
if (argc == 2) {
uint32 result;
if (GetArgumentInteger(&result, argv[1])) {
DoClearSquare((TileIndex)result);
return true;
}
}
return false;
}
#endif /* _DEBUG */
/**
* Scroll to a tile on the map.
* param x tile number or tile x coordinate.
* param y optional y coordinate.
* @note When only one argument is given it is intepreted as the tile number.
* When two arguments are given, they are interpreted as the tile's x
* and y coordinates.
* @return True when either console help was shown or a proper amount of parameters given.
*/
DEF_CONSOLE_CMD(ConScrollToTile)
{
switch (argc) {
case 0:
IConsoleHelp("Center the screen on a given tile.");
IConsoleHelp("Usage: 'scrollto ' or 'scrollto '");
IConsoleHelp("Numbers can be either decimal (34161) or hexadecimal (0x4a5B).");
return true;
case 2: {
uint32 result;
if (GetArgumentInteger(&result, argv[1])) {
if (result >= MapSize()) {
IConsolePrint(CC_ERROR, "Tile does not exist");
return true;
}
ScrollMainWindowToTile((TileIndex)result);
return true;
}
break;
}
case 3: {
uint32 x, y;
if (GetArgumentInteger(&x, argv[1]) && GetArgumentInteger(&y, argv[2])) {
if (x >= MapSizeX() || y >= MapSizeY()) {
IConsolePrint(CC_ERROR, "Tile does not exist");
return true;
}
ScrollMainWindowToTile(TileXY(x, y));
return true;
}
break;
}
}
return false;
}
/**
* Save the map to a file.
* param filename the filename to save the map to.
* @return True when help was displayed or the file attempted to be saved.
*/
DEF_CONSOLE_CMD(ConSave)
{
if (argc == 0) {
IConsoleHelp("Save the current game. Usage: 'save '");
return true;
}
if (argc == 2) {
char *filename = str_fmt("%s.sav", argv[1]);
IConsolePrint(CC_DEFAULT, "Saving map...");
if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, SAVE_DIR) != SL_OK) {
IConsolePrint(CC_ERROR, "Saving map failed");
} else {
IConsolePrintF(CC_DEFAULT, "Map successfully saved to %s", filename);
}
free(filename);
return true;
}
return false;
}
/**
* Explicitly save the configuration.
* @return True.
*/
DEF_CONSOLE_CMD(ConSaveConfig)
{
if (argc == 0) {
IConsoleHelp("Saves the configuration for new games to the configuration file, typically 'openttd.cfg'.");
IConsoleHelp("It does not save the configuration of the current game to the configuration file.");
return true;
}
SaveToConfig();
IConsolePrint(CC_DEFAULT, "Saved config.");
return true;
}
DEF_CONSOLE_CMD(ConLoad)
{
if (argc == 0) {
IConsoleHelp("Load a game by name or index. Usage: 'load '");
return true;
}
if (argc != 2) return false;
const char *file = argv[1];
_console_file_list.ValidateFileList();
const FiosItem *item = _console_file_list.FindItem(file);
if (item != nullptr) {
if (GetAbstractFileType(item->type) == FT_SAVEGAME) {
_switch_mode = SM_LOAD_GAME;
_file_to_saveload.SetMode(item->type);
_file_to_saveload.SetName(FiosBrowseTo(item));
_file_to_saveload.SetTitle(item->title);
} else {
IConsolePrintF(CC_ERROR, "%s: Not a savegame.", file);
}
} else {
IConsolePrintF(CC_ERROR, "%s: No such file or directory.", file);
}
return true;
}
DEF_CONSOLE_CMD(ConRemove)
{
if (argc == 0) {
IConsoleHelp("Remove a savegame by name or index. Usage: 'rm '");
return true;
}
if (argc != 2) return false;
const char *file = argv[1];
_console_file_list.ValidateFileList();
const FiosItem *item = _console_file_list.FindItem(file);
if (item != nullptr) {
if (!FiosDelete(item->name)) {
IConsolePrintF(CC_ERROR, "%s: Failed to delete file", file);
}
} else {
IConsolePrintF(CC_ERROR, "%s: No such file or directory.", file);
}
_console_file_list.InvalidateFileList();
return true;
}
/* List all the files in the current dir via console */
DEF_CONSOLE_CMD(ConListFiles)
{
if (argc == 0) {
IConsoleHelp("List all loadable savegames and directories in the current dir via console. Usage: 'ls | dir'");
return true;
}
_console_file_list.ValidateFileList(true);
for (uint i = 0; i < _console_file_list.Length(); i++) {
IConsolePrintF(CC_DEFAULT, "%d) %s", i, _console_file_list[i].title);
}
return true;
}
/* Change the dir via console */
DEF_CONSOLE_CMD(ConChangeDirectory)
{
if (argc == 0) {
IConsoleHelp("Change the dir via console. Usage: 'cd '");
return true;
}
if (argc != 2) return false;
const char *file = argv[1];
_console_file_list.ValidateFileList(true);
const FiosItem *item = _console_file_list.FindItem(file);
if (item != nullptr) {
switch (item->type) {
case FIOS_TYPE_DIR: case FIOS_TYPE_DRIVE: case FIOS_TYPE_PARENT:
FiosBrowseTo(item);
break;
default: IConsolePrintF(CC_ERROR, "%s: Not a directory.", file);
}
} else {
IConsolePrintF(CC_ERROR, "%s: No such file or directory.", file);
}
_console_file_list.InvalidateFileList();
return true;
}
DEF_CONSOLE_CMD(ConPrintWorkingDirectory)
{
const char *path;
if (argc == 0) {
IConsoleHelp("Print out the current working directory. Usage: 'pwd'");
return true;
}
/* XXX - Workaround for broken file handling */
_console_file_list.ValidateFileList(true);
_console_file_list.InvalidateFileList();
FiosGetDescText(&path, nullptr);
IConsolePrint(CC_DEFAULT, path);
return true;
}
DEF_CONSOLE_CMD(ConClearBuffer)
{
if (argc == 0) {
IConsoleHelp("Clear the console buffer. Usage: 'clear'");
return true;
}
IConsoleClearBuffer();
SetWindowDirty(WC_CONSOLE, 0);
return true;
}
/**********************************
* Network Core Console Commands
**********************************/
static bool ConKickOrBan(const char *argv, bool ban)
{
uint n;
if (strchr(argv, '.') == nullptr && strchr(argv, ':') == nullptr) { // banning with ID
ClientID client_id = (ClientID)atoi(argv);
/* Don't kill the server, or the client doing the rcon. The latter can't be kicked because
* kicking frees closes and subsequently free the connection related instances, which we
* would be reading from and writing to after returning. So we would read or write data
* from freed memory up till the segfault triggers. */
if (client_id == CLIENT_ID_SERVER || client_id == _redirect_console_to_client) {
IConsolePrintF(CC_ERROR, "ERROR: Silly boy, you can not %s yourself!", ban ? "ban" : "kick");
return true;
}
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id);
if (ci == nullptr) {
IConsoleError("Invalid client");
return true;
}
if (!ban) {
/* Kick only this client, not all clients with that IP */
NetworkServerKickClient(client_id);
return true;
}
/* When banning, kick+ban all clients with that IP */
n = NetworkServerKickOrBanIP(client_id, ban);
} else {
n = NetworkServerKickOrBanIP(argv, ban);
}
if (n == 0) {
IConsolePrint(CC_DEFAULT, ban ? "Client not online, address added to banlist" : "Client not found");
} else {
IConsolePrintF(CC_DEFAULT, "%sed %u client(s)", ban ? "Bann" : "Kick", n);
}
return true;
}
DEF_CONSOLE_CMD(ConKick)
{
if (argc == 0) {
IConsoleHelp("Kick a client from a network game. Usage: 'kick '");
IConsoleHelp("For client-id's, see the command 'clients'");
return true;
}
if (argc != 2) return false;
return ConKickOrBan(argv[1], false);
}
DEF_CONSOLE_CMD(ConBan)
{
if (argc == 0) {
IConsoleHelp("Ban a client from a network game. Usage: 'ban '");
IConsoleHelp("For client-id's, see the command 'clients'");
IConsoleHelp("If the client is no longer online, you can still ban his/her IP");
return true;
}
if (argc != 2) return false;
return ConKickOrBan(argv[1], true);
}
DEF_CONSOLE_CMD(ConUnBan)
{
if (argc == 0) {
IConsoleHelp("Unban a client from a network game. Usage: 'unban '");
IConsoleHelp("For a list of banned IP's, see the command 'banlist'");
return true;
}
if (argc != 2) return false;
/* Try by IP. */
uint index;
for (index = 0; index < _network_ban_list.size(); index++) {
if (_network_ban_list[index] == argv[1]) break;
}
/* Try by index. */
if (index >= _network_ban_list.size()) {
index = atoi(argv[1]) - 1U; // let it wrap
}
if (index < _network_ban_list.size()) {
char msg[64];
seprintf(msg, lastof(msg), "Unbanned %s", _network_ban_list[index].c_str());
IConsolePrint(CC_DEFAULT, msg);
_network_ban_list.erase(_network_ban_list.begin() + index);
} else {
IConsolePrint(CC_DEFAULT, "Invalid list index or IP not in ban-list.");
IConsolePrint(CC_DEFAULT, "For a list of banned IP's, see the command 'banlist'");
}
return true;
}
DEF_CONSOLE_CMD(ConBanList)
{
if (argc == 0) {
IConsoleHelp("List the IP's of banned clients: Usage 'banlist'");
return true;
}
IConsolePrint(CC_DEFAULT, "Banlist: ");
uint i = 1;
for (const auto &entry : _network_ban_list) {
IConsolePrintF(CC_DEFAULT, " %d) %s", i, entry.c_str());
}
return true;
}
DEF_CONSOLE_CMD(ConPauseGame)
{
if (argc == 0) {
IConsoleHelp("Pause a network game. Usage: 'pause'");
return true;
}
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
if (!_networking) IConsolePrint(CC_DEFAULT, "Game paused.");
} else {
IConsolePrint(CC_DEFAULT, "Game is already paused.");
}
return true;
}
DEF_CONSOLE_CMD(ConUnpauseGame)
{
if (argc == 0) {
IConsoleHelp("Unpause a network game. Usage: 'unpause'");
return true;
}
if ((_pause_mode & PM_PAUSED_NORMAL) != PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
if (!_networking) IConsolePrint(CC_DEFAULT, "Game unpaused.");
} else if ((_pause_mode & PM_PAUSED_ERROR) != PM_UNPAUSED) {
IConsolePrint(CC_DEFAULT, "Game is in error state and cannot be unpaused via console.");
} else if (_pause_mode != PM_UNPAUSED) {
IConsolePrint(CC_DEFAULT, "Game cannot be unpaused manually; disable pause_on_join/min_active_clients.");
} else {
IConsolePrint(CC_DEFAULT, "Game is already unpaused.");
}
return true;
}
DEF_CONSOLE_CMD(ConRcon)
{
if (argc == 0) {
IConsoleHelp("Remote control the server from another client. Usage: 'rcon '");
IConsoleHelp("Remember to enclose the command in quotes, otherwise only the first parameter is sent");
return true;
}
if (argc < 3) return false;
if (_network_server) {
IConsoleCmdExec(argv[2]);
} else {
NetworkClientSendRcon(argv[1], argv[2]);
}
return true;
}
DEF_CONSOLE_CMD(ConStatus)
{
if (argc == 0) {
IConsoleHelp("List the status of all clients connected to the server. Usage 'status'");
return true;
}
NetworkServerShowStatusToConsole();
return true;
}
DEF_CONSOLE_CMD(ConServerInfo)
{
if (argc == 0) {
IConsoleHelp("List current and maximum client/company limits. Usage 'server_info'");
IConsoleHelp("You can change these values by modifying settings 'network.max_clients', 'network.max_companies' and 'network.max_spectators'");
return true;
}
IConsolePrintF(CC_DEFAULT, "Current/maximum clients: %2d/%2d", _network_game_info.clients_on, _settings_client.network.max_clients);
IConsolePrintF(CC_DEFAULT, "Current/maximum companies: %2d/%2d", (int)Company::GetNumItems(), _settings_client.network.max_companies);
IConsolePrintF(CC_DEFAULT, "Current/maximum spectators: %2d/%2d", NetworkSpectatorCount(), _settings_client.network.max_spectators);
return true;
}
DEF_CONSOLE_CMD(ConClientNickChange)
{
if (argc != 3) {
IConsoleHelp("Change the nickname of a connected client. Usage: 'client_name '");
IConsoleHelp("For client-id's, see the command 'clients'");
return true;
}
ClientID client_id = (ClientID)atoi(argv[1]);
if (client_id == CLIENT_ID_SERVER) {
IConsoleError("Please use the command 'name' to change your own name!");
return true;
}
if (NetworkClientInfo::GetByClientID(client_id) == nullptr) {
IConsoleError("Invalid client");
return true;
}
if (!NetworkServerChangeClientName(client_id, argv[2])) {
IConsoleError("Cannot give a client a duplicate name");
}
return true;
}
DEF_CONSOLE_CMD(ConJoinCompany)
{
if (argc < 2) {
IConsoleHelp("Request joining another company. Usage: join []");
IConsoleHelp("For valid company-id see company list, use 255 for spectator");
return true;
}
CompanyID company_id = (CompanyID)(atoi(argv[1]) <= MAX_COMPANIES ? atoi(argv[1]) - 1 : atoi(argv[1]));
/* Check we have a valid company id! */
if (!Company::IsValidID(company_id) && company_id != COMPANY_SPECTATOR) {
IConsolePrintF(CC_ERROR, "Company does not exist. Company-id must be between 1 and %d.", MAX_COMPANIES);
return true;
}
if (NetworkClientInfo::GetByClientID(_network_own_client_id)->client_playas == company_id) {
IConsoleError("You are already there!");
return true;
}
if (company_id == COMPANY_SPECTATOR && NetworkMaxSpectatorsReached()) {
IConsoleError("Cannot join spectators, maximum number of spectators reached.");
return true;
}
if (company_id != COMPANY_SPECTATOR && !Company::IsHumanID(company_id)) {
IConsoleError("Cannot join AI company.");
return true;
}
/* Check if the company requires a password */
if (NetworkCompanyIsPassworded(company_id) && argc < 3) {
IConsolePrintF(CC_ERROR, "Company %d requires a password to join.", company_id + 1);
return true;
}
/* non-dedicated server may just do the move! */
if (_network_server) {
NetworkServerDoMove(CLIENT_ID_SERVER, company_id);
} else {
NetworkClientRequestMove(company_id, NetworkCompanyIsPassworded(company_id) ? argv[2] : "");
}
return true;
}
DEF_CONSOLE_CMD(ConMoveClient)
{
if (argc < 3) {
IConsoleHelp("Move a client to another company. Usage: move ");
IConsoleHelp("For valid client-id see 'clients', for valid company-id see 'companies', use 255 for moving to spectators");
return true;
}
const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID((ClientID)atoi(argv[1]));
CompanyID company_id = (CompanyID)(atoi(argv[2]) <= MAX_COMPANIES ? atoi(argv[2]) - 1 : atoi(argv[2]));
/* check the client exists */
if (ci == nullptr) {
IConsoleError("Invalid client-id, check the command 'clients' for valid client-id's.");
return true;
}
if (!Company::IsValidID(company_id) && company_id != COMPANY_SPECTATOR) {
IConsolePrintF(CC_ERROR, "Company does not exist. Company-id must be between 1 and %d.", MAX_COMPANIES);
return true;
}
if (company_id != COMPANY_SPECTATOR && !Company::IsHumanID(company_id)) {
IConsoleError("You cannot move clients to AI companies.");
return true;
}
if (ci->client_id == CLIENT_ID_SERVER && _network_dedicated) {
IConsoleError("Silly boy, you cannot move the server!");
return true;
}
if (ci->client_playas == company_id) {
IConsoleError("You cannot move someone to where he/she already is!");
return true;
}
/* we are the server, so force the update */
NetworkServerDoMove(ci->client_id, company_id);
return true;
}
DEF_CONSOLE_CMD(ConResetCompany)
{
if (argc == 0) {
IConsoleHelp("Remove an idle company from the game. Usage: 'reset_company '");
IConsoleHelp("For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc.");
return true;
}
if (argc != 2) return false;
CompanyID index = (CompanyID)(atoi(argv[1]) - 1);
/* Check valid range */
if (!Company::IsValidID(index)) {
IConsolePrintF(CC_ERROR, "Company does not exist. Company-id must be between 1 and %d.", MAX_COMPANIES);
return true;
}
if (!Company::IsHumanID(index)) {
IConsoleError("Company is owned by an AI.");
return true;
}
if (NetworkCompanyHasClients(index)) {
IConsoleError("Cannot remove company: a client is connected to that company.");
return false;
}
const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER);
if (ci->client_playas == index) {
IConsoleError("Cannot remove company: the server is connected to that company.");
return true;
}
/* It is safe to remove this company */
DoCommandP(0, CCA_DELETE | index << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
IConsolePrint(CC_DEFAULT, "Company deleted.");
return true;
}
DEF_CONSOLE_CMD(ConNetworkClients)
{
if (argc == 0) {
IConsoleHelp("Get a list of connected clients including their ID, name, company-id, and IP. Usage: 'clients'");
return true;
}
NetworkPrintClients();
return true;
}
DEF_CONSOLE_CMD(ConNetworkReconnect)
{
if (argc == 0) {
IConsoleHelp("Reconnect to server to which you were connected last time. Usage: 'reconnect []'");
IConsoleHelp("Company 255 is spectator (default, if not specified), 0 means creating new company.");
IConsoleHelp("All others are a certain company with Company 1 being #1");
return true;
}
CompanyID playas = (argc >= 2) ? (CompanyID)atoi(argv[1]) : COMPANY_SPECTATOR;
switch (playas) {
case 0: playas = COMPANY_NEW_COMPANY; break;
case COMPANY_SPECTATOR: /* nothing to do */ break;
default:
/* From a user pov 0 is a new company, internally it's different and all
* companies are offset by one to ease up on users (eg companies 1-8 not 0-7) */
if (playas < COMPANY_FIRST + 1 || playas > MAX_COMPANIES + 1) return false;
break;
}
if (StrEmpty(_settings_client.network.last_host)) {
IConsolePrint(CC_DEFAULT, "No server for reconnecting.");
return true;
}
/* Don't resolve the address first, just print it directly as it comes from the config file. */
IConsolePrintF(CC_DEFAULT, "Reconnecting to %s:%d...", _settings_client.network.last_host, _settings_client.network.last_port);
NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), playas);
return true;
}
DEF_CONSOLE_CMD(ConNetworkConnect)
{
if (argc == 0) {
IConsoleHelp("Connect to a remote OTTD server and join the game. Usage: 'connect '");
IConsoleHelp("IP can contain port and company: 'IP[:Port][#Company]', eg: 'server.ottd.org:443#2'");
IConsoleHelp("Company #255 is spectator all others are a certain company with Company 1 being #1");
return true;
}
if (argc < 2) return false;
if (_networking) NetworkDisconnect(); // we are in network-mode, first close it!
const char *port = nullptr;
const char *company = nullptr;
char *ip = argv[1];
/* Default settings: default port and new company */
uint16 rport = NETWORK_DEFAULT_PORT;
CompanyID join_as = COMPANY_NEW_COMPANY;
ParseConnectionString(&company, &port, ip);
IConsolePrintF(CC_DEFAULT, "Connecting to %s...", ip);
if (company != nullptr) {
join_as = (CompanyID)atoi(company);
IConsolePrintF(CC_DEFAULT, " company-no: %d", join_as);
/* From a user pov 0 is a new company, internally it's different and all
* companies are offset by one to ease up on users (eg companies 1-8 not 0-7) */
if (join_as != COMPANY_SPECTATOR) {
if (join_as > MAX_COMPANIES) return false;
join_as--;
}
}
if (port != nullptr) {
rport = atoi(port);
IConsolePrintF(CC_DEFAULT, " port: %s", port);
}
NetworkClientConnectGame(NetworkAddress(ip, rport), join_as);
return true;
}
/*********************************
* script file console commands
*********************************/
DEF_CONSOLE_CMD(ConExec)
{
if (argc == 0) {
IConsoleHelp("Execute a local script file. Usage: 'exec