Changeset - r25044:b22efcf16422
[Not reviewed]
master
0 1 0
Patric Stout - 3 years ago 2021-03-12 08:23:03
truebrain@openttd.org
Fix: calling "exec" from script never returned

Example:

exec other.script
echo hello

The "echo" was never executed.
1 file changed with 11 insertions and 5 deletions:
0 comments (0 inline, 0 general)
src/console_cmds.cpp
Show inline comments
 
/*
 
 * 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 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 "newgrf_profiling.h"
 
#include "console_func.h"
 
#include "engine_base.h"
 
#include "road.h"
 
#include "rail.h"
 
#include "game/game.hpp"
 
#include "table/strings.h"
 
#include <time.h>
 

	
 
#include "safeguards.h"
 

	
 
/* scriptfile handling */
 
static bool _script_running; ///< Script is running (used to abort execution when #ConReturn is encountered).
 
static uint _script_current_depth; ///< Depth of scripts 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;
 
@@ -867,223 +867,229 @@ DEF_CONSOLE_CMD(ConNetworkClients)
 

	
 
DEF_CONSOLE_CMD(ConNetworkReconnect)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Reconnect to server to which you were connected last time. Usage: 'reconnect [<company>]'");
 
		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 <ip>'");
 
		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 <script> <?>'");
 
		return true;
 
	}
 

	
 
	if (argc < 2) return false;
 

	
 
	FILE *script_file = FioFOpenFile(argv[1], "r", BASE_DIR);
 

	
 
	if (script_file == nullptr) {
 
		if (argc == 2 || atoi(argv[2]) != 0) IConsoleError("script file not found");
 
		return true;
 
	}
 

	
 
	_script_running = true;
 
	_script_current_depth++;
 
	uint script_depth = _script_current_depth;
 

	
 
	char cmdline[ICON_CMDLN_SIZE];
 
	while (_script_running && fgets(cmdline, sizeof(cmdline), script_file) != nullptr) {
 
	while (fgets(cmdline, sizeof(cmdline), script_file) != nullptr) {
 
		/* Remove newline characters from the executing script */
 
		for (char *cmdptr = cmdline; *cmdptr != '\0'; cmdptr++) {
 
			if (*cmdptr == '\n' || *cmdptr == '\r') {
 
				*cmdptr = '\0';
 
				break;
 
			}
 
		}
 
		IConsoleCmdExec(cmdline);
 
		/* Ensure that we are still on the same depth or that we returned via 'return'. */
 
		assert(_script_current_depth == script_depth || _script_current_depth == script_depth - 1);
 

	
 
		/* The 'return' command was executed. */
 
		if (_script_current_depth == script_depth - 1) break;
 
	}
 

	
 
	if (ferror(script_file)) {
 
		IConsoleError("Encountered error while trying to read from script file");
 
	}
 

	
 
	_script_running = false;
 
	if (_script_current_depth == script_depth) _script_current_depth--;
 
	FioFCloseFile(script_file);
 
	return true;
 
}
 

	
 
DEF_CONSOLE_CMD(ConReturn)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Stop executing a running script. Usage: 'return'");
 
		return true;
 
	}
 

	
 
	_script_running = false;
 
	_script_current_depth--;
 
	return true;
 
}
 

	
 
/*****************************
 
 *  default console commands
 
 ******************************/
 
extern bool CloseConsoleLogIfActive();
 

	
 
DEF_CONSOLE_CMD(ConScript)
 
{
 
	extern FILE *_iconsole_output_file;
 

	
 
	if (argc == 0) {
 
		IConsoleHelp("Start or stop logging console output to a file. Usage: 'script <filename>'");
 
		IConsoleHelp("If filename is omitted, a running log is stopped if it is active");
 
		return true;
 
	}
 

	
 
	if (!CloseConsoleLogIfActive()) {
 
		if (argc < 2) return false;
 

	
 
		IConsolePrintF(CC_DEFAULT, "file output started to: %s", argv[1]);
 
		_iconsole_output_file = fopen(argv[1], "ab");
 
		if (_iconsole_output_file == nullptr) IConsoleError("could not open file");
 
	}
 

	
 
	return true;
 
}
 

	
 

	
 
DEF_CONSOLE_CMD(ConEcho)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Print back the first argument to the console. Usage: 'echo <arg>'");
 
		return true;
 
	}
 

	
 
	if (argc < 2) return false;
 
	IConsolePrint(CC_DEFAULT, argv[1]);
 
	return true;
 
}
 

	
 
DEF_CONSOLE_CMD(ConEchoC)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Print back the first argument to the console in a given colour. Usage: 'echoc <colour> <arg2>'");
 
		return true;
 
	}
 

	
 
	if (argc < 3) return false;
 
	IConsolePrint((TextColour)Clamp(atoi(argv[1]), TC_BEGIN, TC_END - 1), argv[2]);
 
	return true;
 
}
 

	
 
DEF_CONSOLE_CMD(ConNewGame)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Start a new game. Usage: 'newgame [seed]'");
 
		IConsoleHelp("The server can force a new game using 'newgame'; any client joined will rejoin after the server is done generating the new game.");
 
		return true;
 
	}
 

	
 
	StartNewGameWithoutGUI((argc == 2) ? strtoul(argv[1], nullptr, 10) : GENERATE_NEW_SEED);
 
	return true;
 
}
 

	
 
DEF_CONSOLE_CMD(ConRestart)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Restart game. Usage: 'restart'");
 
		IConsoleHelp("Restarts a game. It tries to reproduce the exact same map as the game started with.");
 
		IConsoleHelp("However:");
 
		IConsoleHelp(" * restarting games started in another version might create another map due to difference in map generation");
 
		IConsoleHelp(" * restarting games based on scenarios, loaded games or heightmaps will start a new game based on the settings stored in the scenario/savegame");
 
		return true;
 
	}
 

	
 
	/* Don't copy the _newgame pointers to the real pointers, so call SwitchToMode directly */
 
	_settings_game.game_creation.map_x = MapLogX();
 
	_settings_game.game_creation.map_y = FindFirstBit(MapSizeY());
 
	_switch_mode = SM_RESTARTGAME;
 
	return true;
 
}
 

	
 
DEF_CONSOLE_CMD(ConReload)
 
{
 
	if (argc == 0) {
 
		IConsoleHelp("Reload game. Usage: 'reload'");
 
		IConsoleHelp("Reloads a game.");
 
		IConsoleHelp(" * if you started from a savegame / scenario / heightmap, that exact same savegame / scenario / heightmap will be loaded.");
 
		IConsoleHelp(" * if you started from a new game, this acts the same as 'restart'.");
 
		return true;
 
	}
 

	
 
	/* Don't copy the _newgame pointers to the real pointers, so call SwitchToMode directly */
 
	_settings_game.game_creation.map_x = MapLogX();
0 comments (0 inline, 0 general)