OpenRCT2/src/openrct2/Game.cpp

1716 lines
51 KiB
C++

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 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, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "audio/audio.h"
#include "Cheats.h"
#include "config/Config.h"
#include "Context.h"
#include "core/Math.hpp"
#include "core/Util.hpp"
#include "Editor.h"
#include "FileClassifier.h"
#include "Game.h"
#include "Input.h"
#include "interface/Screenshot.h"
#include "interface/viewport.h"
#include "interface/widget.h"
#include "interface/window.h"
#include "localisation/Localisation.h"
#include "management/Finance.h"
#include "management/Marketing.h"
#include "management/NewsItem.h"
#include "management/Research.h"
#include "network/network.h"
#include "object/Object.h"
#include "OpenRCT2.h"
#include "ParkImporter.h"
#include "peep/Peep.h"
#include "peep/Staff.h"
#include "platform/platform.h"
#include "rct1.h"
#include "ride/Ride.h"
#include "ride/ride_ratings.h"
#include "ride/Track.h"
#include "ride/TrackDesign.h"
#include "ride/Vehicle.h"
#include "scenario/Scenario.h"
#include "title/TitleScreen.h"
#include "title/TitleSequencePlayer.h"
#include "util/SawyerCoding.h"
#include "util/Util.h"
#include "windows/Intent.h"
#include "world/Banner.h"
#include "world/Climate.h"
#include "world/Entrance.h"
#include "world/footpath.h"
#include "world/Map.h"
#include "world/map_animation.h"
#include "world/Park.h"
#include "world/scenery.h"
#include "world/sprite.h"
#include "world/water.h"
#include "object/ObjectList.h"
#define NUMBER_OF_AUTOSAVES_TO_KEEP 9
uint16 gTicksSinceLastUpdate;
uint8 gGamePaused = 0;
sint32 gGameSpeed = 1;
float gDayNightCycle = 0;
bool gInUpdateCode = false;
bool gInMapInitCode = false;
sint32 gGameCommandNestLevel;
bool gGameCommandIsNetworked;
char gCurrentLoadedPath[MAX_PATH];
bool gLoadKeepWindowsOpen = false;
uint8 gUnk13CA740;
uint8 gUnk141F568;
uint32 gCurrentTicks;
GAME_COMMAND_CALLBACK_POINTER * game_command_callback = nullptr;
static GAME_COMMAND_CALLBACK_POINTER * const game_command_callback_table[] = {
nullptr,
nullptr,
game_command_callback_ride_construct_placed_front,
game_command_callback_ride_construct_placed_back,
game_command_callback_ride_remove_track_piece,
game_command_callback_place_banner,
game_command_callback_place_ride_entrance_or_exit,
game_command_callback_hire_new_staff_member,
game_command_callback_pickup_guest,
game_command_callback_pickup_staff,
game_command_callback_marketing_start_campaign,
};
sint32 game_command_playerid = -1;
rct_string_id gGameCommandErrorTitle;
rct_string_id gGameCommandErrorText;
uint8 gErrorType;
rct_string_id gErrorStringId;
sint32 game_command_callback_get_index(GAME_COMMAND_CALLBACK_POINTER * callback)
{
for (uint32 i = 0; i < Util::CountOf(game_command_callback_table); i++)
{
if (game_command_callback_table[i] == callback)
{
return i;
}
}
return 0;
}
GAME_COMMAND_CALLBACK_POINTER * game_command_callback_get_callback(uint32 index)
{
if (index < Util::CountOf(game_command_callback_table))
{
return game_command_callback_table[index];
}
return nullptr;
}
void game_increase_game_speed()
{
gGameSpeed = Math::Min(gConfigGeneral.debugging_tools ? 5 : 4, gGameSpeed + 1);
if (gGameSpeed == 5)
gGameSpeed = 8;
window_invalidate_by_class(WC_TOP_TOOLBAR);
}
void game_reduce_game_speed()
{
gGameSpeed = Math::Max(1, gGameSpeed - 1);
if (gGameSpeed == 7)
gGameSpeed = 4;
window_invalidate_by_class(WC_TOP_TOOLBAR);
}
/**
*
* rct2: 0x0066B5C0 (part of 0x0066B3E8)
*/
void game_create_windows()
{
context_open_window(WC_MAIN_WINDOW);
context_open_window(WC_TOP_TOOLBAR);
context_open_window(WC_BOTTOM_TOOLBAR);
window_resize_gui(context_get_width(), context_get_height());
}
enum
{
SPR_GAME_PALETTE_DEFAULT = 1532,
SPR_GAME_PALETTE_WATER = 1533,
SPR_GAME_PALETTE_WATER_DARKER_1 = 1534,
SPR_GAME_PALETTE_WATER_DARKER_2 = 1535,
SPR_GAME_PALETTE_3 = 1536,
SPR_GAME_PALETTE_3_DARKER_1 = 1537,
SPR_GAME_PALETTE_3_DARKER_2 = 1538,
SPR_GAME_PALETTE_4 = 1539,
SPR_GAME_PALETTE_4_DARKER_1 = 1540,
SPR_GAME_PALETTE_4_DARKER_2 = 1541,
};
/**
*
* rct2: 0x006838BD
*/
void update_palette_effects()
{
rct_water_type * water_type = (rct_water_type *) object_entry_groups[OBJECT_TYPE_WATER].chunks[0];
if (gClimateLightningFlash == 1)
{
// Change palette to lighter colour during lightning
sint32 palette = SPR_GAME_PALETTE_DEFAULT;
if (water_type != nullptr)
{
palette = water_type->image_id;
}
const rct_g1_element * g1 = gfx_get_g1_element(palette);
if (g1 != nullptr)
{
sint32 xoffset = g1->x_offset;
xoffset = xoffset * 4;
uint8 * paletteOffset = gGamePalette + xoffset;
for (sint32 i = 0; i < g1->width; i++)
{
paletteOffset[(i * 4) + 0] = -((0xFF - g1->offset[(i * 3) + 0]) / 2) - 1;
paletteOffset[(i * 4) + 1] = -((0xFF - g1->offset[(i * 3) + 1]) / 2) - 1;
paletteOffset[(i * 4) + 2] = -((0xFF - g1->offset[(i * 3) + 2]) / 2) - 1;
}
platform_update_palette(gGamePalette, 10, 236);
}
gClimateLightningFlash++;
}
else
{
if (gClimateLightningFlash == 2)
{
// Change palette back to normal after lightning
sint32 palette = SPR_GAME_PALETTE_DEFAULT;
if (water_type != nullptr)
{
palette = water_type->image_id;
}
const rct_g1_element * g1 = gfx_get_g1_element(palette);
if (g1 != nullptr)
{
sint32 xoffset = g1->x_offset;
xoffset = xoffset * 4;
uint8 * paletteOffset = gGamePalette + xoffset;
for (sint32 i = 0; i < g1->width; i++)
{
paletteOffset[(i * 4) + 0] = g1->offset[(i * 3) + 0];
paletteOffset[(i * 4) + 1] = g1->offset[(i * 3) + 1];
paletteOffset[(i * 4) + 2] = g1->offset[(i * 3) + 2];
}
}
}
// Animate the water/lava/chain movement palette
uint32 shade = 0;
if (gConfigGeneral.render_weather_gloom)
{
uint8 gloom = gClimateCurrentWeatherGloom;
if (gloom != 0)
{
FILTER_PALETTE_ID weatherColour = ClimateWeatherGloomColours[gloom];
shade = 1;
if (weatherColour != PALETTE_DARKEN_1)
{
shade = 2;
}
}
}
uint32 j = gPaletteEffectFrame;
j = (((uint16) ((~j / 2) * 128) * 15) >> 16);
uint32 waterId = SPR_GAME_PALETTE_WATER;
if (water_type != nullptr)
{
waterId = water_type->palette_index_1;
}
const rct_g1_element * g1 = gfx_get_g1_element(shade + waterId);
if (g1 != nullptr)
{
uint8 * vs = &g1->offset[j * 3];
uint8 * vd = &gGamePalette[230 * 4];
sint32 n = 5;
for (sint32 i = 0; i < n; i++)
{
vd[0] = vs[0];
vd[1] = vs[1];
vd[2] = vs[2];
vs += 9;
if (vs >= &g1->offset[9 * n])
{
vs -= 9 * n;
}
vd += 4;
}
}
waterId = SPR_GAME_PALETTE_3;
if (water_type != nullptr)
{
waterId = water_type->palette_index_2;
}
g1 = gfx_get_g1_element(shade + waterId);
if (g1 != nullptr)
{
uint8 * vs = &g1->offset[j * 3];
uint8 * vd = &gGamePalette[235 * 4];
sint32 n = 5;
for (sint32 i = 0; i < n; i++)
{
vd[0] = vs[0];
vd[1] = vs[1];
vd[2] = vs[2];
vs += 9;
if (vs >= &g1->offset[9 * n])
{
vs -= 9 * n;
}
vd += 4;
}
}
j = ((uint16) (gPaletteEffectFrame * -960) * 3) >> 16;
waterId = SPR_GAME_PALETTE_4;
g1 = gfx_get_g1_element(shade + waterId);
if (g1 != nullptr)
{
uint8 * vs = &g1->offset[j * 3];
uint8 * vd = &gGamePalette[243 * 4];
sint32 n = 3;
for (sint32 i = 0; i < n; i++)
{
vd[0] = vs[0];
vd[1] = vs[1];
vd[2] = vs[2];
vs += 3;
if (vs >= &g1->offset[3 * n])
{
vs -= 3 * n;
}
vd += 4;
}
}
platform_update_palette(gGamePalette, 230, 16);
if (gClimateLightningFlash == 2)
{
platform_update_palette(gGamePalette, 10, 236);
gClimateLightningFlash = 0;
}
}
}
void game_update()
{
gInUpdateCode = true;
uint32 numUpdates;
// 0x006E3AEC // screen_game_process_mouse_input();
screenshot_check();
game_handle_keyboard_input();
if (game_is_not_paused() && gPreviewingTitleSequenceInGame)
{
title_sequence_player_update((ITitleSequencePlayer *) title_get_sequence_player());
}
// Determine how many times we need to update the game
if (gGameSpeed > 1)
{
numUpdates = 1 << (gGameSpeed - 1);
}
else
{
numUpdates = gTicksSinceLastUpdate / GAME_UPDATE_TIME_MS;
numUpdates = Math::Clamp(1u, numUpdates, (uint32) GAME_MAX_UPDATES);
}
if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED && network_get_authstatus() == NETWORK_AUTH_OK)
{
if (network_get_server_tick() - gCurrentTicks >= 10)
{
// Make sure client doesn't fall behind the server too much
numUpdates += 10;
}
}
if (game_is_paused())
{
numUpdates = 0;
// Update the animation list. Note this does not
// increment the map animation.
map_animation_invalidate_all();
// Special case because we set numUpdates to 0, otherwise in game_logic_update.
network_update();
network_process_game_commands();
}
// Update the game one or more times
for (uint32 i = 0; i < numUpdates; i++)
{
game_logic_update();
if (gGameSpeed > 1)
continue;
if (input_get_state() == INPUT_STATE_RESET ||
input_get_state() == INPUT_STATE_NORMAL
)
{
if (input_test_flag(INPUT_FLAG_VIEWPORT_SCROLLING))
{
input_set_flag(INPUT_FLAG_VIEWPORT_SCROLLING, false);
break;
}
}
else
{
break;
}
}
if (!gOpenRCT2Headless)
{
input_set_flag(INPUT_FLAG_VIEWPORT_SCROLLING, false);
// the flickering frequency is reduced by 4, compared to the original
// it was done due to inability to reproduce original frequency
// and decision that the original one looks too fast
if (gCurrentTicks % 4 == 0)
gWindowMapFlashingFlags ^= (1 << 15);
// Handle guest map flashing
gWindowMapFlashingFlags &= ~(1 << 1);
if (gWindowMapFlashingFlags & (1 << 0))
gWindowMapFlashingFlags |= (1 << 1);
gWindowMapFlashingFlags &= ~(1 << 0);
// Handle staff map flashing
gWindowMapFlashingFlags &= ~(1 << 3);
if (gWindowMapFlashingFlags & (1 << 2))
gWindowMapFlashingFlags |= (1 << 3);
gWindowMapFlashingFlags &= ~(1 << 2);
context_update_map_tooltip();
// Input
gUnk141F568 = gUnk13CA740;
context_handle_input();
}
// Always perform autosave check, even when paused
if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) &&
!(gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) &&
!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
)
{
scenario_autosave_check();
}
window_dispatch_update_all();
gGameCommandNestLevel = 0;
gInUpdateCode = false;
}
void game_logic_update()
{
gScreenAge++;
if (gScreenAge == 0)
gScreenAge--;
network_update();
if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED && network_get_authstatus() == NETWORK_AUTH_OK)
{
// Can't be in sync with server, round trips won't work if we are at same level.
if (gCurrentTicks >= network_get_server_tick())
{
// Don't run past the server
return;
}
}
if (network_get_mode() == NETWORK_MODE_SERVER)
{
// Send current tick out.
network_send_tick();
}
else if (network_get_mode() == NETWORK_MODE_CLIENT)
{
// Check desync.
network_check_desynchronization();
}
sub_68B089();
scenario_update();
climate_update();
map_update_tiles();
// Temporarily remove provisional paths to prevent peep from interacting with them
map_remove_provisional_elements();
map_update_path_wide_flags();
peep_update_all();
map_restore_provisional_elements();
vehicle_update_all();
sprite_misc_update_all();
ride_update_all();
park_update();
research_update();
ride_ratings_update_all();
ride_measurements_update();
news_item_update_current();
map_animation_invalidate_all();
vehicle_sounds_update();
peep_update_crowd_noise();
climate_update_sound();
editor_open_windows_for_current_step();
// Update windows
//window_dispatch_update_all();
if (gErrorType != ERROR_TYPE_NONE)
{
rct_string_id title_text = STR_UNABLE_TO_LOAD_FILE;
rct_string_id body_text = gErrorStringId;
if (gErrorType == ERROR_TYPE_GENERIC)
{
title_text = gErrorStringId;
body_text = 0xFFFF;
}
gErrorType = ERROR_TYPE_NONE;
context_show_error(title_text, body_text);
}
// Start autosave timer after update
if (gLastAutoSaveUpdate == AUTOSAVE_PAUSE)
{
gLastAutoSaveUpdate = platform_get_ticks();
}
// Separated out processing commands in network_update which could call scenario_rand where gInUpdateCode is false.
// All commands that are received are first queued and then executed where gInUpdateCode is set to true.
network_process_game_commands();
network_flush();
gCurrentTicks++;
gScenarioTicks++;
gSavedAge++;
}
/**
*
* rct2: 0x0069C62C
*
* @param cost (ebp)
*/
static sint32 game_check_affordability(sint32 cost)
{
if (cost <= 0)
return cost;
if (gUnk141F568 & 0xF0)
return cost;
if (cost <= (sint32) gCash)
return cost;
set_format_arg(0, uint32, cost);
gGameCommandErrorText = STR_NOT_ENOUGH_CASH_REQUIRES;
return MONEY32_UNDEFINED;
}
/**
*
* rct2: 0x006677F2
*
* @param flags (ebx)
* @param command (esi)
*/
sint32 game_do_command(sint32 eax, sint32 ebx, sint32 ecx, sint32 edx, sint32 esi, sint32 edi, sint32 ebp)
{
return game_do_command_p(esi, &eax, &ebx, &ecx, &edx, &esi, &edi, &ebp);
}
/**
*
* rct2: 0x006677F2 with pointers as arguments
*
* @param flags (ebx)
* @param command (esi)
*/
sint32 game_do_command_p(uint32 command, sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi, sint32 * ebp)
{
sint32 cost, flags;
sint32 original_ebx, original_edx, original_esi, original_edi, original_ebp;
*esi = command;
original_ebx = *ebx;
original_edx = *edx;
original_esi = *esi;
original_edi = *edi;
original_ebp = *ebp;
if (command >= Util::CountOf(new_game_command_table))
{
return MONEY32_UNDEFINED;
}
flags = *ebx;
if (gGameCommandNestLevel == 0)
{
gGameCommandErrorText = STR_NONE;
gGameCommandIsNetworked = (flags & GAME_COMMAND_FLAG_NETWORKED) != 0;
}
// Increment nest count
gGameCommandNestLevel++;
// Remove ghost scenery so it doesn't interfere with incoming network command
if ((flags & GAME_COMMAND_FLAG_NETWORKED) && !(flags & GAME_COMMAND_FLAG_GHOST) &&
(command == GAME_COMMAND_PLACE_WALL ||
command == GAME_COMMAND_PLACE_SCENERY ||
command == GAME_COMMAND_PLACE_LARGE_SCENERY ||
command == GAME_COMMAND_PLACE_BANNER ||
command == GAME_COMMAND_PLACE_PATH))
{
scenery_remove_ghost_tool_placement();
}
if (game_command_playerid == -1)
{
game_command_playerid = network_get_current_player_id();
}
// Log certain commands if we are in multiplayer and logging is enabled
bool serverLog = (network_get_mode() == NETWORK_MODE_SERVER) && gGameCommandNestLevel == 1 && gConfigNetwork.log_server_actions;
bool clientLog = (network_get_mode() == NETWORK_MODE_CLIENT) && (flags & GAME_COMMAND_FLAG_NETWORKED) && gGameCommandNestLevel == 1 && gConfigNetwork.log_server_actions;
if (serverLog || clientLog)
{
game_log_multiplayer_command(command, eax, ebx, ecx, edx, edi, ebp);
}
*ebx &= ~GAME_COMMAND_FLAG_APPLY;
// First call for validity and price check
new_game_command_table[command](eax, ebx, ecx, edx, esi, edi, ebp);
cost = *ebx;
if (cost != MONEY32_UNDEFINED)
{
// Check funds
sint32 insufficientFunds = 0;
if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_2) && !(flags & GAME_COMMAND_FLAG_5) && cost != 0)
insufficientFunds = game_check_affordability(cost);
if (insufficientFunds != MONEY32_UNDEFINED)
{
*ebx = original_ebx;
*edx = original_edx;
*esi = original_esi;
*edi = original_edi;
*ebp = original_ebp;
if (!(flags & GAME_COMMAND_FLAG_APPLY))
{
// Decrement nest count
gGameCommandNestLevel--;
return cost;
}
if (network_get_mode() != NETWORK_MODE_NONE &&
!(flags & GAME_COMMAND_FLAG_NETWORKED) &&
!(flags & GAME_COMMAND_FLAG_GHOST) &&
!(flags & GAME_COMMAND_FLAG_5) &&
gGameCommandNestLevel == 1) /* Send only top-level commands */
{
// Disable these commands over the network
if (command != GAME_COMMAND_LOAD_OR_QUIT)
{
network_send_gamecmd(*eax, *ebx, *ecx, *edx, *esi, *edi, *ebp, game_command_callback_get_index(game_command_callback));
if (network_get_mode() == NETWORK_MODE_CLIENT)
{
// Client sent the command to the server, do not run it locally, just return. It will run when server sends it.
game_command_callback = nullptr;
// Decrement nest count
gGameCommandNestLevel--;
return cost;
}
}
}
// Second call to actually perform the operation
new_game_command_table[command](eax, ebx, ecx, edx, esi, edi, ebp);
// Do the callback (required for multiplayer to work correctly), but only for top level commands
if (gGameCommandNestLevel == 1)
{
if (game_command_callback && !(flags & GAME_COMMAND_FLAG_GHOST))
{
game_command_callback(*eax, *ebx, *ecx, *edx, *esi, *edi, *ebp);
game_command_callback = nullptr;
}
}
game_command_playerid = -1;
*edx = *ebx;
if (*edx != MONEY32_UNDEFINED && *edx < cost)
cost = *edx;
// Decrement nest count
gGameCommandNestLevel--;
if (gGameCommandNestLevel != 0)
return cost;
//
if (!(flags & 0x20))
{
// Update money balance
finance_payment(cost, gCommandExpenditureType);
if (gUnk141F568 == gUnk13CA740)
{
// Create a +/- money text effect
if (cost != 0 && game_is_not_paused())
money_effect_create(cost);
}
}
if (network_get_mode() == NETWORK_MODE_SERVER && !(flags & GAME_COMMAND_FLAG_NETWORKED) && !(flags & GAME_COMMAND_FLAG_GHOST))
{
network_set_player_last_action(network_get_player_index(network_get_current_player_id()), command);
network_add_player_money_spent(network_get_current_player_id(), cost);
}
// Start autosave timer after game command
if (gLastAutoSaveUpdate == AUTOSAVE_PAUSE)
gLastAutoSaveUpdate = platform_get_ticks();
return cost;
}
}
// Error occurred
// Decrement nest count
gGameCommandNestLevel--;
// Clear the game command callback to prevent the next command triggering it
game_command_callback = nullptr;
// Show error window
if (gGameCommandNestLevel == 0 && (flags & GAME_COMMAND_FLAG_APPLY) && gUnk141F568 == gUnk13CA740 && !(flags & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED) && !(flags & GAME_COMMAND_FLAG_NETWORKED))
context_show_error(gGameCommandErrorTitle, gGameCommandErrorText);
return MONEY32_UNDEFINED;
}
void game_log_multiplayer_command(int command, int * eax, int * ebx, int * ecx, int * edx, int * edi, int * ebp)
{
// Get player name
int player_index = network_get_player_index(game_command_playerid);
const char * player_name = network_get_player_name(player_index);
char log_msg[256];
if (command == GAME_COMMAND_CHEAT)
{
// Get cheat name
const char * cheat = cheats_get_cheat_string(*ecx, *edx, *edi);
char * args[2] = {
(char *) player_name,
(char *) cheat
};
format_string(log_msg, 256, STR_LOG_CHEAT_USED, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_CREATE_RIDE && *ebp == 1)
{ // ebp is 1 if the command comes from ride_create method in ride.c, other calls send ride_entry instead of ride and wont work
// Get ride name
Ride * ride = get_ride(*edx);
char ride_name[128];
format_string(ride_name, 128, ride->name, &ride->name_arguments);
char * args[2] = {
(char *) player_name,
ride_name
};
format_string(log_msg, 256, STR_LOG_CREATE_RIDE, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_DEMOLISH_RIDE && (*ebp == 1 || *ebp == 0))
{ // ebp is 1 if command comes from ride window prompt, so we don't log "demolishing" ride previews
// Get ride name
Ride * ride = get_ride(*edx);
char ride_name[128];
format_string(ride_name, 128, ride->name, &ride->name_arguments);
char * args[2] = {
(char *) player_name,
ride_name
};
format_string(log_msg, 256, STR_LOG_DEMOLISH_RIDE, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_SET_RIDE_APPEARANCE || command == GAME_COMMAND_SET_RIDE_VEHICLES || command == GAME_COMMAND_SET_RIDE_SETTING)
{
// Get ride name
int ride_index = *edx & 0xFF;
Ride * ride = get_ride(ride_index);
char ride_name[128];
format_string(ride_name, 128, ride->name, &ride->name_arguments);
char * args[2] = {
(char *) player_name,
ride_name
};
switch (command)
{
case GAME_COMMAND_SET_RIDE_APPEARANCE:
format_string(log_msg, 256, STR_LOG_RIDE_APPEARANCE, args);
break;
case GAME_COMMAND_SET_RIDE_VEHICLES:
format_string(log_msg, 256, STR_LOG_RIDE_VEHICLES, args);
break;
case GAME_COMMAND_SET_RIDE_SETTING:
format_string(log_msg, 256, STR_LOG_RIDE_SETTINGS, args);
break;
}
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_SET_RIDE_STATUS)
{
// Get ride name
int ride_index = *edx & 0xFF;
Ride * ride = get_ride(ride_index);
char ride_name[128];
format_string(ride_name, 128, ride->name, &ride->name_arguments);
char * args[2] = {
(char *) player_name,
ride_name
};
int status = *edx >> 8;
switch (status)
{
case 0:
format_string(log_msg, 256, STR_LOG_RIDE_STATUS_CLOSED, args);
break;
case 1:
format_string(log_msg, 256, STR_LOG_RIDE_STATUS_OPEN, args);
break;
case 2:
format_string(log_msg, 256, STR_LOG_RIDE_STATUS_TESTING, args);
break;
}
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_SET_RIDE_PRICE)
{
// Get ride name
int ride_index = *edx & 0xFF;
Ride * ride = get_ride(ride_index);
char ride_name[128];
format_string(ride_name, 128, ride->name, &ride->name_arguments);
// Format price
int price_args[1] = {*edi};
char price_str[16];
format_string(price_str, 16, STR_BOTTOM_TOOLBAR_CASH, price_args);
// Log change in primary or secondary price
char * args[3] = {
(char *) player_name,
ride_name,
price_str
};
if (*edx >> 8 == 0)
{
format_string(log_msg, 256, STR_LOG_RIDE_PRICE, args);
}
else if (*edx >> 8 == 1)
{
format_string(log_msg, 256, STR_LOG_RIDE_SECONDARY_PRICE, args);
}
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_SET_PARK_OPEN)
{
// Log change in park open/close
char * args[1] = {
(char *) player_name
};
if (*edx >> 8 == 0)
{
format_string(log_msg, 256, STR_LOG_PARK_OPEN, args);
}
else if (*edx >> 8 == 1)
{
format_string(log_msg, 256, STR_LOG_PARK_CLOSED, args);
}
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_SET_PARK_ENTRANCE_FEE)
{
// Format price
int price_args[1] = {*edi};
char price_str[16];
format_string(price_str, 16, STR_BOTTOM_TOOLBAR_CASH, price_args);
// Log change in park entrance fee
char * args[2] = {
(char *) player_name,
price_str
};
format_string(log_msg, 256, STR_LOG_PARK_ENTRANCE_FEE, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_PLACE_SCENERY || command == GAME_COMMAND_PLACE_WALL ||
command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER)
{
uint8 flags = *ebx & 0xFF;
if (flags & GAME_COMMAND_FLAG_GHOST)
{
// Don't log ghost previews being removed
return;
}
// Log placing scenery
char * args[1] = {
(char *) player_name
};
format_string(log_msg, 256, STR_LOG_PLACE_SCENERY, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_REMOVE_SCENERY || command == GAME_COMMAND_REMOVE_WALL ||
command == GAME_COMMAND_REMOVE_LARGE_SCENERY || command == GAME_COMMAND_REMOVE_BANNER)
{
uint8 flags = *ebx & 0xFF;
if (flags & GAME_COMMAND_FLAG_GHOST)
{
// Don't log ghost previews being removed
return;
}
// Log removing scenery
char * args[1] = {
(char *) player_name
};
format_string(log_msg, 256, STR_LOG_REMOVE_SCENERY, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_SET_SCENERY_COLOUR || command == GAME_COMMAND_SET_WALL_COLOUR ||
command == GAME_COMMAND_SET_LARGE_SCENERY_COLOUR || command == GAME_COMMAND_SET_BANNER_COLOUR ||
command == GAME_COMMAND_SET_BANNER_NAME || command == GAME_COMMAND_SET_SIGN_NAME ||
command == GAME_COMMAND_SET_BANNER_STYLE || command == GAME_COMMAND_SET_SIGN_STYLE)
{
// Log editing scenery
char * args[1] = {
(char *) player_name
};
format_string(log_msg, 256, STR_LOG_EDIT_SCENERY, args);
network_append_server_log(log_msg);
if (command == GAME_COMMAND_SET_BANNER_NAME || command == GAME_COMMAND_SET_SIGN_NAME)
{
static char banner_name[128];
memset(banner_name, ' ', sizeof(banner_name));
int nameChunkIndex = *eax & 0xFFFF;
int nameChunkOffset = nameChunkIndex - 1;
if (nameChunkOffset < 0)
nameChunkOffset = 2;
nameChunkOffset *= 12;
nameChunkOffset = Math::Min(nameChunkOffset, (sint32) (Util::CountOf(banner_name) - 12));
memcpy(banner_name + nameChunkOffset + 0, edx, 4);
memcpy(banner_name + nameChunkOffset + 4, ebp, 4);
memcpy(banner_name + nameChunkOffset + 8, edi, 4);
banner_name[sizeof(banner_name) - 1] = '\0';
char * args_sign[2] = {
(char *) player_name,
(char *) banner_name
};
format_string(log_msg, 256, STR_LOG_SET_SIGN_NAME, args_sign);
network_append_server_log(log_msg);
}
}
else if (command == GAME_COMMAND_PLACE_TRACK)
{
// Get ride name
int ride_index = *edx & 0xFF;
Ride * ride = get_ride(ride_index);
char ride_name[128];
format_string(ride_name, 128, ride->name, &ride->name_arguments);
char * args[2] = {
(char *) player_name,
ride_name
};
format_string(log_msg, 256, STR_LOG_PLACE_TRACK, args);
network_append_server_log(log_msg);
}
else if (command == GAME_COMMAND_REMOVE_TRACK)
{
char * args[1] = {
(char *) player_name
};
format_string(log_msg, 256, STR_LOG_REMOVE_TRACK, args);
network_append_server_log(log_msg);
}
}
void pause_toggle()
{
gGamePaused ^= GAME_PAUSED_NORMAL;
window_invalidate_by_class(WC_TOP_TOOLBAR);
if (gGamePaused & GAME_PAUSED_NORMAL)
{
audio_stop_all_music_and_sounds();
}
}
bool game_is_paused()
{
return gGamePaused != 0;
}
bool game_is_not_paused()
{
return gGamePaused == 0;
}
/**
*
* rct2: 0x00667C15
*/
void game_pause_toggle(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi, sint32 * ebp)
{
if (*ebx & GAME_COMMAND_FLAG_APPLY)
pause_toggle();
*ebx = 0;
}
/**
*
* rct2: 0x0066DB5F
*/
static void game_load_or_quit(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi, sint32 * ebp)
{
if (*ebx & GAME_COMMAND_FLAG_APPLY)
{
switch (*edx & 0xFF)
{
case 0:
gSavePromptMode = *edi & 0xFF;
context_open_window(WC_SAVE_PROMPT);
break;
case 1:
window_close_by_class(WC_SAVE_PROMPT);
break;
default:
game_load_or_quit_no_save_prompt();
break;
}
}
*ebx = 0;
}
/**
*
* rct2: 0x0066DC0F
*/
static void load_landscape()
{
Intent * intent = intent_create(WC_LOADSAVE);
intent_set_uint(intent, INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE);
context_open_intent(intent);
intent_release(intent);
}
static void utf8_to_rct2_self(char * buffer, size_t length)
{
char tempBuffer[512];
utf8_to_rct2(tempBuffer, buffer);
size_t i = 0;
const char * src = tempBuffer;
char * dst = buffer;
while (*src != 0 && i < length - 1)
{
if (*src == (char) (uint8) 0xFF)
{
if (i < length - 3)
{
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
}
else
{
break;
}
i += 3;
}
else
{
*dst++ = *src++;
i++;
}
}
do
{
*dst++ = '\0';
i++;
}
while (i < length);
}
static void rct2_to_utf8_self(char * buffer, size_t length)
{
if (length > 0)
{
char tempBuffer[512];
rct2_to_utf8(tempBuffer, buffer);
safe_strcpy(buffer, tempBuffer, length);
}
}
/**
* Converts all the user strings and news item strings to UTF-8.
*/
void game_convert_strings_to_utf8()
{
// Scenario details
rct2_to_utf8_self(gScenarioCompletedBy, 32);
rct2_to_utf8_self(gScenarioName, 64);
rct2_to_utf8_self(gScenarioDetails, 256);
// User strings
for (auto * string : gUserStrings)
{
if (!str_is_null_or_empty(string))
{
rct2_to_utf8_self(string, RCT12_USER_STRING_MAX_LENGTH);
utf8_remove_formatting(string, true);
}
}
// News items
game_convert_news_items_to_utf8();
}
void game_convert_news_items_to_utf8()
{
for (sint32 i = 0; i < MAX_NEWS_ITEMS; i++)
{
NewsItem * newsItem = news_item_get(i);
if (!str_is_null_or_empty(newsItem->Text))
{
rct2_to_utf8_self(newsItem->Text, sizeof(newsItem->Text));
}
}
}
/**
* Converts all the user strings and news item strings to RCT2 encoding.
*/
void game_convert_strings_to_rct2(rct_s6_data * s6)
{
// Scenario details
utf8_to_rct2_self(s6->scenario_completed_name, sizeof(s6->scenario_completed_name));
utf8_to_rct2_self(s6->scenario_name, sizeof(s6->scenario_name));
utf8_to_rct2_self(s6->scenario_description, sizeof(s6->scenario_description));
// User strings
for (auto * userString : s6->custom_strings)
{
if (!str_is_null_or_empty(userString))
{
utf8_to_rct2_self(userString, RCT12_USER_STRING_MAX_LENGTH);
}
}
// News items
for (auto &newsItem : s6->news_items)
{
if (!str_is_null_or_empty(newsItem.Text))
{
utf8_to_rct2_self(newsItem.Text, sizeof(newsItem.Text));
}
}
}
// OpenRCT2 workaround to recalculate some values which are saved redundantly in the save to fix corrupted files.
// For example recalculate guest count by looking at all the guests instead of trusting the value in the file.
void game_fix_save_vars()
{
// Recalculates peep count after loading a save to fix corrupted files
rct_peep * peep;
uint16 spriteIndex;
uint16 peepCount = 0;
FOR_ALL_GUESTS(spriteIndex, peep)
{
if (!peep->outside_of_park)
peepCount++;
}
gNumGuestsInPark = peepCount;
peep_sort();
// Fixes broken saves where a surface element could be null
// and broken saves with incorrect invisible map border tiles
for (sint32 y = 0; y < 256; y++)
{
for (sint32 x = 0; x < 256; x++)
{
rct_tile_element * tileElement = map_get_surface_element_at(x, y);
if (tileElement == nullptr)
{
log_error("Null map element at x = %d and y = %d. Fixing...", x, y);
tileElement = tile_element_insert(x, y, 14, 0);
if (tileElement == nullptr)
{
log_error("Unable to fix: Map element limit reached.");
return;
}
}
// Fix the invisible border tiles.
// At this point, we can be sure that tileElement is not NULL.
if (x == 0 || x == gMapSize - 1 || y == 0 || y == gMapSize - 1)
{
tileElement->base_height = 2;
tileElement->clearance_height = 2;
tileElement->properties.surface.slope = TILE_ELEMENT_SLOPE_FLAT;
}
}
}
research_fix();
// Fix banner list pointing to NULL map elements
banner_reset_broken_index();
// Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
fix_invalid_vehicle_sprite_sizes();
// Fix gParkEntrance locations for which the tile_element no longer exists
fix_park_entrance_locations();
// Fix ride entrances and exits that were moved without updating ride->entrances[] / ride->exits[]
fix_ride_entrance_and_exit_locations();
}
void handle_park_load_failure_with_title_opt(const ParkLoadResult * result, const utf8 * path, bool loadTitleFirst)
{
if (ParkLoadResult_GetError(result) == PARK_LOAD_ERROR_MISSING_OBJECTS)
{
// This option is used when loading parks from the command line
// to ensure that the title sequence loads before the window
if (loadTitleFirst)
{
title_load();
}
// The path needs to be duplicated as it's a const here
// which the window function doesn't like
Intent * intent = intent_create(WC_OBJECT_LOAD_ERROR);
intent_set_string(intent, INTENT_EXTRA_PATH, strndup(path, strnlen(path, MAX_PATH)));
intent_set_pointer(intent, INTENT_EXTRA_LIST, (void *) ParkLoadResult_GetMissingObjects(result));
intent_set_uint(intent, INTENT_EXTRA_LIST_COUNT, (uint32) ParkLoadResult_GetMissingObjectsCount(result));
context_open_intent(intent);
intent_release(intent);
}
else if (ParkLoadResult_GetError(result) == PARK_LOAD_ERROR_UNSUPPORTED_RCTC_FLAG)
{
// This option is used when loading parks from the command line
// to ensure that the title sequence loads before the window
if (loadTitleFirst)
{
title_load();
}
set_format_arg(0, uint16, ParkLoadResult_GetFlag(result));
context_show_error(STR_FAILED_TO_LOAD_IMCOMPATIBLE_RCTC_FLAG, STR_NONE);
}
else if (ParkLoadResult_GetError(result) != PARK_LOAD_ERROR_OK)
{
// If loading the SV6 or SV4 failed for a reason other than invalid objects
// the current park state will be corrupted so just go back to the title screen.
title_load();
}
}
void handle_park_load_failure(const ParkLoadResult * result, const utf8 * path)
{
handle_park_load_failure_with_title_opt(result, path, false);
}
void game_load_init()
{
rct_window * mainWindow;
gScreenFlags = SCREEN_FLAGS_PLAYING;
audio_stop_all_music_and_sounds();
if (!gLoadKeepWindowsOpen)
{
viewport_init_all();
game_create_windows();
mainWindow = window_get_main();
}
else
{
mainWindow = window_get_main();
window_unfollow_sprite(mainWindow);
}
if (mainWindow != nullptr)
{
mainWindow->viewport_target_sprite = SPRITE_INDEX_NULL;
mainWindow->saved_view_x = gSavedViewX;
mainWindow->saved_view_y = gSavedViewY;
uint8 zoomDifference = gSavedViewZoom - mainWindow->viewport->zoom;
mainWindow->viewport->zoom = gSavedViewZoom;
gCurrentRotation = gSavedViewRotation;
if (zoomDifference != 0)
{
mainWindow->viewport->view_width <<= zoomDifference;
mainWindow->viewport->view_height <<= zoomDifference;
}
mainWindow->saved_view_x -= mainWindow->viewport->view_width >> 1;
mainWindow->saved_view_y -= mainWindow->viewport->view_height >> 1;
// Make sure the viewport has correct coordinates set.
viewport_update_position(mainWindow);
window_invalidate(mainWindow);
}
if (network_get_mode() != NETWORK_MODE_CLIENT)
{
reset_sprite_spatial_index();
}
reset_all_sprite_quadrant_placements();
scenery_set_default_placement_configuration();
Intent * intent = intent_create(INTENT_ACTION_REFRESH_NEW_RIDES);
context_broadcast_intent(intent);
intent_release(intent);
gWindowUpdateTicks = 0;
load_palette();
if (!gOpenRCT2Headless)
{
intent = intent_create(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
context_broadcast_intent(intent);
intent_release(intent);
window_update_all();
}
audio_stop_title_music();
gGameSpeed = 1;
}
/**
*
* rct2: 0x0069E9A7
* Call after a rotation or loading of a save to reset sprite quadrants
*/
void reset_all_sprite_quadrant_placements()
{
for (size_t i = 0; i < MAX_SPRITES; i++)
{
rct_sprite * spr = get_sprite(i);
if (spr->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL)
{
sprite_move(spr->unknown.x, spr->unknown.y, spr->unknown.z, spr);
}
}
}
void save_game()
{
if (!gFirstTimeSaving)
{
log_verbose("Saving to %s", gScenarioSavePath);
if (scenario_save(gScenarioSavePath, 0x80000000 | (gConfigGeneral.save_plugin_data ? 1 : 0)))
{
log_verbose("Saved to %s", gScenarioSavePath);
safe_strcpy(gCurrentLoadedPath, gScenarioSavePath, MAX_PATH);
// Setting screen age to zero, so no prompt will pop up when closing the
// game shortly after saving.
gScreenAge = 0;
}
}
else
{
save_game_as();
}
}
void * create_save_game_as_intent()
{
char name[MAX_PATH];
safe_strcpy(name, path_get_filename(gScenarioSavePath), MAX_PATH);
path_remove_extension(name);
Intent * intent = intent_create(WC_LOADSAVE);
intent_set_uint(intent, INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME);
intent_set_string(intent, INTENT_EXTRA_PATH, name);
return intent;
}
void save_game_as()
{
Intent * intent = (Intent *) create_save_game_as_intent();
context_open_intent(intent);
intent_release(intent);
}
static sint32 compare_autosave_file_paths(const void * a, const void * b)
{
return strcmp(*(char **) a, *(char **) b);
}
static void limit_autosave_count(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
{
sint32 fileEnumHandle = 0;
size_t autosavesCount = 0;
size_t numAutosavesToDelete = 0;
file_info fileInfo;
utf8 filter[MAX_PATH];
utf8 ** autosaveFiles = nullptr;
if (processLandscapeFolder)
{
platform_get_user_directory(filter, "landscape", sizeof(filter));
safe_strcat_path(filter, "autosave", sizeof(filter));
safe_strcat_path(filter, "autosave_*.sc6", sizeof(filter));
}
else
{
platform_get_user_directory(filter, "save", sizeof(filter));
safe_strcat_path(filter, "autosave", sizeof(filter));
safe_strcat_path(filter, "autosave_*.sv6", sizeof(filter));
}
// At first, count how many autosaves there are
fileEnumHandle = platform_enumerate_files_begin(filter);
while (platform_enumerate_files_next(fileEnumHandle, &fileInfo))
{
autosavesCount++;
}
platform_enumerate_files_end(fileEnumHandle);
// If there are fewer autosaves than the number of files to keep we don't need to delete anything
if (autosavesCount <= numberOfFilesToKeep)
{
return;
}
autosaveFiles = (utf8 **) malloc(sizeof(utf8 *) * autosavesCount);
fileEnumHandle = platform_enumerate_files_begin(filter);
for (size_t i = 0; i < autosavesCount; i++)
{
autosaveFiles[i] = (utf8 *) malloc(sizeof(utf8) * MAX_PATH);
memset(autosaveFiles[i], 0, sizeof(utf8) * MAX_PATH);
if (platform_enumerate_files_next(fileEnumHandle, &fileInfo))
{
if (processLandscapeFolder)
{
platform_get_user_directory(autosaveFiles[i], "landscape", sizeof(utf8) * MAX_PATH);
}
else
{
platform_get_user_directory(autosaveFiles[i], "save", sizeof(utf8) * MAX_PATH);
}
safe_strcat_path(autosaveFiles[i], "autosave", sizeof(utf8) * MAX_PATH);
safe_strcat_path(autosaveFiles[i], fileInfo.path, sizeof(utf8) * MAX_PATH);
}
}
platform_enumerate_files_end(fileEnumHandle);
qsort(autosaveFiles, autosavesCount, sizeof(char *), compare_autosave_file_paths);
// Calculate how many saves we need to delete.
numAutosavesToDelete = autosavesCount - numberOfFilesToKeep;
for (size_t i = 0; numAutosavesToDelete > 0; i++, numAutosavesToDelete--)
{
platform_file_delete(autosaveFiles[i]);
}
for (size_t i = 0; i < autosavesCount; i++)
{
free(autosaveFiles[i]);
}
free(autosaveFiles);
}
void game_autosave()
{
const char * subDirectory = "save";
const char * fileExtension = ".sv6";
uint32 saveFlags = 0x80000000;
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
{
subDirectory = "landscape";
fileExtension = ".sc6";
saveFlags |= 2;
}
// Retrieve current time
rct2_date currentDate;
platform_get_date_local(&currentDate);
rct2_time currentTime;
platform_get_time_local(&currentTime);
utf8 timeName[44];
snprintf(timeName, sizeof(timeName), "autosave_%04u-%02u-%02u_%02u-%02u-%02u%s",
currentDate.year, currentDate.month, currentDate.day, currentTime.hour,
currentTime.minute, currentTime.second, fileExtension);
limit_autosave_count(NUMBER_OF_AUTOSAVES_TO_KEEP, (gScreenFlags & SCREEN_FLAGS_EDITOR));
utf8 path[MAX_PATH];
utf8 backupPath[MAX_PATH];
platform_get_user_directory(path, subDirectory, sizeof(path));
safe_strcat_path(path, "autosave", sizeof(path));
platform_ensure_directory_exists(path);
safe_strcpy(backupPath, path, sizeof(backupPath));
safe_strcat_path(path, timeName, sizeof(path));
safe_strcat_path(backupPath, "autosave", sizeof(backupPath));
safe_strcat(backupPath, fileExtension, sizeof(backupPath));
safe_strcat(backupPath, ".bak", sizeof(backupPath));
if (platform_file_exists(path))
{
platform_file_copy(path, backupPath, true);
}
scenario_save(path, saveFlags);
}
static void game_load_or_quit_no_save_prompt_callback(sint32 result, const utf8 * path)
{
if (result == MODAL_RESULT_OK)
{
context_load_park_from_file(path);
}
}
/**
*
* rct2: 0x0066DB79
*/
void game_load_or_quit_no_save_prompt()
{
switch (gSavePromptMode)
{
case PM_SAVE_BEFORE_LOAD:
game_do_command(0, 1, 0, 1, GAME_COMMAND_LOAD_OR_QUIT, 0, 0);
tool_cancel();
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
{
load_landscape();
}
else
{
Intent * intent = intent_create(WC_LOADSAVE);
intent_set_uint(intent, INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME);
intent_set_pointer(intent, INTENT_EXTRA_CALLBACK, (void *) game_load_or_quit_no_save_prompt_callback);
context_open_intent(intent);
intent_release(intent);
}
break;
case PM_SAVE_BEFORE_QUIT:
game_do_command(0, 1, 0, 1, GAME_COMMAND_LOAD_OR_QUIT, 0, 0);
tool_cancel();
if (input_test_flag(INPUT_FLAG_5))
{
input_set_flag(INPUT_FLAG_5, false);
}
gGameSpeed = 1;
gFirstTimeSaving = true;
title_load();
break;
default:
openrct2_finish();
break;
}
}
/**
* Initialises the map, park etc. basically all S6 data.
*/
void game_init_all(sint32 mapSize)
{
gInMapInitCode = true;
map_init(mapSize);
park_init();
finance_init();
reset_park_entry();
banner_init();
ride_init_all();
reset_sprite_list();
staff_reset_modes();
date_reset();
climate_reset(CLIMATE_COOL_AND_WET);
news_item_init_queue();
user_string_clear_all();
gInMapInitCode = false;
gNextGuestNumber = 1;
context_init();
scenery_set_default_placement_configuration();
Intent * intent = intent_create(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
context_broadcast_intent(intent);
intent_release(intent);
load_palette();
}
GAME_COMMAND_POINTER * new_game_command_table[GAME_COMMAND_COUNT] = {
game_command_set_ride_appearance,
game_command_set_land_height,
game_pause_toggle,
game_command_place_track,
game_command_remove_track,
game_load_or_quit,
game_command_create_ride,
game_command_demolish_ride,
game_command_set_ride_status,
game_command_set_ride_vehicles,
game_command_set_ride_name,
game_command_set_ride_setting,
game_command_place_ride_entrance_or_exit,
game_command_remove_ride_entrance_or_exit,
game_command_remove_scenery,
game_command_place_scenery,
game_command_set_water_height,
game_command_place_footpath,
game_command_place_footpath_from_track,
game_command_remove_footpath,
game_command_change_surface_style,
game_command_set_ride_price,
game_command_set_guest_name,
game_command_set_staff_name,
game_command_raise_land,
game_command_lower_land,
game_command_smooth_land,
game_command_raise_water,
game_command_lower_water,
game_command_set_brakes_speed,
game_command_hire_new_staff_member,
game_command_set_staff_patrol,
game_command_fire_staff_member,
game_command_set_staff_order,
game_command_set_park_name,
game_command_set_park_open,
game_command_buy_land_rights,
game_command_place_park_entrance,
game_command_remove_park_entrance,
game_command_set_maze_track,
game_command_set_park_entrance_fee,
game_command_update_staff_colour,
game_command_place_wall,
game_command_remove_wall,
game_command_place_large_scenery,
game_command_remove_large_scenery,
game_command_set_current_loan,
game_command_set_research_funding,
game_command_place_track_design,
game_command_start_campaign,
game_command_place_maze_design,
game_command_place_banner,
game_command_remove_banner,
game_command_set_scenery_colour,
game_command_set_wall_colour,
game_command_set_large_scenery_colour,
game_command_set_banner_colour,
game_command_set_land_ownership,
game_command_clear_scenery,
game_command_set_banner_name,
game_command_set_sign_name,
game_command_set_banner_style,
game_command_set_sign_style,
game_command_set_player_group,
game_command_modify_groups,
game_command_kick_player,
game_command_cheat,
game_command_pickup_guest,
game_command_pickup_staff,
game_command_balloon_press,
game_command_modify_tile,
game_command_edit_scenario_options,
};