1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-26 00:04:43 +01:00
Files
OpenRCT2/src/openrct2/Game.cpp
Hielke Morsink 087519ca62 Fix #5261 Deleting the sign after copy/pasting it will crash the game
Prior to the previous commit, signs could be duplicated by pasting them using the tile inspector, which results in some funky behaviour. A duplicated sign refers to the same banner index (which in turn refers to the same user string ID), making the player unable to modify it. Deleting it would previously even crash the game.
This commit looks for such signs when loading a scenario, and attempts to fix them, by creating a new banner entry (and user string if one is used).
2018-03-13 19:45:48 +01:00

1764 lines
52 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 <memory>
#include "audio/audio.h"
#include "Cheats.h"
#include "config/Config.h"
#include "Context.h"
#include "core/FileScanner.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/RCT1.h"
#include "ride/Ride.h"
#include "ride/RideRatings.h"
#include "ride/Station.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/MapAnimation.h"
#include "world/Park.h"
#include "world/Scenery.h"
#include "world/Sprite.h"
#include "world/Water.h"
#include "object/ObjectList.h"
#include "interface/Window_internal.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()
{
auto water_type = (rct_water_type *)object_entry_get_chunk(OBJECT_TYPE_WATER, 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)
{
auto paletteId = climate_get_weather_gloom_palette_id(gClimateCurrent);
if (paletteId != PALETTE_NULL)
{
shade = 1;
if (paletteId != 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 <= 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, const int * eax, const int * ebx, const 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);
}
else if (command == GAME_COMMAND_PLACE_PEEP_SPAWN)
{
char * args[1] = {
(char *) player_name
};
format_string(log_msg, 256, STR_LOG_PLACE_PEEP_SPAWN, 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()
{
auto intent = Intent(WC_LOADSAVE);
intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE);
context_open_intent(&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();
// Peeps to remove have to be cached here, as removing them from within the loop breaks iteration
std::vector<rct_peep *> peepsToRemove;
// Fix possibly invalid field values
FOR_ALL_GUESTS(spriteIndex, peep)
{
if (peep->current_ride_station >= MAX_STATIONS)
{
const uint8 srcStation = peep->current_ride_station;
const uint8 rideIdx = peep->current_ride;
if (rideIdx == RIDE_ID_NULL)
{
continue;
}
set_format_arg(0, uint32, peep->id);
utf8 * curName = gCommonStringFormatBuffer;
rct_string_id curId = peep->name_string_idx;
format_string(curName, 256, curId, gCommonFormatArgs);
log_warning("Peep %u (%s) has invalid ride station = %u for ride %u.", spriteIndex, curName, srcStation, rideIdx);
sint8 station = ride_get_first_valid_station_exit(get_ride(rideIdx));
if (station == -1)
{
log_warning("Couldn't find station, removing peep %u", spriteIndex);
peepsToRemove.push_back(peep);
} else {
log_warning("Amending ride station to %u.", station);
peep->current_ride_station = station;
}
}
}
if (peepsToRemove.size() > 0)
{
// Some broken saves have broken spatial indexes
reset_sprite_spatial_index();
}
for (auto ptr : peepsToRemove)
{
peep_remove(ptr);
}
// Fixes broken saves where a surface element could be null
// and broken saves with incorrect invisible map border tiles
for (sint32 y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
{
for (sint32 x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; 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 banners which share their index
fix_duplicated_banners();
// 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();
}
void handle_park_load_failure_with_title_opt(const ParkLoadResult * result, const std::string & 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
auto intent = Intent(WC_OBJECT_LOAD_ERROR);
intent.putExtra(INTENT_EXTRA_PATH, path);
intent.putExtra(INTENT_EXTRA_LIST, (void *) ParkLoadResult_GetMissingObjects(result));
intent.putExtra(INTENT_EXTRA_LIST_COUNT, (uint32) ParkLoadResult_GetMissingObjectsCount(result));
context_open_intent(&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 std::string & 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)
{
rct_viewport * viewport = window_get_viewport(mainWindow);
mainWindow->viewport_target_sprite = SPRITE_INDEX_NULL;
mainWindow->saved_view_x = gSavedViewX;
mainWindow->saved_view_y = gSavedViewY;
uint8 zoomDifference = gSavedViewZoom - viewport->zoom;
viewport->zoom = gSavedViewZoom;
gCurrentRotation = gSavedViewRotation;
if (zoomDifference != 0)
{
viewport->view_width <<= zoomDifference;
viewport->view_height <<= zoomDifference;
}
mainWindow->saved_view_x -= viewport->view_width >> 1;
mainWindow->saved_view_y -= 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();
auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
context_broadcast_intent(&intent);
gWindowUpdateTicks = 0;
load_palette();
if (!gOpenRCT2Headless)
{
intent = Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
context_broadcast_intent(&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 = new Intent(WC_LOADSAVE);
intent->putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME);
intent->putExtra(INTENT_EXTRA_PATH, std::string{name});
return intent;
}
void save_game_as()
{
auto * intent = (Intent *) create_save_game_as_intent();
context_open_intent(intent);
delete 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)
{
size_t autosavesCount = 0;
size_t numAutosavesToDelete = 0;
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
{
auto scanner = std::unique_ptr<IFileScanner>(Path::ScanDirectory(filter, false));
while (scanner->Next())
{
autosavesCount++;
}
}
// 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);
{
auto scanner = std::unique_ptr<IFileScanner>(Path::ScanDirectory(filter, false));
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 (scanner->Next())
{
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], scanner->GetPathRelative(), sizeof(utf8) * MAX_PATH);
}
}
}
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
{
auto intent = Intent(WC_LOADSAVE);
intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME);
intent.putExtra(INTENT_EXTRA_CALLBACK, (void *) game_load_or_quit_no_save_prompt_callback);
context_open_intent(&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();
auto intent = Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
context_broadcast_intent(&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,
NULL,
};