mirror of https://github.com/OpenRCT2/OpenRCT2.git
2206 lines
83 KiB
C++
2206 lines
83 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2024 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "InteractiveConsole.h"
|
|
|
|
#include "../Context.h"
|
|
#include "../Date.h"
|
|
#include "../EditorObjectSelectionSession.h"
|
|
#include "../Game.h"
|
|
#include "../GameState.h"
|
|
#include "../OpenRCT2.h"
|
|
#include "../PlatformEnvironment.h"
|
|
#include "../ReplayManager.h"
|
|
#include "../Version.h"
|
|
#include "../actions/CheatSetAction.h"
|
|
#include "../actions/ClimateSetAction.h"
|
|
#include "../actions/ParkSetDateAction.h"
|
|
#include "../actions/ParkSetParameterAction.h"
|
|
#include "../actions/RideFreezeRatingAction.h"
|
|
#include "../actions/RideSetPriceAction.h"
|
|
#include "../actions/RideSetSettingAction.h"
|
|
#include "../actions/ScenarioSetSettingAction.h"
|
|
#include "../actions/StaffSetCostumeAction.h"
|
|
#include "../config/Config.h"
|
|
#include "../core/Console.hpp"
|
|
#include "../core/Guard.hpp"
|
|
#include "../core/Path.hpp"
|
|
#include "../core/String.hpp"
|
|
#include "../drawing/Drawing.h"
|
|
#include "../drawing/Font.h"
|
|
#include "../drawing/Image.h"
|
|
#include "../entity/Balloon.h"
|
|
#include "../entity/EntityList.h"
|
|
#include "../entity/EntityRegistry.h"
|
|
#include "../entity/Staff.h"
|
|
#include "../interface/Chat.h"
|
|
#include "../interface/Colour.h"
|
|
#include "../interface/Window_internal.h"
|
|
#include "../localisation/Formatting.h"
|
|
#include "../localisation/Localisation.h"
|
|
#include "../management/Finance.h"
|
|
#include "../management/NewsItem.h"
|
|
#include "../management/Research.h"
|
|
#include "../network/network.h"
|
|
#include "../object/Object.h"
|
|
#include "../object/ObjectList.h"
|
|
#include "../object/ObjectManager.h"
|
|
#include "../object/ObjectRepository.h"
|
|
#include "../platform/Platform.h"
|
|
#include "../profiling/Profiling.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/RideData.h"
|
|
#include "../ride/Vehicle.h"
|
|
#include "../util/Util.h"
|
|
#include "../windows/Intent.h"
|
|
#include "../world/Climate.h"
|
|
#include "../world/Park.h"
|
|
#include "../world/Scenery.h"
|
|
#include "Viewport.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdarg>
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
#include <exception>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#ifndef NO_TTF
|
|
# include "../drawing/TTF.h"
|
|
#endif
|
|
|
|
using namespace OpenRCT2;
|
|
|
|
using arguments_t = std::vector<std::string>;
|
|
using OpenRCT2::Date;
|
|
|
|
static constexpr const char* ClimateNames[] = {
|
|
"cool_and_wet",
|
|
"warm",
|
|
"hot_and_dry",
|
|
"cold",
|
|
};
|
|
|
|
static int32_t ConsoleParseInt(const std::string& src, bool* valid);
|
|
static double ConsoleParseDouble(const std::string& src, bool* valid);
|
|
|
|
static void ConsoleWriteAllCommands(InteractiveConsole& console);
|
|
static int32_t ConsoleCommandVariables(InteractiveConsole& console, const arguments_t& argv);
|
|
static int32_t ConsoleCommandWindows(InteractiveConsole& console, const arguments_t& argv);
|
|
static int32_t ConsoleCommandHelp(InteractiveConsole& console, const arguments_t& argv);
|
|
|
|
static bool InvalidArguments(bool* invalid, bool arguments);
|
|
|
|
#define SET_FLAG(variable, flag, value) \
|
|
{ \
|
|
if (value) \
|
|
variable |= flag; \
|
|
else \
|
|
variable &= ~(flag); \
|
|
}
|
|
|
|
static int32_t ConsoleParseInt(const std::string& src, bool* valid)
|
|
{
|
|
utf8* end;
|
|
int32_t value;
|
|
value = static_cast<int32_t>(strtol(src.c_str(), &end, 10));
|
|
*valid = (*end == '\0');
|
|
return value;
|
|
}
|
|
|
|
static double ConsoleParseDouble(const std::string& src, bool* valid)
|
|
{
|
|
utf8* end;
|
|
double value;
|
|
value = strtod(src.c_str(), &end);
|
|
*valid = (*end == '\0');
|
|
return value;
|
|
}
|
|
|
|
static int32_t ConsoleCommandClear(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
console.Clear();
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandClose(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
console.Close();
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandHide(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
console.Hide();
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandEcho(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
console.WriteLine(argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandRides(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
{
|
|
if (argv[0] == "list")
|
|
{
|
|
for (const auto& ride : GetRideManager())
|
|
{
|
|
auto name = ride.GetName();
|
|
console.WriteFormatLine(
|
|
"ride: %03d type: %02u subtype %03u operating mode: %02u name: %s", ride.id, ride.type, ride.subtype,
|
|
ride.mode, name.c_str());
|
|
}
|
|
}
|
|
else if (argv[0] == "set")
|
|
{
|
|
if (argv.size() < 4)
|
|
{
|
|
if (argv.size() > 1 && argv[1] == "mode")
|
|
{
|
|
console.WriteFormatLine("Ride modes are specified using integer IDs as given below:");
|
|
for (int32_t i = 0; i < static_cast<uint8_t>(RideMode::Count); i++)
|
|
{
|
|
char mode_name[128] = { 0 };
|
|
StringId mode_string_id = RideModeNames[i];
|
|
OpenRCT2::FormatStringLegacy(mode_name, 128, mode_string_id, nullptr);
|
|
console.WriteFormatLine("%02d - %s", i, mode_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("rides set type <ride id> <ride type>");
|
|
console.WriteFormatLine("rides set mode [<ride id> <operating mode>]");
|
|
console.WriteFormatLine("rides set mass <ride id> <mass value>");
|
|
console.WriteFormatLine("rides set excitement <ride id> <excitement value>");
|
|
console.WriteFormatLine("rides set intensity <ride id> <intensity value>");
|
|
console.WriteFormatLine("rides set nausea <ride id> <nausea value>");
|
|
console.WriteFormatLine("rides set price <ride id / all [type]> <price>");
|
|
}
|
|
return 0;
|
|
}
|
|
if (argv[1] == "type")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
int32_t ride_index = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
int32_t type = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects integer arguments");
|
|
}
|
|
else if (ride_index < 0)
|
|
{
|
|
console.WriteFormatLine("Ride index must not be negative");
|
|
}
|
|
else
|
|
{
|
|
auto res = SetOperatingSetting(RideId::FromUnderlying(ride_index), RideSetSetting::RideType, type);
|
|
if (res == kMoney64Undefined)
|
|
{
|
|
if (!GetGameState().Cheats.AllowArbitraryRideTypeChanges)
|
|
{
|
|
console.WriteFormatLine(
|
|
"That didn't work. Try enabling the 'Allow arbitrary ride type changes' cheat");
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("That didn't work");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "mode")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
int32_t ride_index = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
int32_t mode = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects integer arguments");
|
|
}
|
|
else if (ride_index < 0)
|
|
{
|
|
console.WriteFormatLine("Ride index must not be negative");
|
|
}
|
|
else
|
|
{
|
|
auto ride = GetRide(RideId::FromUnderlying(ride_index));
|
|
if (mode >= static_cast<uint8_t>(RideMode::Count))
|
|
{
|
|
console.WriteFormatLine("Invalid ride mode.");
|
|
}
|
|
else if (ride == nullptr)
|
|
{
|
|
console.WriteFormatLine("No ride found with index %d", ride_index);
|
|
}
|
|
else
|
|
{
|
|
ride->mode = static_cast<RideMode>(mode & 0xFF);
|
|
InvalidateTestResults(*ride);
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "mass")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
int32_t ride_index = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
int32_t mass = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
|
|
if (ride_index < 0)
|
|
{
|
|
console.WriteFormatLine("Ride index must not be negative");
|
|
}
|
|
else if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects integer arguments");
|
|
}
|
|
else
|
|
{
|
|
auto ride = GetRide(RideId::FromUnderlying(ride_index));
|
|
if (mass <= 0)
|
|
{
|
|
console.WriteFormatLine("Friction value must be strictly positive");
|
|
}
|
|
else if (ride == nullptr)
|
|
{
|
|
console.WriteFormatLine("No ride found with index %d", ride_index);
|
|
}
|
|
else
|
|
{
|
|
for (int32_t i = 0; i < ride->NumTrains; ++i)
|
|
{
|
|
for (Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]); vehicle != nullptr;
|
|
vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
|
|
{
|
|
vehicle->mass = mass;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "excitement")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
int32_t ride_index = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
ride_rating excitement = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
|
|
if (ride_index < 0)
|
|
{
|
|
console.WriteFormatLine("Ride index must not be negative");
|
|
}
|
|
else if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects integer arguments");
|
|
}
|
|
else
|
|
{
|
|
auto rideIndex = RideId::FromUnderlying(ride_index);
|
|
auto ride = GetRide(rideIndex);
|
|
if (excitement <= 0)
|
|
{
|
|
console.WriteFormatLine("Excitement value must be strictly positive");
|
|
}
|
|
else if (ride == nullptr)
|
|
{
|
|
console.WriteFormatLine("No ride found with index %d", ride_index);
|
|
}
|
|
else
|
|
{
|
|
auto rideAction = RideFreezeRatingAction(rideIndex, RideRatingType::Excitement, excitement);
|
|
GameActions::Execute(&rideAction);
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "intensity")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
int32_t ride_index = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
ride_rating intensity = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
|
|
if (ride_index < 0)
|
|
{
|
|
console.WriteFormatLine("Ride index must not be negative");
|
|
}
|
|
else if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects integer arguments");
|
|
}
|
|
else
|
|
{
|
|
auto rideIndex = RideId::FromUnderlying(ride_index);
|
|
auto ride = GetRide(rideIndex);
|
|
if (intensity <= 0)
|
|
{
|
|
console.WriteFormatLine("Intensity value must be strictly positive");
|
|
}
|
|
else if (ride == nullptr)
|
|
{
|
|
console.WriteFormatLine("No ride found with index %d", ride_index);
|
|
}
|
|
else
|
|
{
|
|
auto rideAction = RideFreezeRatingAction(rideIndex, RideRatingType::Intensity, intensity);
|
|
GameActions::Execute(&rideAction);
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "nausea")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
int32_t ride_index = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
ride_rating nausea = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
|
|
if (ride_index < 0)
|
|
{
|
|
console.WriteFormatLine("Ride index must not be negative");
|
|
}
|
|
else if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects integer arguments");
|
|
}
|
|
else
|
|
{
|
|
auto rideIndex = RideId::FromUnderlying(ride_index);
|
|
auto ride = GetRide(rideIndex);
|
|
if (nausea <= 0)
|
|
{
|
|
console.WriteFormatLine("Nausea value must be strictly positive");
|
|
}
|
|
else if (ride == nullptr)
|
|
{
|
|
console.WriteFormatLine("No ride found with index %d", ride_index);
|
|
}
|
|
else
|
|
{
|
|
auto rideAction = RideFreezeRatingAction(rideIndex, RideRatingType::Nausea, nausea);
|
|
GameActions::Execute(&rideAction);
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "price")
|
|
{
|
|
bool int_valid[2] = { false };
|
|
if (argv[2] == "all")
|
|
{
|
|
auto arg1 = ConsoleParseInt(argv[3], &int_valid[0]);
|
|
if (argv.size() <= 4)
|
|
{
|
|
auto price = arg1;
|
|
if (int_valid[0])
|
|
{
|
|
for (const auto& ride : GetRideManager())
|
|
{
|
|
auto rideSetPrice = RideSetPriceAction(ride.id, price, true);
|
|
GameActions::Execute(&rideSetPrice);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("This command expects one or two integer arguments");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto rideType = arg1;
|
|
auto price = ConsoleParseInt(argv[4], &int_valid[1]);
|
|
|
|
if (int_valid[0] && int_valid[1])
|
|
{
|
|
for (const auto& ride : GetRideManager())
|
|
{
|
|
if (ride.type == rideType)
|
|
{
|
|
auto rideSetPrice = RideSetPriceAction(ride.id, price, true);
|
|
GameActions::Execute(&rideSetPrice);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("This command expects one or two integer arguments");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32_t rideId = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
money64 price = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
|
|
if (!int_valid[0] || !int_valid[1])
|
|
{
|
|
console.WriteFormatLine("This command expects the string all or two integer arguments");
|
|
}
|
|
else
|
|
{
|
|
auto rideSetPrice = RideSetPriceAction(RideId::FromUnderlying(rideId), price, true);
|
|
GameActions::Execute(&rideSetPrice);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("subcommands: list, set");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandStaff(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
{
|
|
if (argv[0] == "list")
|
|
{
|
|
for (auto peep : EntityList<Staff>())
|
|
{
|
|
auto name = peep->GetName();
|
|
console.WriteFormatLine(
|
|
"staff id %03d type: %02u energy %03u name %s", peep->Id, peep->AssignedStaffType, peep->Energy,
|
|
name.c_str());
|
|
}
|
|
}
|
|
else if (argv[0] == "set")
|
|
{
|
|
if (argv.size() < 4)
|
|
{
|
|
console.WriteFormatLine("staff set energy <staff id> <value 0-255>");
|
|
console.WriteFormatLine("staff set costume <staff id> <costume id>");
|
|
for (int32_t i = 0; i < static_cast<uint8_t>(EntertainerCostume::Count); i++)
|
|
{
|
|
char costume_name[128] = { 0 };
|
|
StringId costume = StaffCostumeNames[i];
|
|
OpenRCT2::FormatStringLegacy(costume_name, 128, STR_DROPDOWN_MENU_LABEL, &costume);
|
|
// That's a terrible hack here. Costume names include inline sprites
|
|
// that don't work well with the console, so manually skip past them.
|
|
console.WriteFormatLine(" costume %i: %s", i, costume_name + 7);
|
|
}
|
|
return 0;
|
|
}
|
|
if (argv[1] == "energy")
|
|
{
|
|
int32_t int_val[3];
|
|
bool int_valid[3] = { false };
|
|
int_val[0] = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
int_val[1] = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
|
|
if (int_valid[0] && int_valid[1])
|
|
{
|
|
Peep* peep = GetEntity<Peep>(EntityId::FromUnderlying(int_val[0]));
|
|
if (peep != nullptr)
|
|
{
|
|
peep->Energy = int_val[1];
|
|
peep->EnergyTarget = int_val[1];
|
|
}
|
|
}
|
|
}
|
|
else if (argv[1] == "costume")
|
|
{
|
|
int32_t int_val[2];
|
|
bool int_valid[2] = { false };
|
|
int_val[0] = ConsoleParseInt(argv[2], &int_valid[0]);
|
|
int_val[1] = ConsoleParseInt(argv[3], &int_valid[1]);
|
|
if (!int_valid[0])
|
|
{
|
|
console.WriteLineError("Invalid staff ID");
|
|
return 1;
|
|
}
|
|
auto staff = GetEntity<Staff>(EntityId::FromUnderlying(int_val[0]));
|
|
if (staff == nullptr)
|
|
{
|
|
console.WriteLineError("Invalid staff ID");
|
|
return 1;
|
|
}
|
|
if (staff->AssignedStaffType != StaffType::Entertainer)
|
|
{
|
|
console.WriteLineError("Specified staff is not entertainer");
|
|
return 1;
|
|
}
|
|
if (!int_valid[1] || int_val[1] < 0 || int_val[1] >= static_cast<uint8_t>(EntertainerCostume::Count))
|
|
{
|
|
console.WriteLineError("Invalid costume ID");
|
|
return 1;
|
|
}
|
|
|
|
EntertainerCostume costume = static_cast<EntertainerCostume>(int_val[1]);
|
|
auto staffSetCostumeAction = StaffSetCostumeAction(EntityId::FromUnderlying(int_val[0]), costume);
|
|
GameActions::Execute(&staffSetCostumeAction);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("subcommands: list, set");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandGet(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
auto& gameState = GetGameState();
|
|
|
|
if (!argv.empty())
|
|
{
|
|
if (argv[0] == "park_rating")
|
|
{
|
|
console.WriteFormatLine("park_rating %d", gameState.Park.Rating);
|
|
}
|
|
else if (argv[0] == "park_value")
|
|
{
|
|
console.WriteFormatLine("park_value %d", gameState.Park.Value / 10);
|
|
}
|
|
else if (argv[0] == "company_value")
|
|
{
|
|
console.WriteFormatLine("company_value %d", gameState.CompanyValue / 10);
|
|
}
|
|
else if (argv[0] == "money")
|
|
{
|
|
console.WriteFormatLine("money %d.%d0", gameState.Cash / 10, gameState.Cash % 10);
|
|
}
|
|
else if (argv[0] == "scenario_initial_cash")
|
|
{
|
|
console.WriteFormatLine("scenario_initial_cash %d", gameState.InitialCash / 10);
|
|
}
|
|
else if (argv[0] == "current_loan")
|
|
{
|
|
console.WriteFormatLine("current_loan %d", gameState.BankLoan / 10);
|
|
}
|
|
else if (argv[0] == "max_loan")
|
|
{
|
|
console.WriteFormatLine("max_loan %d", gameState.MaxBankLoan / 10);
|
|
}
|
|
else if (argv[0] == "guest_initial_cash")
|
|
{
|
|
console.WriteFormatLine(
|
|
"guest_initial_cash %d.%d0", gameState.GuestInitialCash / 10, gameState.GuestInitialCash % 10);
|
|
}
|
|
else if (argv[0] == "guest_initial_happiness")
|
|
{
|
|
uint32_t current_happiness = gameState.GuestInitialHappiness;
|
|
for (int32_t i = 15; i <= 99; i++)
|
|
{
|
|
if (i == 99)
|
|
{
|
|
console.WriteFormatLine("guest_initial_happiness %d%% (%d)", 15, gameState.GuestInitialHappiness);
|
|
}
|
|
else if (current_happiness == Park::CalculateGuestInitialHappiness(i))
|
|
{
|
|
console.WriteFormatLine("guest_initial_happiness %d%% (%d)", i, gameState.GuestInitialHappiness);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (argv[0] == "guest_initial_hunger")
|
|
{
|
|
console.WriteFormatLine(
|
|
"guest_initial_hunger %d%% (%d)", ((255 - gameState.GuestInitialHunger) * 100) / 255,
|
|
gameState.GuestInitialHunger);
|
|
}
|
|
else if (argv[0] == "guest_initial_thirst")
|
|
{
|
|
console.WriteFormatLine(
|
|
"guest_initial_thirst %d%% (%d)", ((255 - gameState.GuestInitialThirst) * 100) / 255,
|
|
gameState.GuestInitialThirst);
|
|
}
|
|
else if (argv[0] == "guest_prefer_less_intense_rides")
|
|
{
|
|
console.WriteFormatLine(
|
|
"guest_prefer_less_intense_rides %d", (gameState.Park.Flags & PARK_FLAGS_PREF_LESS_INTENSE_RIDES) != 0);
|
|
}
|
|
else if (argv[0] == "guest_prefer_more_intense_rides")
|
|
{
|
|
console.WriteFormatLine(
|
|
"guest_prefer_more_intense_rides %d", (gameState.Park.Flags & PARK_FLAGS_PREF_MORE_INTENSE_RIDES) != 0);
|
|
}
|
|
else if (argv[0] == "forbid_marketing_campaigns")
|
|
{
|
|
console.WriteFormatLine(
|
|
"forbid_marketing_campaigns %d", (gameState.Park.Flags & PARK_FLAGS_FORBID_MARKETING_CAMPAIGN) != 0);
|
|
}
|
|
else if (argv[0] == "forbid_landscape_changes")
|
|
{
|
|
console.WriteFormatLine(
|
|
"forbid_landscape_changes %d", (gameState.Park.Flags & PARK_FLAGS_FORBID_LANDSCAPE_CHANGES) != 0);
|
|
}
|
|
else if (argv[0] == "forbid_tree_removal")
|
|
{
|
|
console.WriteFormatLine("forbid_tree_removal %d", (gameState.Park.Flags & PARK_FLAGS_FORBID_TREE_REMOVAL) != 0);
|
|
}
|
|
else if (argv[0] == "forbid_high_construction")
|
|
{
|
|
console.WriteFormatLine(
|
|
"forbid_high_construction %d", (gameState.Park.Flags & PARK_FLAGS_FORBID_HIGH_CONSTRUCTION) != 0);
|
|
}
|
|
else if (argv[0] == "pay_for_rides")
|
|
{
|
|
console.WriteFormatLine("pay_for_rides %d", (gameState.Park.Flags & PARK_FLAGS_PARK_FREE_ENTRY) != 0);
|
|
}
|
|
else if (argv[0] == "no_money")
|
|
{
|
|
console.WriteFormatLine("no_money %d", (gameState.Park.Flags & PARK_FLAGS_NO_MONEY) != 0);
|
|
}
|
|
else if (argv[0] == "difficult_park_rating")
|
|
{
|
|
console.WriteFormatLine("difficult_park_rating %d", (gameState.Park.Flags & PARK_FLAGS_DIFFICULT_PARK_RATING) != 0);
|
|
}
|
|
else if (argv[0] == "difficult_guest_generation")
|
|
{
|
|
console.WriteFormatLine(
|
|
"difficult_guest_generation %d", (gameState.Park.Flags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) != 0);
|
|
}
|
|
else if (argv[0] == "park_open")
|
|
{
|
|
console.WriteFormatLine("park_open %d", (gameState.Park.Flags & PARK_FLAGS_PARK_OPEN) != 0);
|
|
}
|
|
else if (argv[0] == "land_rights_cost")
|
|
{
|
|
console.WriteFormatLine("land_rights_cost %d.%d0", gameState.LandPrice / 10, gameState.LandPrice % 10);
|
|
}
|
|
else if (argv[0] == "construction_rights_cost")
|
|
{
|
|
console.WriteFormatLine(
|
|
"construction_rights_cost %d.%d0", gameState.ConstructionRightsPrice / 10,
|
|
gameState.ConstructionRightsPrice % 10);
|
|
}
|
|
else if (argv[0] == "climate")
|
|
{
|
|
console.WriteFormatLine(
|
|
"climate %s (%d)", ClimateNames[EnumValue(gameState.Climate)], EnumValue(gameState.Climate));
|
|
}
|
|
else if (argv[0] == "game_speed")
|
|
{
|
|
console.WriteFormatLine("game_speed %d", gGameSpeed);
|
|
}
|
|
else if (argv[0] == "console_small_font")
|
|
{
|
|
console.WriteFormatLine("console_small_font %d", gConfigInterface.ConsoleSmallFont);
|
|
}
|
|
else if (argv[0] == "location")
|
|
{
|
|
WindowBase* w = WindowGetMain();
|
|
if (w != nullptr)
|
|
{
|
|
Viewport* viewport = WindowGetViewport(w);
|
|
auto info = GetMapCoordinatesFromPos(
|
|
{ viewport->view_width / 2, viewport->view_height / 2 }, EnumsToFlags(ViewportInteractionItem::Terrain));
|
|
|
|
auto tileMapCoord = TileCoordsXY(info.Loc);
|
|
console.WriteFormatLine("location %d %d", tileMapCoord.x, tileMapCoord.y);
|
|
}
|
|
}
|
|
else if (argv[0] == "window_scale")
|
|
{
|
|
console.WriteFormatLine("window_scale %.3f", gConfigGeneral.WindowScale);
|
|
}
|
|
else if (argv[0] == "window_limit")
|
|
{
|
|
console.WriteFormatLine("window_limit %d", gConfigGeneral.WindowLimit);
|
|
}
|
|
else if (argv[0] == "render_weather_effects")
|
|
{
|
|
console.WriteFormatLine("render_weather_effects %d", gConfigGeneral.RenderWeatherEffects);
|
|
}
|
|
else if (argv[0] == "render_weather_gloom")
|
|
{
|
|
console.WriteFormatLine("render_weather_gloom %d", gConfigGeneral.RenderWeatherGloom);
|
|
}
|
|
else if (argv[0] == "cheat_sandbox_mode")
|
|
{
|
|
console.WriteFormatLine("cheat_sandbox_mode %d", GetGameState().Cheats.SandboxMode);
|
|
}
|
|
else if (argv[0] == "cheat_disable_clearance_checks")
|
|
{
|
|
console.WriteFormatLine("cheat_disable_clearance_checks %d", GetGameState().Cheats.DisableClearanceChecks);
|
|
}
|
|
else if (argv[0] == "cheat_disable_support_limits")
|
|
{
|
|
console.WriteFormatLine("cheat_disable_support_limits %d", GetGameState().Cheats.DisableSupportLimits);
|
|
}
|
|
else if (argv[0] == "current_rotation")
|
|
{
|
|
console.WriteFormatLine("current_rotation %d", GetCurrentRotation());
|
|
}
|
|
else if (argv[0] == "host_timescale")
|
|
{
|
|
console.WriteFormatLine("host_timescale %.02f", OpenRCT2::GetContext()->GetTimeScale());
|
|
}
|
|
#ifndef NO_TTF
|
|
else if (argv[0] == "enable_hinting")
|
|
{
|
|
console.WriteFormatLine("enable_hinting %d", gConfigFonts.EnableHinting);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
console.WriteLineWarning("Invalid variable.");
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
static int32_t ConsoleCommandSet(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (argv.size() > 1)
|
|
{
|
|
int32_t int_val[4];
|
|
bool int_valid[4];
|
|
double double_val[4];
|
|
bool double_valid[4];
|
|
bool invalidArgs = false;
|
|
|
|
for (uint32_t i = 0; i < std::size(int_val); i++)
|
|
{
|
|
if (i + 1 < argv.size())
|
|
{
|
|
int_val[i] = ConsoleParseInt(argv[i + 1], &int_valid[i]);
|
|
double_val[i] = ConsoleParseDouble(argv[i + 1], &double_valid[i]);
|
|
}
|
|
else
|
|
{
|
|
int_val[i] = 0;
|
|
int_valid[i] = false;
|
|
double_val[i] = 0;
|
|
double_valid[i] = false;
|
|
}
|
|
}
|
|
|
|
auto& gameState = GetGameState();
|
|
if (argv[0] == "money" && InvalidArguments(&invalidArgs, double_valid[0]))
|
|
{
|
|
money64 money = ToMoney64FromGBP(double_val[0]);
|
|
if (gameState.Cash != money)
|
|
{
|
|
auto cheatSetAction = CheatSetAction(CheatType::SetMoney, money);
|
|
cheatSetAction.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set money command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get money");
|
|
});
|
|
GameActions::Execute(&cheatSetAction);
|
|
}
|
|
else
|
|
{
|
|
console.Execute("get money");
|
|
}
|
|
}
|
|
else if (argv[0] == "scenario_initial_cash" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::InitialCash, std::clamp(ToMoney64FromGBP(int_val[0]), 0.00_GBP, 1000000.00_GBP));
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set scenario_initial_cash command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get scenario_initial_cash");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "current_loan" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto amount = std::clamp(
|
|
ToMoney64FromGBP(int_val[0]) - ToMoney64FromGBP(int_val[0] % 1000), 0.00_GBP, gameState.MaxBankLoan);
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::InitialLoan, amount);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set current_loan command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get current_loan");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "max_loan" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto amount = std::clamp(
|
|
ToMoney64FromGBP(int_val[0]) - ToMoney64FromGBP(int_val[0] % 1000), 0.00_GBP, 5000000.00_GBP);
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::MaximumLoanSize, amount);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set max_loan command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get max_loan");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "guest_initial_cash" && InvalidArguments(&invalidArgs, double_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::AverageCashPerGuest, std::clamp(ToMoney64FromGBP(double_val[0]), 0.00_GBP, 1000.00_GBP));
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set guest_initial_cash command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get guest_initial_cash");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "guest_initial_happiness" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::GuestInitialHappiness,
|
|
Park::CalculateGuestInitialHappiness(static_cast<uint8_t>(int_val[0])));
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set guest_initial_happiness command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get guest_initial_happiness");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "guest_initial_hunger" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::GuestInitialHunger, (std::clamp(int_val[0], 1, 84) * 255 / 100 - 255) * -1);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set guest_initial_hunger command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get guest_initial_happiness");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "guest_initial_thirst" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::GuestInitialThirst, (std::clamp(int_val[0], 1, 84) * 255 / 100 - 255) * -1);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set guest_initial_thirst command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get guest_initial_thirst");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "guest_prefer_less_intense_rides" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::GuestsPreferLessIntenseRides, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set guest_prefer_less_intense_rides command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get guest_prefer_less_intense_rides");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "guest_prefer_more_intense_rides" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::GuestsPreferMoreIntenseRides, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set guest_prefer_more_intense_rides command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get guest_prefer_more_intense_rides");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "forbid_marketing_campaigns" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::ForbidMarketingCampaigns, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set forbid_marketing_campaigns command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get forbid_marketing_campaigns");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "forbid_landscape_changes" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::ForbidLandscapeChanges, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set forbid_landscape_changes command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get forbid_landscape_changes");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "forbid_tree_removal" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::ForbidTreeRemoval, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set forbid_tree_removal command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get forbid_tree_removal");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "forbid_high_construction" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::ForbidHighConstruction, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set forbid_high_construction command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get forbid_high_construction");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "pay_for_rides" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
SET_FLAG(gameState.Park.Flags, PARK_FLAGS_PARK_FREE_ENTRY, int_val[0]);
|
|
console.Execute("get pay_for_rides");
|
|
}
|
|
else if (argv[0] == "no_money" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto cheatSetAction = CheatSetAction(CheatType::NoMoney, int_val[0] != 0);
|
|
cheatSetAction.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set no_money command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get no_money");
|
|
});
|
|
GameActions::Execute(&cheatSetAction);
|
|
}
|
|
else if (argv[0] == "difficult_park_rating" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(ScenarioSetSetting::ParkRatingHigherDifficultyLevel, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set difficult_park_rating command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get difficult_park_rating");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "difficult_guest_generation" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::GuestGenerationHigherDifficultyLevel, int_val[0]);
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set difficult_guest_generation command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get difficult_guest_generation");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "park_open" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
auto parkSetParameter = ParkSetParameterAction((int_val[0] == 1) ? ParkParameter::Open : ParkParameter::Close);
|
|
parkSetParameter.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set park_open command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get park_open");
|
|
});
|
|
GameActions::Execute(&parkSetParameter);
|
|
}
|
|
else if (argv[0] == "land_rights_cost" && InvalidArguments(&invalidArgs, double_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::CostToBuyLand, std::clamp(ToMoney64FromGBP(double_val[0]), 0.00_GBP, 200.00_GBP));
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set land_rights_cost command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get land_rights_cost");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "construction_rights_cost" && InvalidArguments(&invalidArgs, double_valid[0]))
|
|
{
|
|
auto scenarioSetSetting = ScenarioSetSettingAction(
|
|
ScenarioSetSetting::CostToBuyConstructionRights,
|
|
std::clamp(ToMoney64FromGBP(double_val[0]), 0.00_GBP, 200.00_GBP));
|
|
scenarioSetSetting.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("set construction_rights_cost command failed, likely due to permissions.");
|
|
else
|
|
console.Execute("get construction_rights_cost");
|
|
});
|
|
GameActions::Execute(&scenarioSetSetting);
|
|
}
|
|
else if (argv[0] == "climate")
|
|
{
|
|
uint8_t newClimate = static_cast<uint8_t>(ClimateType::Count);
|
|
invalidArgs = true;
|
|
|
|
if (int_valid[0])
|
|
{
|
|
newClimate = static_cast<uint8_t>(int_val[0]);
|
|
invalidArgs = false;
|
|
}
|
|
else
|
|
{
|
|
for (newClimate = 0; newClimate < static_cast<uint8_t>(ClimateType::Count); newClimate++)
|
|
{
|
|
if (argv[1] == ClimateNames[newClimate])
|
|
{
|
|
invalidArgs = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (invalidArgs)
|
|
{
|
|
console.WriteLine(LanguageGetString(STR_INVALID_CLIMATE_ID));
|
|
}
|
|
else
|
|
{
|
|
auto gameAction = ClimateSetAction(ClimateType{ newClimate });
|
|
GameActions::Execute(&gameAction);
|
|
|
|
console.Execute("get climate");
|
|
}
|
|
}
|
|
else if (argv[0] == "game_speed" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
gGameSpeed = std::clamp(int_val[0], 1, 8);
|
|
console.Execute("get game_speed");
|
|
}
|
|
else if (argv[0] == "console_small_font" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
gConfigInterface.ConsoleSmallFont = (int_val[0] != 0);
|
|
ConfigSaveDefault();
|
|
console.Execute("get console_small_font");
|
|
}
|
|
else if (argv[0] == "location" && InvalidArguments(&invalidArgs, int_valid[0] && int_valid[1]))
|
|
{
|
|
WindowBase* w = WindowGetMain();
|
|
if (w != nullptr)
|
|
{
|
|
auto location = TileCoordsXYZ(int_val[0], int_val[1], 0).ToCoordsXYZ().ToTileCentre();
|
|
location.z = TileElementHeight(location);
|
|
w->SetLocation(location);
|
|
ViewportUpdatePosition(w);
|
|
console.Execute("get location");
|
|
}
|
|
}
|
|
else if (argv[0] == "window_scale" && InvalidArguments(&invalidArgs, double_valid[0]))
|
|
{
|
|
float newScale = static_cast<float>(0.001 * std::trunc(1000 * double_val[0]));
|
|
gConfigGeneral.WindowScale = std::clamp(newScale, 0.5f, 5.0f);
|
|
ConfigSaveDefault();
|
|
GfxInvalidateScreen();
|
|
ContextTriggerResize();
|
|
ContextUpdateCursorScale();
|
|
console.Execute("get window_scale");
|
|
}
|
|
else if (argv[0] == "window_limit" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
WindowSetWindowLimit(int_val[0]);
|
|
console.Execute("get window_limit");
|
|
}
|
|
else if (argv[0] == "render_weather_effects" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
gConfigGeneral.RenderWeatherEffects = (int_val[0] != 0);
|
|
ConfigSaveDefault();
|
|
console.Execute("get render_weather_effects");
|
|
}
|
|
else if (argv[0] == "render_weather_gloom" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
gConfigGeneral.RenderWeatherGloom = (int_val[0] != 0);
|
|
ConfigSaveDefault();
|
|
console.Execute("get render_weather_gloom");
|
|
}
|
|
else if (argv[0] == "cheat_sandbox_mode" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
if (GetGameState().Cheats.SandboxMode != (int_val[0] != 0))
|
|
{
|
|
auto cheatSetAction = CheatSetAction(CheatType::SandboxMode, int_val[0] != 0);
|
|
cheatSetAction.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("Network error: Permission denied!");
|
|
else
|
|
console.Execute("get cheat_sandbox_mode");
|
|
});
|
|
GameActions::Execute(&cheatSetAction);
|
|
}
|
|
else
|
|
{
|
|
console.Execute("get cheat_sandbox_mode");
|
|
}
|
|
}
|
|
else if (argv[0] == "cheat_disable_clearance_checks" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
if (GetGameState().Cheats.DisableClearanceChecks != (int_val[0] != 0))
|
|
{
|
|
auto cheatSetAction = CheatSetAction(CheatType::DisableClearanceChecks, int_val[0] != 0);
|
|
cheatSetAction.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("Network error: Permission denied!");
|
|
else
|
|
console.Execute("get cheat_disable_clearance_checks");
|
|
});
|
|
GameActions::Execute(&cheatSetAction);
|
|
}
|
|
else
|
|
{
|
|
console.Execute("get cheat_disable_clearance_checks");
|
|
}
|
|
}
|
|
else if (argv[0] == "cheat_disable_support_limits" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
if (GetGameState().Cheats.DisableSupportLimits != (int_val[0] != 0))
|
|
{
|
|
auto cheatSetAction = CheatSetAction(CheatType::DisableSupportLimits, int_val[0] != 0);
|
|
cheatSetAction.SetCallback([&console](const GameAction*, const GameActions::Result* res) {
|
|
if (res->Error != GameActions::Status::Ok)
|
|
console.WriteLineError("Network error: Permission denied!");
|
|
else
|
|
console.Execute("get cheat_disable_support_limits");
|
|
});
|
|
GameActions::Execute(&cheatSetAction);
|
|
}
|
|
else
|
|
{
|
|
console.Execute("get cheat_disable_support_limits");
|
|
}
|
|
}
|
|
else if (argv[0] == "current_rotation" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
uint8_t currentRotation = GetCurrentRotation();
|
|
int32_t newRotation = int_val[0];
|
|
if (newRotation < 0 || newRotation > 3)
|
|
{
|
|
console.WriteLineError("Invalid argument. Valid rotations are 0-3.");
|
|
}
|
|
else if (newRotation != currentRotation)
|
|
{
|
|
ViewportRotateAll(newRotation - currentRotation);
|
|
}
|
|
console.Execute("get current_rotation");
|
|
}
|
|
else if (argv[0] == "host_timescale" && InvalidArguments(&invalidArgs, double_valid[0]))
|
|
{
|
|
float newScale = static_cast<float>(double_val[0]);
|
|
|
|
OpenRCT2::GetContext()->SetTimeScale(newScale);
|
|
|
|
console.Execute("get host_timescale");
|
|
}
|
|
#ifndef NO_TTF
|
|
else if (argv[0] == "enable_hinting" && InvalidArguments(&invalidArgs, int_valid[0]))
|
|
{
|
|
gConfigFonts.EnableHinting = (int_val[0] != 0);
|
|
ConfigSaveDefault();
|
|
console.Execute("get enable_hinting");
|
|
TTFToggleHinting();
|
|
}
|
|
#endif
|
|
else if (invalidArgs)
|
|
{
|
|
console.WriteLineError("Invalid arguments.");
|
|
}
|
|
else
|
|
{
|
|
console.WriteLineError("Invalid variable.");
|
|
}
|
|
|
|
GfxInvalidateScreen();
|
|
}
|
|
else
|
|
{
|
|
console.WriteLineError("Value required.");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandLoadObject(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
{
|
|
char name[9] = { 0 };
|
|
std::fill_n(name, 8, ' ');
|
|
std::size_t i = 0;
|
|
for (const char* ch = argv[0].c_str(); *ch != '\0' && i < std::size(name) - 1; ch++)
|
|
{
|
|
name[i++] = *ch;
|
|
}
|
|
|
|
const ObjectRepositoryItem* ori = ObjectRepositoryFindObjectByName(name);
|
|
if (ori == nullptr)
|
|
{
|
|
console.WriteLineError("Could not find the object.");
|
|
return 1;
|
|
}
|
|
|
|
const RCTObjectEntry* entry = &ori->ObjectEntry;
|
|
const auto* loadedObject = ObjectManagerGetLoadedObject(ObjectEntryDescriptor(*ori));
|
|
if (loadedObject != nullptr)
|
|
{
|
|
console.WriteLineError("Object is already in scenario.");
|
|
return 1;
|
|
}
|
|
|
|
loadedObject = ObjectManagerLoadObject(entry);
|
|
if (loadedObject == nullptr)
|
|
{
|
|
console.WriteLineError("Unable to load object.");
|
|
return 1;
|
|
}
|
|
auto groupIndex = ObjectManagerGetLoadedObjectEntryIndex(loadedObject);
|
|
|
|
ObjectType objectType = entry->GetType();
|
|
if (objectType == ObjectType::Ride)
|
|
{
|
|
// Automatically research the ride so it's supported by the game.
|
|
const auto* rideEntry = GetRideEntryByIndex(groupIndex);
|
|
|
|
for (int32_t j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++)
|
|
{
|
|
auto rideType = rideEntry->ride_type[j];
|
|
if (rideType != RIDE_TYPE_NULL)
|
|
{
|
|
ResearchCategory category = GetRideTypeDescriptor(rideType).GetResearchCategory();
|
|
ResearchInsertRideEntry(rideType, groupIndex, category, true);
|
|
}
|
|
}
|
|
|
|
gSilentResearch = true;
|
|
ResearchResetCurrentItem();
|
|
gSilentResearch = false;
|
|
}
|
|
else if (objectType == ObjectType::SceneryGroup)
|
|
{
|
|
ResearchInsertSceneryGroupEntry(groupIndex, true);
|
|
|
|
gSilentResearch = true;
|
|
ResearchResetCurrentItem();
|
|
gSilentResearch = false;
|
|
}
|
|
ScenerySetDefaultPlacementConfiguration();
|
|
|
|
auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
|
|
ContextBroadcastIntent(&intent);
|
|
|
|
gWindowUpdateTicks = 0;
|
|
GfxInvalidateScreen();
|
|
console.WriteLine("Object file loaded.");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
constexpr std::array _objectTypeNames = {
|
|
"Rides",
|
|
"Small Scenery",
|
|
"Large Scenery",
|
|
"Walls",
|
|
"Banners",
|
|
"Paths",
|
|
"Path Additions",
|
|
"Scenery groups",
|
|
"Park entrances",
|
|
"Water",
|
|
"ScenarioText",
|
|
"Terrain Surface",
|
|
"Terrain Edges",
|
|
"Stations",
|
|
"Music",
|
|
"Footpath Surface",
|
|
"Footpath Railings",
|
|
"Audio",
|
|
};
|
|
static_assert(_objectTypeNames.size() == EnumValue(ObjectType::Count));
|
|
|
|
static int32_t ConsoleCommandCountObjects(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
for (auto objectType : ObjectTypes)
|
|
{
|
|
int32_t entryGroupIndex = 0;
|
|
for (; entryGroupIndex < object_entry_group_counts[EnumValue(objectType)]; entryGroupIndex++)
|
|
{
|
|
if (ObjectEntryGetObject(objectType, entryGroupIndex) == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
console.WriteFormatLine(
|
|
"%s: %d/%d", _objectTypeNames[EnumValue(objectType)], entryGroupIndex,
|
|
object_entry_group_counts[EnumValue(objectType)]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandOpen(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
{
|
|
bool title = (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) != 0;
|
|
bool invalidTitle = false;
|
|
if (argv[0] == "object_selection" && InvalidArguments(&invalidTitle, !title))
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteLineError("Cannot open this window in multiplayer mode.");
|
|
}
|
|
else
|
|
{
|
|
// Only this window should be open for safety reasons
|
|
WindowCloseAll();
|
|
ContextOpenWindow(WindowClass::EditorObjectSelection);
|
|
}
|
|
}
|
|
else if (argv[0] == "inventions_list" && InvalidArguments(&invalidTitle, !title))
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteLineError("Cannot open this window in multiplayer mode.");
|
|
}
|
|
else
|
|
{
|
|
ContextOpenWindow(WindowClass::EditorInventionList);
|
|
}
|
|
}
|
|
else if (argv[0] == "scenario_options" && InvalidArguments(&invalidTitle, !title))
|
|
{
|
|
ContextOpenWindow(WindowClass::EditorScenarioOptions);
|
|
}
|
|
else if (argv[0] == "objective_options" && InvalidArguments(&invalidTitle, !title))
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteLineError("Cannot open this window in multiplayer mode.");
|
|
}
|
|
else
|
|
{
|
|
ContextOpenWindow(WindowClass::EditorObjectiveOptions);
|
|
}
|
|
}
|
|
else if (argv[0] == "options")
|
|
{
|
|
ContextOpenWindow(WindowClass::Options);
|
|
}
|
|
else if (argv[0] == "themes")
|
|
{
|
|
ContextOpenWindow(WindowClass::Themes);
|
|
}
|
|
else if (invalidTitle)
|
|
{
|
|
console.WriteLineError("Cannot open this window in the title screen.");
|
|
}
|
|
else
|
|
{
|
|
console.WriteLineError("Invalid window.");
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandRemoveUnusedObjects(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
int32_t result = EditorRemoveUnusedObjects();
|
|
console.WriteFormatLine("%d unused object entries have been removed.", result);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandRemoveFloatingObjects(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
uint16_t result = RemoveFloatingEntities();
|
|
console.WriteFormatLine("Removed %d flying objects", result);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandRemoveParkFences(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
auto action = CheatSetAction(CheatType::RemoveParkFences);
|
|
GameActions::Execute(&action);
|
|
|
|
console.WriteFormatLine("Park fences have been removed.");
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandShowLimits(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
const auto& tileElements = GetTileElements();
|
|
const auto tileElementCount = tileElements.size();
|
|
|
|
int32_t rideCount = RideGetCount();
|
|
int32_t spriteCount = 0;
|
|
for (int32_t i = 0; i < static_cast<uint8_t>(EntityType::Count); ++i)
|
|
{
|
|
spriteCount += GetEntityListCount(EntityType(i));
|
|
}
|
|
|
|
auto bannerCount = GetNumBanners();
|
|
|
|
console.WriteFormatLine("Sprites: %d/%d", spriteCount, MAX_ENTITIES);
|
|
console.WriteFormatLine("Map Elements: %zu/%d", tileElementCount, MAX_TILE_ELEMENTS);
|
|
console.WriteFormatLine("Banners: %d/%zu", bannerCount, MAX_BANNERS);
|
|
console.WriteFormatLine("Rides: %d/%d", rideCount, OpenRCT2::Limits::MaxRidesInPark);
|
|
console.WriteFormatLine("Images: %zu/%zu", ImageListGetUsedCount(), ImageListGetMaximum());
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandForceDate([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
int32_t year = 0;
|
|
int32_t month = 0;
|
|
int32_t day = 0;
|
|
if (argv.size() < 1 || argv.size() > 3)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// All cases involve providing a year, so grab that first
|
|
year = atoi(argv[0].c_str());
|
|
if (year < 1 || year > kMaxYear)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// YYYY (no month provided, preserve existing month)
|
|
if (argv.size() == 1)
|
|
{
|
|
month = GetDate().GetMonth() + 1;
|
|
}
|
|
|
|
// YYYY MM or YYYY MM DD (month provided)
|
|
if (argv.size() >= 2)
|
|
{
|
|
month = atoi(argv[1].c_str());
|
|
month -= 2;
|
|
if (month < 1 || month > MONTH_COUNT)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// YYYY OR YYYY MM (no day provided, preserve existing day)
|
|
if (argv.size() <= 2)
|
|
{
|
|
day = std::clamp(GetDate().GetDay() + 1, 1, static_cast<int>(Date::GetDaysInMonth(month - 1)));
|
|
}
|
|
|
|
// YYYY MM DD (year, month, and day provided)
|
|
if (argv.size() == 3)
|
|
{
|
|
day = atoi(argv[2].c_str());
|
|
if (day < 1 || day > Date::GetDaysInMonth(month - 1))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
auto setDateAction = ParkSetDateAction(year - 1, month - 1, day - 1);
|
|
GameActions::Execute(&setDateAction);
|
|
WindowInvalidateByClass(WindowClass::BottomToolbar);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int32_t ConsoleCommandLoadPark([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (argv.size() < 1)
|
|
{
|
|
console.WriteLine("Parameters required <filename>");
|
|
return 0;
|
|
}
|
|
|
|
u8string savePath = {};
|
|
if (String::IndexOf(argv[0].c_str(), '/') == SIZE_MAX && String::IndexOf(argv[0].c_str(), '\\') == SIZE_MAX)
|
|
{
|
|
// no / or \ was included. File should be in save dir.
|
|
auto env = OpenRCT2::GetContext()->GetPlatformEnvironment();
|
|
auto directory = env->GetDirectoryPath(OpenRCT2::DIRBASE::USER, OpenRCT2::DIRID::SAVE);
|
|
savePath = Path::Combine(directory, argv[0]);
|
|
}
|
|
else
|
|
{
|
|
savePath = argv[0];
|
|
}
|
|
if (!String::EndsWith(savePath, ".sv6", true) && !String::EndsWith(savePath, ".sc6", true)
|
|
&& !String::EndsWith(savePath, ".park", true))
|
|
{
|
|
savePath += ".park";
|
|
}
|
|
if (OpenRCT2::GetContext()->LoadParkFromFile(savePath))
|
|
{
|
|
console.WriteFormatLine("Park %s was loaded successfully", savePath.c_str());
|
|
}
|
|
else
|
|
{
|
|
console.WriteFormatLine("Loading Park %s failed", savePath.c_str());
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int32_t ConsoleCommandSavePark([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (argv.size() < 1)
|
|
{
|
|
SaveGameCmd();
|
|
}
|
|
else
|
|
{
|
|
SaveGameCmd(argv[0].c_str());
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int32_t ConsoleCommandSay(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (NetworkGetMode() == NETWORK_MODE_NONE || NetworkGetStatus() != NETWORK_STATUS_CONNECTED
|
|
|| NetworkGetAuthstatus() != NetworkAuth::Ok)
|
|
{
|
|
console.WriteFormatLine("This command only works in multiplayer mode.");
|
|
return 0;
|
|
}
|
|
|
|
if (!argv.empty())
|
|
{
|
|
NetworkSendChat(argv[0].c_str());
|
|
return 1;
|
|
}
|
|
|
|
console.WriteFormatLine("Input your message");
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandReplayStartRecord(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteFormatLine("This command is currently not supported in multiplayer mode.");
|
|
return 0;
|
|
}
|
|
|
|
if (argv.size() < 1)
|
|
{
|
|
console.WriteFormatLine("Parameters required <replay_name> [<max_ticks = 0xFFFFFFFF>]");
|
|
return 0;
|
|
}
|
|
|
|
std::string name = argv[0];
|
|
|
|
if (!String::EndsWith(name, ".parkrep", true))
|
|
{
|
|
name += ".parkrep";
|
|
}
|
|
std::string outPath = OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath(
|
|
OpenRCT2::DIRBASE::USER, OpenRCT2::DIRID::REPLAY);
|
|
name = Path::Combine(outPath, name);
|
|
|
|
// If ticks are specified by user use that otherwise maximum ticks specified by const.
|
|
uint32_t maxTicks = OpenRCT2::k_MaxReplayTicks;
|
|
if (argv.size() >= 2)
|
|
{
|
|
maxTicks = atol(argv[1].c_str());
|
|
}
|
|
|
|
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
|
|
if (replayManager->StartRecording(name, maxTicks))
|
|
{
|
|
OpenRCT2::ReplayRecordInfo info;
|
|
replayManager->GetCurrentReplayInfo(info);
|
|
|
|
const char* logFmt = "Replay recording started: (%s) %s";
|
|
console.WriteFormatLine(logFmt, info.Name.c_str(), info.FilePath.c_str());
|
|
Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str());
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandReplayStopRecord(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteFormatLine("This command is currently not supported in multiplayer mode.");
|
|
return 0;
|
|
}
|
|
|
|
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
|
|
if (!replayManager->IsRecording() && !replayManager->IsNormalising())
|
|
{
|
|
console.WriteFormatLine("Replay currently not recording");
|
|
return 0;
|
|
}
|
|
|
|
OpenRCT2::ReplayRecordInfo info;
|
|
replayManager->GetCurrentReplayInfo(info);
|
|
|
|
if (replayManager->StopRecording())
|
|
{
|
|
const char* logFmt = "Replay recording stopped: (%s) %s\n"
|
|
" Ticks: %u\n"
|
|
" Commands: %u\n"
|
|
" Checksums: %u";
|
|
|
|
console.WriteFormatLine(
|
|
logFmt, info.Name.c_str(), info.FilePath.c_str(), info.Ticks, info.NumCommands, info.NumChecksums);
|
|
Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str(), info.Ticks, info.NumCommands, info.NumChecksums);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandReplayStart(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteFormatLine("This command is currently not supported in multiplayer mode.");
|
|
return 0;
|
|
}
|
|
|
|
if (argv.size() < 1)
|
|
{
|
|
console.WriteFormatLine("Parameters required <replay_name>");
|
|
return 0;
|
|
}
|
|
|
|
std::string name = argv[0];
|
|
|
|
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
|
|
if (replayManager->StartPlayback(name))
|
|
{
|
|
OpenRCT2::ReplayRecordInfo info;
|
|
replayManager->GetCurrentReplayInfo(info);
|
|
|
|
std::time_t ts = info.TimeRecorded;
|
|
|
|
char recordingDate[128] = {};
|
|
std::strftime(recordingDate, sizeof(recordingDate), "%c", std::localtime(&ts));
|
|
|
|
const char* logFmt = "Replay playback started: %s\n"
|
|
" Date Recorded: %s\n"
|
|
" Ticks: %u\n"
|
|
" Commands: %u\n"
|
|
" Checksums: %u";
|
|
|
|
console.WriteFormatLine(logFmt, info.FilePath.c_str(), recordingDate, info.Ticks, info.NumCommands, info.NumChecksums);
|
|
Console::WriteLine(logFmt, info.FilePath.c_str(), recordingDate, info.Ticks, info.NumCommands, info.NumChecksums);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandReplayStop(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteFormatLine("This command is currently not supported in multiplayer mode.");
|
|
return 0;
|
|
}
|
|
|
|
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
|
|
if (replayManager->StopPlayback())
|
|
{
|
|
console.WriteFormatLine("Stopped replay");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandReplayNormalise(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (NetworkGetMode() != NETWORK_MODE_NONE)
|
|
{
|
|
console.WriteFormatLine("This command is currently not supported in multiplayer mode.");
|
|
return 0;
|
|
}
|
|
|
|
if (argv.size() < 2)
|
|
{
|
|
console.WriteFormatLine("Parameters required <replay_input> <replay_output>");
|
|
return 0;
|
|
}
|
|
|
|
std::string inputFile = argv[0];
|
|
std::string outputFile = argv[1];
|
|
|
|
if (!String::EndsWith(outputFile, ".parkrep", true))
|
|
{
|
|
outputFile += ".parkrep";
|
|
}
|
|
std::string outPath = OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath(
|
|
OpenRCT2::DIRBASE::USER, OpenRCT2::DIRID::REPLAY);
|
|
outputFile = Path::Combine(outPath, outputFile);
|
|
|
|
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
|
|
if (replayManager->NormaliseReplay(inputFile, outputFile))
|
|
{
|
|
console.WriteFormatLine("Stopped replay");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandMpDesync(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
int32_t desyncType = 0;
|
|
if (argv.size() >= 1)
|
|
{
|
|
desyncType = atoi(argv[0].c_str());
|
|
}
|
|
|
|
std::vector<Guest*> guests;
|
|
|
|
for (auto* guest : EntityList<Guest>())
|
|
{
|
|
guests.push_back(guest);
|
|
}
|
|
|
|
switch (desyncType)
|
|
{
|
|
case 0: // Guest t-shirts.
|
|
{
|
|
if (guests.empty())
|
|
{
|
|
console.WriteFormatLine("No guests");
|
|
}
|
|
else
|
|
{
|
|
auto* guest = guests[0];
|
|
if (guests.size() > 1)
|
|
guest = guests[UtilRand() % guests.size() - 1];
|
|
guest->TshirtColour = UtilRand() & 0xFF;
|
|
guest->Invalidate();
|
|
}
|
|
break;
|
|
}
|
|
case 1: // Remove random guest.
|
|
{
|
|
if (guests.empty())
|
|
{
|
|
console.WriteFormatLine("No guest removed");
|
|
}
|
|
else
|
|
{
|
|
auto* guest = guests[0];
|
|
if (guests.size() > 1)
|
|
guest = guests[UtilRand() % guests.size() - 1];
|
|
guest->Remove();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4702) // unreachable code
|
|
static int32_t ConsoleCommandAbort([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
std::abort();
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandDereference([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wnull-dereference"
|
|
// Dereference a nullptr to induce a crash to be caught by crash handler, on supported platforms
|
|
uint8_t* myptr = nullptr;
|
|
*myptr = 42;
|
|
return 0;
|
|
#pragma GCC diagnostic pop
|
|
}
|
|
|
|
static int32_t ConsoleCommandTerminate([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
std::terminate();
|
|
return 0;
|
|
}
|
|
#pragma warning(pop)
|
|
|
|
static int32_t ConsoleCommandAssert([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
Guard::Assert(false, "%s", argv[0].c_str());
|
|
else
|
|
Guard::Assert(false);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandAddNewsItem([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (argv.size() < 2)
|
|
{
|
|
console.WriteLineWarning("Too few arguments");
|
|
static_assert(News::ItemTypeCount == 11, "News::ItemType::Count changed, update console command!");
|
|
console.WriteLine("add_news_item <type> <message> [assoc]");
|
|
console.WriteLine("type is one of:");
|
|
console.WriteLine(" 0 (News::ItemType::Null)");
|
|
console.WriteLine(" 1 (News::ItemType::Ride)");
|
|
console.WriteLine(" 2 (News::ItemType::PeepOnRide)");
|
|
console.WriteLine(" 3 (News::ItemType::Peep)");
|
|
console.WriteLine(" 4 (News::ItemType::Money)");
|
|
console.WriteLine(" 5 (News::ItemType::Blank)");
|
|
console.WriteLine(" 6 (News::ItemType::Research)");
|
|
console.WriteLine(" 7 (News::ItemType::Peeps)");
|
|
console.WriteLine(" 8 (News::ItemType::Award)");
|
|
console.WriteLine(" 9 (News::ItemType::Graph)");
|
|
console.WriteLine(" 10 (News::ItemType::Campaign)");
|
|
console.WriteLine("message is the message to display, wrapped in quotes for multiple words");
|
|
console.WriteLine("assoc is the associated id of ride/peep/tile/etc. If the selected ItemType doesn't need an assoc "
|
|
"(Null, Money, Award, Graph), you can leave this field blank");
|
|
return 1;
|
|
}
|
|
|
|
auto type = atoi(argv[0].c_str());
|
|
auto msg = argv[1].c_str();
|
|
auto assoc = 0;
|
|
|
|
News::ItemType itemType = static_cast<News::ItemType>(type);
|
|
|
|
if (argv.size() == 3) // 3 arguments passed, set assoc
|
|
{
|
|
assoc = atoi(argv[2].c_str());
|
|
}
|
|
else
|
|
{
|
|
if (News::CheckIfItemRequiresAssoc(itemType))
|
|
{
|
|
console.WriteLine("Selected ItemType requires an assoc");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
News::AddItemToQueue(itemType, msg, assoc);
|
|
console.WriteLine("Successfully added News Item");
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandProfilerReset(
|
|
[[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
OpenRCT2::Profiling::ResetData();
|
|
return 0;
|
|
}
|
|
static int32_t ConsoleCommandProfilerStart(
|
|
[[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (!OpenRCT2::Profiling::IsEnabled())
|
|
console.WriteLine("Started profiler");
|
|
OpenRCT2::Profiling::Enable();
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandProfilerExportCSV(
|
|
[[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (argv.size() < 1)
|
|
{
|
|
console.WriteLineError("Missing argument: <file path>");
|
|
return 1;
|
|
}
|
|
|
|
const auto& csvFilePath = argv[0];
|
|
if (!OpenRCT2::Profiling::ExportCSV(csvFilePath))
|
|
{
|
|
console.WriteFormatLine("Unable to export CSV file to %s", csvFilePath.c_str());
|
|
return 1;
|
|
}
|
|
|
|
console.WriteFormatLine("Wrote file CSV file: \"%s\"", csvFilePath.c_str());
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandProfilerStop(
|
|
[[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
if (OpenRCT2::Profiling::IsEnabled())
|
|
console.WriteLine("Stopped profiler");
|
|
OpenRCT2::Profiling::Disable();
|
|
|
|
// Export to CSV if argument is provided.
|
|
if (argv.size() >= 1)
|
|
{
|
|
return ConsoleCommandProfilerExportCSV(console, argv);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleSpawnBalloon(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (argv.size() < 3)
|
|
{
|
|
console.WriteLineError("Need arguments: <x> <y> <z> <colour>");
|
|
return 1;
|
|
}
|
|
int32_t x = COORDS_XY_STEP * atof(argv[0].c_str());
|
|
int32_t y = COORDS_XY_STEP * atof(argv[1].c_str());
|
|
int32_t z = COORDS_Z_STEP * atof(argv[2].c_str());
|
|
int32_t col = 28;
|
|
if (argv.size() > 3)
|
|
col = atoi(argv[3].c_str());
|
|
Balloon::Create({ x, y, z }, col, false);
|
|
return 0;
|
|
}
|
|
|
|
using console_command_func = int32_t (*)(InteractiveConsole& console, const arguments_t& argv);
|
|
struct ConsoleCommand
|
|
{
|
|
const utf8* command;
|
|
console_command_func func;
|
|
const utf8* help;
|
|
const utf8* usage;
|
|
};
|
|
|
|
// clang-format off
|
|
static constexpr const utf8* console_variable_table[] = {
|
|
"park_rating",
|
|
"park_value",
|
|
"company_value",
|
|
"money",
|
|
"scenario_initial_cash",
|
|
"current_loan",
|
|
"max_loan",
|
|
"guest_initial_cash",
|
|
"guest_initial_happiness",
|
|
"guest_initial_hunger",
|
|
"guest_initial_thirst",
|
|
"guest_prefer_less_intense_rides",
|
|
"guest_prefer_more_intense_rides",
|
|
"forbid_marketing_campaigns",
|
|
"forbid_landscape_changes",
|
|
"forbid_tree_removal",
|
|
"forbid_high_construction",
|
|
"pay_for_rides",
|
|
"no_money",
|
|
"difficult_park_rating",
|
|
"difficult_guest_generation",
|
|
"land_rights_cost",
|
|
"construction_rights_cost",
|
|
"park_open",
|
|
"climate",
|
|
"game_speed",
|
|
"console_small_font",
|
|
"location",
|
|
"window_scale",
|
|
"window_limit",
|
|
"render_weather_effects",
|
|
"render_weather_gloom",
|
|
"cheat_sandbox_mode",
|
|
"cheat_disable_clearance_checks",
|
|
"cheat_disable_support_limits",
|
|
"current_rotation",
|
|
};
|
|
|
|
static constexpr const utf8* console_window_table[] = {
|
|
"object_selection",
|
|
"inventions_list",
|
|
"scenario_options",
|
|
"objective_options",
|
|
"options",
|
|
"themes",
|
|
"title_sequences",
|
|
};
|
|
// clang-format on
|
|
|
|
static constexpr ConsoleCommand console_command_table[] = {
|
|
{ "abort", ConsoleCommandAbort, "Calls std::abort(), for testing purposes only.", "abort" },
|
|
{ "add_news_item", ConsoleCommandAddNewsItem, "Inserts a news item", "add_news_item [<type> <message> <assoc>]" },
|
|
{ "assert", ConsoleCommandAssert, "Triggers assertion failure, for testing purposes only", "assert" },
|
|
{ "clear", ConsoleCommandClear, "Clears the console.", "clear" },
|
|
{ "close", ConsoleCommandClose, "Closes the console.", "close" },
|
|
{ "date", ConsoleCommandForceDate, "Sets the date to a given date.", "Format <year>[ <month>[ <day>]]." },
|
|
{ "dereference", ConsoleCommandDereference, "Dereferences a nullptr, for testing purposes only", "dereference" },
|
|
{ "echo", ConsoleCommandEcho, "Echoes the text to the console.", "echo <text>" },
|
|
{ "exit", ConsoleCommandClose, "Closes the console.", "exit" },
|
|
{ "get", ConsoleCommandGet, "Gets the value of the specified variable.", "get <variable>" },
|
|
{ "help", ConsoleCommandHelp, "Lists commands or info about a command.", "help [command]" },
|
|
{ "hide", ConsoleCommandHide, "Hides the console.", "hide" },
|
|
{ "load_object", ConsoleCommandLoadObject,
|
|
"Loads the object file into the scenario.\n"
|
|
"Loading a scenery group will not load its associated objects.\n"
|
|
"This is a safer method opposed to \"open object_selection\".",
|
|
"load_object <objectfilenodat>" },
|
|
{ "load_park", ConsoleCommandLoadPark, "Load park from save directory or by absolute path", "load_park <filename>" },
|
|
{ "object_count", ConsoleCommandCountObjects, "Shows the number of objects of each type in the scenario.", "object_count" },
|
|
{ "open", ConsoleCommandOpen, "Opens the window with the give name.", "open <window>." },
|
|
{ "quit", ConsoleCommandClose, "Closes the console.", "quit" },
|
|
{ "remove_park_fences", ConsoleCommandRemoveParkFences, "Removes all park fences from the surface", "remove_park_fences" },
|
|
{ "remove_unused_objects", ConsoleCommandRemoveUnusedObjects, "Removes all the unused objects from the object selection.",
|
|
"remove_unused_objects" },
|
|
{ "remove_floating_objects", ConsoleCommandRemoveFloatingObjects, "Removes floating objects", "remove_floating_objects" },
|
|
{ "rides", ConsoleCommandRides, "Ride management.", "rides <subcommand>" },
|
|
{ "save_park", ConsoleCommandSavePark, "Save current state of park. If no name specified default path will be used.",
|
|
"save_park [name]" },
|
|
{ "say", ConsoleCommandSay, "Say to other players.", "say <message>" },
|
|
{ "set", ConsoleCommandSet, "Sets the variable to the specified value.", "set <variable> <value>" },
|
|
{ "show_limits", ConsoleCommandShowLimits, "Shows the map data counts and limits.", "show_limits" },
|
|
{ "spawn_balloon", ConsoleSpawnBalloon, "Spawns a balloon.", "spawn_balloon <x> <y> <z> <colour>" },
|
|
{ "staff", ConsoleCommandStaff, "Staff management.", "staff <subcommand>" },
|
|
{ "terminate", ConsoleCommandTerminate, "Calls std::terminate(), for testing purposes only.", "terminate" },
|
|
{ "variables", ConsoleCommandVariables, "Lists all the variables that can be used with get and sometimes set.",
|
|
"variables" },
|
|
{ "windows", ConsoleCommandWindows, "Lists all the windows that can be opened.", "windows" },
|
|
{ "replay_startrecord", ConsoleCommandReplayStartRecord, "Starts recording a new replay.",
|
|
"replay_startrecord <name> [max_ticks]" },
|
|
{ "replay_stoprecord", ConsoleCommandReplayStopRecord, "Stops recording a new replay.", "replay_stoprecord" },
|
|
{ "replay_start", ConsoleCommandReplayStart, "Starts a replay", "replay_start <name>" },
|
|
{ "replay_stop", ConsoleCommandReplayStop, "Stops the replay", "replay_stop" },
|
|
{ "replay_normalise", ConsoleCommandReplayNormalise, "Normalises the replay to remove all gaps",
|
|
"replay_normalise <input file> <output file>" },
|
|
{ "mp_desync", ConsoleCommandMpDesync, "Forces a multiplayer desync",
|
|
"ConsoleCommandMpDesync [desync_type, 0 = Random t-shirt color on random guest, 1 = Remove random guest ]" },
|
|
{ "profiler_reset", ConsoleCommandProfilerReset, "Resets the profiler data.", "profiler_reset" },
|
|
{ "profiler_start", ConsoleCommandProfilerStart, "Starts the profiler.", "profiler_start" },
|
|
{ "profiler_stop", ConsoleCommandProfilerStop, "Stops the profiler.", "profiler_stop [<output file>]" },
|
|
{ "profiler_exportcsv", ConsoleCommandProfilerExportCSV, "Exports the current profiler data.",
|
|
"profiler_exportcsv <output file>" },
|
|
};
|
|
|
|
static int32_t ConsoleCommandWindows(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
for (auto s : console_window_table)
|
|
{
|
|
console.WriteLine(s);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandVariables(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
|
{
|
|
for (auto s : console_variable_table)
|
|
{
|
|
console.WriteLine(s);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ConsoleCommandHelp(InteractiveConsole& console, const arguments_t& argv)
|
|
{
|
|
if (!argv.empty())
|
|
{
|
|
for (const auto& c : console_command_table)
|
|
{
|
|
if (argv[0] == c.command)
|
|
{
|
|
console.WriteLine(c.help);
|
|
console.WriteFormatLine("\nUsage: %s", c.usage);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ConsoleWriteAllCommands(console);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ConsoleWriteAllCommands(InteractiveConsole& console)
|
|
{
|
|
for (const auto& c : console_command_table)
|
|
{
|
|
console.WriteLine(c.command);
|
|
}
|
|
}
|
|
|
|
static bool InvalidArguments(bool* invalid, bool arguments)
|
|
{
|
|
if (!arguments)
|
|
{
|
|
*invalid = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void InteractiveConsole::Execute(const std::string& s)
|
|
{
|
|
arguments_t argv;
|
|
argv.reserve(8);
|
|
|
|
const utf8* start = s.c_str();
|
|
const utf8* end;
|
|
bool inQuotes = false;
|
|
do
|
|
{
|
|
while (*start == ' ')
|
|
start++;
|
|
|
|
if (*start == '"')
|
|
{
|
|
inQuotes = true;
|
|
start++;
|
|
}
|
|
else
|
|
{
|
|
inQuotes = false;
|
|
}
|
|
|
|
end = start;
|
|
while (*end != 0)
|
|
{
|
|
if (*end == ' ' && !inQuotes)
|
|
break;
|
|
if (*end == '"' && inQuotes)
|
|
break;
|
|
end++;
|
|
}
|
|
size_t length = end - start;
|
|
|
|
if (length > 0)
|
|
{
|
|
argv.emplace_back(start, length);
|
|
}
|
|
|
|
start = end;
|
|
} while (*end != 0);
|
|
|
|
if (argv.empty())
|
|
return;
|
|
|
|
bool validCommand = false;
|
|
|
|
for (const auto& c : console_command_table)
|
|
{
|
|
if (argv[0] == c.command)
|
|
{
|
|
argv.erase(argv.begin());
|
|
c.func(*this, argv);
|
|
validCommand = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!validCommand)
|
|
{
|
|
WriteLineError("Unknown command. Type help to list available commands.");
|
|
}
|
|
}
|
|
|
|
void InteractiveConsole::WriteLine(const std::string& s)
|
|
{
|
|
WriteLine(s, FormatToken::ColourWindow2);
|
|
}
|
|
|
|
void InteractiveConsole::WriteLineError(const std::string& s)
|
|
{
|
|
WriteLine(s, FormatToken::ColourRed);
|
|
}
|
|
|
|
void InteractiveConsole::WriteLineWarning(const std::string& s)
|
|
{
|
|
WriteLine(s, FormatToken::ColourYellow);
|
|
}
|
|
|
|
void InteractiveConsole::WriteFormatLine(const char* format, ...)
|
|
{
|
|
va_list list;
|
|
va_start(list, format);
|
|
auto buffer = String::Format_VA(format, list);
|
|
va_end(list);
|
|
WriteLine(buffer);
|
|
}
|