mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-21 22:13:07 +01:00
This was causing the number of map animations to skyrocket as there was no way to remove animations caused by ghosts. This shouldn't progress the animation only remove invalid animations.
1202 lines
33 KiB
C
1202 lines
33 KiB
C
/*****************************************************************************
|
|
* Copyright (c) 2014 Ted John, Peter Hill
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
*
|
|
* This file is part of 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.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*****************************************************************************/
|
|
|
|
#include "addresses.h"
|
|
#include "audio/audio.h"
|
|
#include "config.h"
|
|
#include "game.h"
|
|
#include "editor.h"
|
|
#include "world/footpath.h"
|
|
#include "input.h"
|
|
#include "localisation/localisation.h"
|
|
#include "interface/screenshot.h"
|
|
#include "interface/viewport.h"
|
|
#include "interface/widget.h"
|
|
#include "interface/window.h"
|
|
#include "management/finance.h"
|
|
#include "management/marketing.h"
|
|
#include "management/news_item.h"
|
|
#include "management/research.h"
|
|
#include "network/network.h"
|
|
#include "object.h"
|
|
#include "openrct2.h"
|
|
#include "peep/peep.h"
|
|
#include "peep/staff.h"
|
|
#include "platform/platform.h"
|
|
#include "ride/ride.h"
|
|
#include "ride/ride_ratings.h"
|
|
#include "ride/vehicle.h"
|
|
#include "ride/track.h"
|
|
#include "scenario.h"
|
|
#include "title.h"
|
|
#include "tutorial.h"
|
|
#include "util/sawyercoding.h"
|
|
#include "util/util.h"
|
|
#include "windows/error.h"
|
|
#include "windows/tooltip.h"
|
|
#include "world/climate.h"
|
|
#include "world/map_animation.h"
|
|
#include "world/park.h"
|
|
#include "world/scenery.h"
|
|
#include "world/sprite.h"
|
|
#include "world/water.h"
|
|
|
|
int gGameSpeed = 1;
|
|
float gDayNightCycle = 0;
|
|
bool gInUpdateCode = false;
|
|
|
|
GAME_COMMAND_CALLBACK_POINTER* game_command_callback = 0;
|
|
GAME_COMMAND_CALLBACK_POINTER* game_command_callback_table[] = {
|
|
0,
|
|
game_command_callback_ride_construct_new,
|
|
game_command_callback_ride_construct_placed_front,
|
|
game_command_callback_ride_construct_placed_back,
|
|
game_command_callback_ride_remove_track_piece,
|
|
};
|
|
|
|
int game_command_callback_get_index(GAME_COMMAND_CALLBACK_POINTER* callback)
|
|
{
|
|
for (int i = 0; i < 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(int index)
|
|
{
|
|
if (index < countof(game_command_callback_table)) {
|
|
return game_command_callback_table[index];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void game_increase_game_speed()
|
|
{
|
|
gGameSpeed = 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 = 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()
|
|
{
|
|
window_main_open();
|
|
window_top_toolbar_open();
|
|
window_game_bottom_toolbar_open();
|
|
window_resize_gui(RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_WIDTH, uint16), RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_HEIGHT, uint16));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006838BD
|
|
*/
|
|
void update_palette_effects()
|
|
{
|
|
rct_water_type* water_type = (rct_water_type*)object_entry_groups[OBJECT_TYPE_WATER].chunks[0];
|
|
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_LIGHTNING_ACTIVE, uint8) == 1) {
|
|
// change palette to lighter color during lightning
|
|
int palette = 1532;
|
|
|
|
if ((sint32)water_type != -1) {
|
|
palette = water_type->image_id;
|
|
}
|
|
rct_g1_element g1_element = g1Elements[palette];
|
|
int xoffset = g1_element.x_offset;
|
|
xoffset = xoffset * 4;
|
|
for (int i = 0; i < g1_element.width; i++) {
|
|
RCT2_ADDRESS(RCT2_ADDRESS_PALETTE + xoffset, uint8)[(i * 4) + 0] = -((0xFF - g1_element.offset[(i * 3) + 0]) / 2) - 1;
|
|
RCT2_ADDRESS(RCT2_ADDRESS_PALETTE + xoffset, uint8)[(i * 4) + 1] = -((0xFF - g1_element.offset[(i * 3) + 1]) / 2) - 1;
|
|
RCT2_ADDRESS(RCT2_ADDRESS_PALETTE + xoffset, uint8)[(i * 4) + 2] = -((0xFF - g1_element.offset[(i * 3) + 2]) / 2) - 1;
|
|
}
|
|
RCT2_GLOBAL(0x014241BC, uint32) = 2;
|
|
platform_update_palette(RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8), 10, 236);
|
|
RCT2_GLOBAL(0x014241BC, uint32) = 0;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_LIGHTNING_ACTIVE, uint8)++;
|
|
} else {
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_LIGHTNING_ACTIVE, uint8) == 2) {
|
|
// change palette back to normal after lightning
|
|
int palette = 1532;
|
|
|
|
if ((sint32)water_type != -1) {
|
|
palette = water_type->image_id;
|
|
}
|
|
|
|
rct_g1_element g1_element = g1Elements[palette];
|
|
int xoffset = g1_element.x_offset;
|
|
xoffset = xoffset * 4;
|
|
for (int i = 0; i < g1_element.width; i++) {
|
|
RCT2_ADDRESS(RCT2_ADDRESS_PALETTE + xoffset, uint8)[(i * 4) + 0] = g1_element.offset[(i * 3) + 0];
|
|
RCT2_ADDRESS(RCT2_ADDRESS_PALETTE + xoffset, uint8)[(i * 4) + 1] = g1_element.offset[(i * 3) + 1];
|
|
RCT2_ADDRESS(RCT2_ADDRESS_PALETTE + xoffset, uint8)[(i * 4) + 2] = g1_element.offset[(i * 3) + 2];
|
|
}
|
|
}
|
|
|
|
// animate the water/lava/chain movement palette
|
|
int q = 0;
|
|
int weather_colour = RCT2_ADDRESS(0x98195C, uint32)[RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_WEATHER_GLOOM, uint8)];
|
|
if (weather_colour != -1) {
|
|
q = 1;
|
|
if (weather_colour != 0x2000031) {
|
|
q = 2;
|
|
}
|
|
}
|
|
uint32 j = RCT2_GLOBAL(RCT2_ADDRESS_PALETTE_EFFECT_FRAME_NO, uint32);
|
|
j = (((uint16)((~j / 2) * 128) * 15) >> 16);
|
|
int p = 1533;
|
|
if ((sint32)water_type != -1) {
|
|
p = water_type->var_06;
|
|
}
|
|
rct_g1_element g1_element = g1Elements[q + p];
|
|
uint8* vs = &g1_element.offset[j * 3];
|
|
uint8* vd = RCT2_ADDRESS(0x01424A18, uint8);
|
|
int n = 5;
|
|
for (int i = 0; i < n; i++) {
|
|
vd[0] = vs[0];
|
|
vd[1] = vs[1];
|
|
vd[2] = vs[2];
|
|
vs += 9;
|
|
if (vs >= &g1_element.offset[9 * n]) {
|
|
vs -= 9 * n;
|
|
}
|
|
vd += 4;
|
|
}
|
|
|
|
p = 1536;
|
|
if ((sint32)water_type != -1) {
|
|
p = water_type->var_0A;
|
|
}
|
|
g1_element = g1Elements[q + p];
|
|
vs = &g1_element.offset[j * 3];
|
|
n = 5;
|
|
for (int i = 0; i < n; i++) {
|
|
vd[0] = vs[0];
|
|
vd[1] = vs[1];
|
|
vd[2] = vs[2];
|
|
vs += 9;
|
|
if (vs >= &g1_element.offset[9 * n]) {
|
|
vs -= 9 * n;
|
|
}
|
|
vd += 4;
|
|
}
|
|
|
|
j = ((uint16)(RCT2_GLOBAL(RCT2_ADDRESS_PALETTE_EFFECT_FRAME_NO, uint32) * -960) * 3) >> 16;
|
|
p = 1539;
|
|
g1_element = g1Elements[q + p];
|
|
vs = &g1_element.offset[j * 3];
|
|
vd += 12;
|
|
n = 3;
|
|
for (int i = 0; i < n; i++) {
|
|
vd[0] = vs[0];
|
|
vd[1] = vs[1];
|
|
vd[2] = vs[2];
|
|
vs += 3;
|
|
if (vs >= &g1_element.offset[3 * n]) {
|
|
vs -= 3 * n;
|
|
}
|
|
vd += 4;
|
|
}
|
|
|
|
RCT2_GLOBAL(0x014241BC, uint32) = 2;
|
|
platform_update_palette(RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8), 230, 16);
|
|
RCT2_GLOBAL(0x014241BC, uint32) = 0;
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_LIGHTNING_ACTIVE, uint8) == 2) {
|
|
RCT2_GLOBAL(0x014241BC, uint32) = 2;
|
|
platform_update_palette(RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8), 10, 236);
|
|
RCT2_GLOBAL(0x014241BC, uint32) = 0;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_LIGHTNING_ACTIVE, uint8) = 0;
|
|
}
|
|
}
|
|
if (RCT2_GLOBAL(0x009E2C4C, uint32) == 2 || RCT2_GLOBAL(0x009E2C4C, uint32) == 1) {
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_CAP_BPP, uint32) != 8) {
|
|
RCT2_GLOBAL(0x009E2C78, int) = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void game_update()
|
|
{
|
|
int i, numUpdates;
|
|
|
|
// 0x006E3AEC // screen_game_process_mouse_input();
|
|
screenshot_check();
|
|
game_handle_keyboard_input();
|
|
|
|
// Determine how many times we need to update the game
|
|
if (gGameSpeed > 1) {
|
|
numUpdates = 1 << (gGameSpeed - 1);
|
|
} else {
|
|
numUpdates = RCT2_GLOBAL(RCT2_ADDRESS_TICKS_SINCE_LAST_UPDATE, uint16) / 31;
|
|
numUpdates = clamp(1, numUpdates, 4);
|
|
}
|
|
|
|
if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED && network_get_authstatus() == NETWORK_AUTH_OK) {
|
|
if (network_get_server_tick() - RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) >= 10) {
|
|
// make sure client doesn't fall behind the server too much
|
|
numUpdates += 10;
|
|
}
|
|
} else {
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_GAME_PAUSED, uint8) != 0) {
|
|
numUpdates = 0;
|
|
// Update the animation list. Note this does not
|
|
// increment the map animation.
|
|
map_animation_invalidate_all();
|
|
}
|
|
}
|
|
|
|
// Update the game one or more times
|
|
for (i = 0; i < numUpdates; i++) {
|
|
game_logic_update();
|
|
start_title_music();
|
|
|
|
if (gGameSpeed > 1)
|
|
continue;
|
|
|
|
// Possibly smooths viewport scrolling, I don't see a difference though
|
|
if (RCT2_GLOBAL(0x009E2D74, uint32) == 1) {
|
|
RCT2_GLOBAL(0x009E2D74, uint32) = 0;
|
|
break;
|
|
} else {
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_INPUT_STATE, uint8) == INPUT_STATE_RESET ||
|
|
RCT2_GLOBAL(RCT2_ADDRESS_INPUT_STATE, uint8) == INPUT_STATE_NORMAL
|
|
) {
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) & INPUT_FLAG_VIEWPORT_SCROLLING) {
|
|
RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) &= ~INPUT_FLAG_VIEWPORT_SCROLLING;
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always perform autosave check, even when paused
|
|
scenario_autosave_check();
|
|
|
|
network_update();
|
|
news_item_update_current();
|
|
window_dispatch_update_all();
|
|
|
|
RCT2_GLOBAL(0x009A8C28, uint8) = 0;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) &= ~INPUT_FLAG_VIEWPORT_SCROLLING;
|
|
|
|
// 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 (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) % 4 == 0)
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) ^= (1 << 15);
|
|
|
|
// Handle guest map flashing
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) &= ~(1 << 1);
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) & (1 << 0))
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) |= (1 << 1);
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) &= ~(1 << 0);
|
|
|
|
// Handle staff map flashing
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) &= ~(1 << 3);
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) & (1 << 2))
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) |= (1 << 3);
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS, uint16) &= ~(1 << 2);
|
|
|
|
window_map_tooltip_update_visibility();
|
|
|
|
// Input
|
|
RCT2_GLOBAL(0x0141F568, uint8) = RCT2_GLOBAL(0x0013CA740, uint8);
|
|
game_handle_input();
|
|
}
|
|
|
|
void game_logic_update()
|
|
{
|
|
///////////////////////////
|
|
gInUpdateCode = true;
|
|
///////////////////////////
|
|
network_update();
|
|
if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED && network_get_authstatus() == NETWORK_AUTH_OK) {
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) >= network_get_server_tick()) {
|
|
// dont run past the server
|
|
return;
|
|
}
|
|
}
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32)++;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TICKS, uint32)++;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_AGE, sint16)++;
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_AGE, sint16) == 0)
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_AGE, sint16)--;
|
|
|
|
sub_68B089();
|
|
scenario_update();
|
|
climate_update();
|
|
map_update_tiles();
|
|
map_update_path_wide_flags();
|
|
peep_update_all();
|
|
vehicle_update_all();
|
|
sprite_misc_update_all();
|
|
ride_update_all();
|
|
park_update();
|
|
research_update();
|
|
ride_ratings_update_all();
|
|
ride_measurements_update();
|
|
///////////////////////////
|
|
gInUpdateCode = false;
|
|
///////////////////////////
|
|
|
|
map_animation_invalidate_all();
|
|
vehicle_sounds_update();
|
|
peep_update_crowd_noise();
|
|
climate_update_sound();
|
|
editor_open_windows_for_current_step();
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SAVED_AGE, uint16)++;
|
|
|
|
// Update windows
|
|
//window_dispatch_update_all();
|
|
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_ERROR_TYPE, uint8) != 0) {
|
|
rct_string_id title_text = STR_UNABLE_TO_LOAD_FILE;
|
|
rct_string_id body_text = RCT2_GLOBAL(RCT2_ADDRESS_ERROR_STRING_ID, uint16);
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_ERROR_TYPE, uint8) == 254) {
|
|
title_text = RCT2_GLOBAL(RCT2_ADDRESS_ERROR_STRING_ID, uint16);
|
|
body_text = 0xFFFF;
|
|
}
|
|
RCT2_GLOBAL(RCT2_ADDRESS_ERROR_TYPE, uint8) = 0;
|
|
|
|
window_error_open(title_text, body_text);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0069C62C
|
|
*
|
|
* @param cost (ebp)
|
|
*/
|
|
static int game_check_affordability(int cost)
|
|
{
|
|
if (cost <= 0)return cost;
|
|
if (RCT2_GLOBAL(0x141F568, uint8) & 0xF0)return cost;
|
|
|
|
if (!(RCT2_GLOBAL(RCT2_ADDRESS_PARK_FLAGS, uint32)&(1 << 8))){
|
|
if (cost <= (sint32)(DECRYPT_MONEY(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_MONEY_ENCRYPTED, sint32))))return cost;
|
|
}
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS, uint32) = cost;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 827;
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
static GAME_COMMAND_POINTER* new_game_command_table[62];
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006677F2
|
|
*
|
|
* @param flags (ebx)
|
|
* @param command (esi)
|
|
*/
|
|
int game_do_command(int eax, int ebx, int ecx, int edx, int esi, int edi, int 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)
|
|
*/
|
|
int game_do_command_p(int command, int *eax, int *ebx, int *ecx, int *edx, int *esi, int *edi, int *ebp)
|
|
{
|
|
int cost, flags, insufficientFunds;
|
|
int 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;
|
|
|
|
flags = *ebx;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 0xFFFF;
|
|
|
|
// Increment nest count
|
|
RCT2_GLOBAL(0x009A8C28, uint8)++;
|
|
|
|
// 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_FENCE ||
|
|
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();
|
|
}
|
|
|
|
*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
|
|
insufficientFunds = 0;
|
|
if (RCT2_GLOBAL(0x009A8C28, uint8) == 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
|
|
RCT2_GLOBAL(0x009A8C28, uint8)--;
|
|
return cost;
|
|
}
|
|
|
|
if (network_get_mode() != NETWORK_MODE_NONE && !(flags & GAME_COMMAND_FLAG_NETWORKED) && !(flags & GAME_COMMAND_FLAG_GHOST) && !(flags & GAME_COMMAND_FLAG_5) && RCT2_GLOBAL(0x009A8C28, uint8) == 1 /* Send only top-level commands */) {
|
|
if (command != GAME_COMMAND_LOAD_OR_QUIT) { // Disable these commands over the network
|
|
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 = 0;
|
|
// Decrement nest count
|
|
RCT2_GLOBAL(0x009A8C28, uint8)--;
|
|
return cost;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second call to actually perform the operation
|
|
new_game_command_table[command](eax, ebx, ecx, edx, esi, edi, ebp);
|
|
|
|
if (game_command_callback) {
|
|
game_command_callback(*eax, *ebx, *ecx, *edx, *esi, *edi, *ebp);
|
|
game_command_callback = 0;
|
|
}
|
|
|
|
*edx = *ebx;
|
|
|
|
if (*edx != MONEY32_UNDEFINED && *edx < cost)
|
|
cost = *edx;
|
|
|
|
// Decrement nest count
|
|
RCT2_GLOBAL(0x009A8C28, uint8)--;
|
|
if (RCT2_GLOBAL(0x009A8C28, uint8) != 0)
|
|
return cost;
|
|
|
|
//
|
|
if (!(flags & 0x20)) {
|
|
// Update money balance
|
|
finance_payment(cost, RCT2_GLOBAL(RCT2_ADDRESS_NEXT_EXPENDITURE_TYPE, uint8) / 4);
|
|
if (RCT2_GLOBAL(0x0141F568, uint8) == RCT2_GLOBAL(0x013CA740, uint8)) {
|
|
// Create a +/- money text effect
|
|
if (cost != 0)
|
|
money_effect_create(cost);
|
|
}
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
}
|
|
|
|
// Error occured
|
|
|
|
// Decrement nest count
|
|
RCT2_GLOBAL(0x009A8C28, uint8)--;
|
|
|
|
// Show error window
|
|
if (RCT2_GLOBAL(0x009A8C28, uint8) == 0 && (flags & GAME_COMMAND_FLAG_APPLY) && RCT2_GLOBAL(0x0141F568, uint8) == RCT2_GLOBAL(0x013CA740, uint8) && !(flags & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED))
|
|
window_error_open(RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TITLE, uint16), RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16));
|
|
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
void pause_toggle()
|
|
{
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GAME_PAUSED, uint32) ^= 1;
|
|
window_invalidate_by_class(WC_TOP_TOOLBAR);
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_GAME_PAUSED, uint32) & 1) {
|
|
pause_sounds();
|
|
unpause_sounds();
|
|
} else {
|
|
unpause_sounds();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00667C15
|
|
*/
|
|
void game_pause_toggle(int *eax, int *ebx, int *ecx, int *edx, int *esi, int *edi, int *ebp)
|
|
{
|
|
if (*ebx & GAME_COMMAND_FLAG_APPLY)
|
|
pause_toggle();
|
|
|
|
*ebx = 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066DB5F
|
|
*/
|
|
static void game_load_or_quit(int *eax, int *ebx, int *ecx, int *edx, int *esi, int *edi, int *ebp)
|
|
{
|
|
if (*ebx & GAME_COMMAND_FLAG_APPLY) {
|
|
switch (*edx & 0xFF) {
|
|
case 0:
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SAVE_PROMPT_MODE, uint16) = *edi & 0xFF;
|
|
window_save_prompt_open();
|
|
break;
|
|
case 1:
|
|
window_close_by_class(WC_SAVE_PROMPT);
|
|
break;
|
|
default:
|
|
game_load_or_quit_no_save_prompt();
|
|
break;
|
|
}
|
|
}
|
|
*ebx = 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00674F40
|
|
*/
|
|
static int open_landscape_file_dialog()
|
|
{
|
|
int result;
|
|
format_string((char*)RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, STR_LOAD_LANDSCAPE_DIALOG_TITLE, 0);
|
|
safe_strncpy((char*)0x0141EF68, (char*)RCT2_ADDRESS_LANDSCAPES_PATH, MAX_PATH);
|
|
format_string((char*)0x0141EE68, STR_RCT2_LANDSCAPE_FILE, 0);
|
|
pause_sounds();
|
|
result = platform_open_common_file_dialog(1, (char*)RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, (char*)0x0141EF68, "*.SV6;*.SV4;*.SC6", (char*)0x0141EE68);
|
|
unpause_sounds();
|
|
// window_proc
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00674EB6
|
|
*/
|
|
static int open_load_game_dialog()
|
|
{
|
|
int result;
|
|
format_string((char*)RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, STR_LOAD_GAME_DIALOG_TITLE, 0);
|
|
safe_strncpy((char*)0x0141EF68, (char*)RCT2_ADDRESS_SAVED_GAMES_PATH, MAX_PATH);
|
|
format_string((char*)0x0141EE68, STR_RCT2_SAVED_GAME, 0);
|
|
pause_sounds();
|
|
result = platform_open_common_file_dialog(1, (char*)RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, (char*)0x0141EF68, "*.SV6", (char*)0x0141EE68);
|
|
unpause_sounds();
|
|
// window_proc
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066DC0F
|
|
*/
|
|
static void load_landscape()
|
|
{
|
|
window_loadsave_open(LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE, NULL);
|
|
return;
|
|
|
|
if (open_landscape_file_dialog() == 0) {
|
|
gfx_invalidate_screen();
|
|
} else {
|
|
// Set default filename
|
|
char *esi = (char*)0x0141EF67;
|
|
while (1) {
|
|
esi++;
|
|
if (*esi == '.')
|
|
break;
|
|
if (*esi != 0)
|
|
continue;
|
|
strcpy(esi, ".SC6");
|
|
break;
|
|
}
|
|
safe_strncpy((char*)RCT2_ADDRESS_SAVED_GAMES_PATH_2, (char*)0x0141EF68, MAX_PATH);
|
|
|
|
editor_load_landscape((char*)0x0141EF68);
|
|
if (1) {
|
|
gfx_invalidate_screen();
|
|
rct2_endupdate();
|
|
} else {
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_AGE, uint16) = 0;
|
|
rct2_endupdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts all the user strings and news item strings to UTF-8.
|
|
*/
|
|
void game_convert_strings_to_utf8()
|
|
{
|
|
utf8 buffer[512];
|
|
|
|
// User strings
|
|
for (int i = 0; i < MAX_USER_STRINGS; i++) {
|
|
utf8 *userString = &gUserStrings[i * USER_STRING_MAX_LENGTH];
|
|
|
|
if (!str_is_null_or_empty(userString)) {
|
|
rct2_to_utf8(buffer, userString);
|
|
memcpy(userString, buffer, 31);
|
|
userString[31] = 0;
|
|
}
|
|
}
|
|
|
|
// News items
|
|
for (int i = 0; i < MAX_NEWS_ITEMS; i++) {
|
|
rct_news_item *newsItem = news_item_get(i);
|
|
|
|
if (!str_is_null_or_empty(newsItem->text)) {
|
|
rct2_to_utf8(buffer, newsItem->text);
|
|
memcpy(newsItem->text, buffer, 255);
|
|
newsItem->text[255] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts all the user strings and news item strings to RCT2 encoding.
|
|
*/
|
|
void game_convert_strings_to_rct2(rct_s6_data *s6)
|
|
{
|
|
char buffer[512];
|
|
|
|
// User strings
|
|
for (int i = 0; i < MAX_USER_STRINGS; i++) {
|
|
char *userString = &s6->custom_strings[i * USER_STRING_MAX_LENGTH];
|
|
|
|
if (!str_is_null_or_empty(userString)) {
|
|
utf8_to_rct2(buffer, userString);
|
|
memcpy(userString, buffer, 31);
|
|
userString[31] = 0;
|
|
}
|
|
}
|
|
|
|
// News items
|
|
for (int i = 0; i < MAX_NEWS_ITEMS; i++) {
|
|
rct_news_item *newsItem = &s6->news_items[i];
|
|
|
|
if (!str_is_null_or_empty(newsItem->text)) {
|
|
utf8_to_rct2(buffer, newsItem->text);
|
|
memcpy(newsItem->text, buffer, 255);
|
|
newsItem->text[255] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00675E1B
|
|
*/
|
|
int game_load_sv6(SDL_RWops* rw)
|
|
{
|
|
int i, j;
|
|
|
|
if (!sawyercoding_validate_checksum(rw)) {
|
|
log_error("invalid checksum");
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_ERROR_TYPE, uint8) = 255;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TITLE, uint16) = STR_FILE_CONTAINS_INVALID_DATA;
|
|
return 0;
|
|
}
|
|
|
|
rct_s6_header *s6Header = (rct_s6_header*)0x009E34E4;
|
|
rct_s6_info *s6Info = (rct_s6_info*)0x0141F570;
|
|
|
|
// Read first chunk
|
|
sawyercoding_read_chunk(rw, (uint8*)s6Header);
|
|
if (s6Header->type == S6_TYPE_SAVEDGAME) {
|
|
// Read packed objects
|
|
if (s6Header->num_packed_objects > 0) {
|
|
j = 0;
|
|
for (i = 0; i < s6Header->num_packed_objects; i++)
|
|
j += object_load_packed(rw);
|
|
if (j > 0)
|
|
object_list_load();
|
|
}
|
|
}
|
|
|
|
uint8 load_success = object_read_and_load_entries(rw);
|
|
|
|
// Read flags (16 bytes)
|
|
sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_MONTH_YEAR);
|
|
|
|
// Read map elements
|
|
memset((void*)RCT2_ADDRESS_MAP_ELEMENTS, 0, MAX_MAP_ELEMENTS * sizeof(rct_map_element));
|
|
sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_MAP_ELEMENTS);
|
|
|
|
// Read game data, including sprites
|
|
sawyercoding_read_chunk(rw, (uint8*)0x010E63B8);
|
|
|
|
if (!load_success){
|
|
set_load_objects_fail_reason();
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) & INPUT_FLAG_5){
|
|
//call 0x0040705E Sets cursor position and something else. Calls maybe wind func 8 probably pointless
|
|
RCT2_GLOBAL(0x14241BC, uint32) = 0;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) &= ~INPUT_FLAG_5;
|
|
}
|
|
|
|
return 0;//This never gets called
|
|
}
|
|
|
|
// The rest is the same as in scenario_load
|
|
reset_loaded_objects();
|
|
map_update_tile_pointers();
|
|
reset_0x69EBE4();
|
|
openrct2_reset_object_tween_locations();
|
|
game_convert_strings_to_utf8();
|
|
game_fix_save_vars(); // OpenRCT2 fix broken save games
|
|
|
|
return 1;
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GUESTS_IN_PARK, uint16) = peepCount;
|
|
|
|
// Fixes broken saves where a surface element could be null
|
|
for (int y = 0; y < 256; y++) {
|
|
for (int x = 0; x < 256; x++) {
|
|
rct_map_element *mapElement = map_get_surface_element_at(x, y);
|
|
|
|
if (mapElement == NULL)
|
|
{
|
|
log_error("Null map element at x = %d and y = %d. Fixing...", x, y);
|
|
map_element_insert(x, y, 14, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load game state for multiplayer
|
|
int game_load_network(SDL_RWops* rw)
|
|
{
|
|
int i, j;
|
|
|
|
rct_s6_header *s6Header = (rct_s6_header*)0x009E34E4;
|
|
rct_s6_info *s6Info = (rct_s6_info*)0x0141F570;
|
|
|
|
// Read first chunk
|
|
sawyercoding_read_chunk(rw, (uint8*)s6Header);
|
|
if (s6Header->type == S6_TYPE_SAVEDGAME) {
|
|
// Read packed objects
|
|
if (s6Header->num_packed_objects > 0) {
|
|
j = 0;
|
|
for (i = 0; i < s6Header->num_packed_objects; i++)
|
|
j += object_load_packed(rw);
|
|
if (j > 0)
|
|
object_list_load();
|
|
}
|
|
}
|
|
|
|
uint8 load_success = object_read_and_load_entries(rw);
|
|
|
|
// Read flags (16 bytes)
|
|
sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_CURRENT_MONTH_YEAR);
|
|
|
|
// Read map elements
|
|
memset((void*)RCT2_ADDRESS_MAP_ELEMENTS, 0, MAX_MAP_ELEMENTS * sizeof(rct_map_element));
|
|
sawyercoding_read_chunk(rw, (uint8*)RCT2_ADDRESS_MAP_ELEMENTS);
|
|
|
|
// Read game data, including sprites
|
|
sawyercoding_read_chunk(rw, (uint8*)0x010E63B8);
|
|
|
|
// Read checksum
|
|
uint32 checksum;
|
|
SDL_RWread(rw, &checksum, sizeof(uint32), 1);
|
|
|
|
// Read other data not in normal save files
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GAME_PAUSED, uint32) = SDL_ReadLE32(rw);
|
|
|
|
if (!load_success){
|
|
set_load_objects_fail_reason();
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) & INPUT_FLAG_5){
|
|
//call 0x0040705E Sets cursor position and something else. Calls maybe wind func 8 probably pointless
|
|
RCT2_GLOBAL(0x14241BC, uint32) = 0;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) &= ~INPUT_FLAG_5;
|
|
}
|
|
|
|
return 0;//This never gets called
|
|
}
|
|
|
|
// The rest is the same as in scenario load and play
|
|
reset_loaded_objects();
|
|
map_update_tile_pointers();
|
|
reset_0x69EBE4();
|
|
openrct2_reset_object_tween_locations();
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00675E1B
|
|
*/
|
|
int game_load_save(const char *path)
|
|
{
|
|
log_verbose("loading saved game, %s", path);
|
|
|
|
safe_strncpy((char*)0x0141EF68, path, MAX_PATH);
|
|
safe_strncpy((char*)RCT2_ADDRESS_SAVED_GAMES_PATH_2, path, MAX_PATH);
|
|
|
|
safe_strncpy(gScenarioSavePath, path, MAX_PATH);
|
|
|
|
SDL_RWops* rw = SDL_RWFromFile(path, "rb");
|
|
if (rw == NULL) {
|
|
log_error("unable to open %s", path);
|
|
RCT2_GLOBAL(RCT2_ADDRESS_ERROR_TYPE, uint8) = 255;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TITLE, uint16) = STR_FILE_CONTAINS_INVALID_DATA;
|
|
return 0;
|
|
}
|
|
|
|
if (!game_load_sv6(rw)) {
|
|
title_load();
|
|
rct2_endupdate();
|
|
SDL_RWclose(rw);
|
|
return 0;
|
|
}
|
|
SDL_RWclose(rw);
|
|
|
|
game_load_init();
|
|
if (network_get_mode() == NETWORK_MODE_SERVER) {
|
|
network_send_map();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void game_load_init()
|
|
{
|
|
rct_window *mainWindow;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_PLAYING;
|
|
viewport_init_all();
|
|
game_create_windows();
|
|
mainWindow = window_get_main();
|
|
|
|
mainWindow->viewport_target_sprite = -1;
|
|
mainWindow->saved_view_x = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_X, sint16);
|
|
mainWindow->saved_view_y = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_Y, sint16);
|
|
uint8 _cl = (RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, sint16) & 0xFF) - mainWindow->viewport->zoom;
|
|
mainWindow->viewport->zoom = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, sint16) & 0xFF;
|
|
*((char*)(&RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_ROTATION, sint32))) = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, sint16) >> 8;
|
|
if (_cl != 0) {
|
|
if (_cl < 0) {
|
|
_cl = -_cl;
|
|
mainWindow->viewport->view_width >>= _cl;
|
|
mainWindow->viewport->view_height >>= _cl;
|
|
} else {
|
|
mainWindow->viewport->view_width <<= _cl;
|
|
mainWindow->viewport->view_height <<= _cl;
|
|
}
|
|
}
|
|
mainWindow->saved_view_x -= mainWindow->viewport->view_width >> 1;
|
|
mainWindow->saved_view_y -= mainWindow->viewport->view_height >> 1;
|
|
window_invalidate(mainWindow);
|
|
|
|
reset_all_sprite_quadrant_placements();
|
|
scenery_set_default_placement_configuration();
|
|
window_new_ride_init_vars();
|
|
RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_UPDATE_TICKS, uint16) = 0;
|
|
if (RCT2_GLOBAL(0x0013587C4, uint32) == 0) // this check is not in scenario play
|
|
finance_update_loan_hash();
|
|
|
|
load_palette();
|
|
gfx_invalidate_screen();
|
|
window_update_all();
|
|
|
|
gGameSpeed = 1;
|
|
|
|
scenario_set_filename((char*)0x0135936C);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* rct2: 0x0069E9A7
|
|
* Call after a rotation or loading of a save to reset sprite quadrants
|
|
*/
|
|
void reset_all_sprite_quadrant_placements()
|
|
{
|
|
for (rct_sprite* spr = g_sprite_list; spr < (rct_sprite*)RCT2_ADDRESS_SPRITES_NEXT_INDEX; spr++)
|
|
if (spr->unknown.sprite_identifier != 0xFF)
|
|
sprite_move(spr->unknown.x, spr->unknown.y, spr->unknown.z, spr);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066DBB7
|
|
*/
|
|
static void load_game()
|
|
{
|
|
window_loadsave_open(LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME, NULL);
|
|
return;
|
|
|
|
if (open_load_game_dialog() == 0) {
|
|
gfx_invalidate_screen();
|
|
} else {
|
|
// Set default filename
|
|
char *esi = (char*)0x0141EF67;
|
|
while (1) {
|
|
esi++;
|
|
if (*esi == '.')
|
|
break;
|
|
if (*esi != 0)
|
|
continue;
|
|
strcpy(esi, ".SV6");
|
|
break;
|
|
}
|
|
safe_strncpy((char*)RCT2_ADDRESS_SAVED_GAMES_PATH_2, (char*)0x0141EF68, MAX_PATH);
|
|
|
|
if (game_load_save((char *)0x0141EF68)) {
|
|
gfx_invalidate_screen();
|
|
rct2_endupdate();
|
|
} else {
|
|
RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_AGE, uint16) = 0;
|
|
rct2_endupdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006750E9
|
|
*/
|
|
static int show_save_game_dialog(char *resultPath)
|
|
{
|
|
rct_s6_info *s6Info = (rct_s6_info*)0x0141F570;
|
|
|
|
int result;
|
|
char title[256];
|
|
char filename[MAX_PATH];
|
|
char filterName[256];
|
|
|
|
format_string(title, STR_SAVE_GAME_1040, NULL);
|
|
safe_strncpy(filename, RCT2_ADDRESS(RCT2_ADDRESS_SAVED_GAMES_PATH_2, char), MAX_PATH);
|
|
format_string(filterName, STR_RCT2_SAVED_GAME, NULL);
|
|
|
|
pause_sounds();
|
|
result = platform_open_common_file_dialog(0, title, filename, "*.SV6", filterName);
|
|
unpause_sounds();
|
|
|
|
if (result)
|
|
safe_strncpy(resultPath, filename, MAX_PATH);
|
|
return result;
|
|
}
|
|
|
|
void save_game()
|
|
{
|
|
if (!gFirstTimeSave) {
|
|
log_verbose("Saving to %s", gScenarioSavePath);
|
|
|
|
SDL_RWops* rw = SDL_RWFromFile(gScenarioSavePath, "wb+");
|
|
if (rw != NULL) {
|
|
scenario_save(rw, 0x80000000);
|
|
log_verbose("Saved to %s", gScenarioSavePath);
|
|
SDL_RWclose(rw);
|
|
}
|
|
} else {
|
|
save_game_as();
|
|
}
|
|
|
|
}
|
|
void save_game_as()
|
|
{
|
|
window_loadsave_open(LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME, gScenarioSavePath);
|
|
}
|
|
|
|
|
|
void game_autosave()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
utf8 backupPath[MAX_PATH];
|
|
|
|
platform_get_user_directory(path, "save");
|
|
safe_strncpy(backupPath, path, MAX_PATH);
|
|
|
|
strcat(path, "autosave.sv6");
|
|
strcat(backupPath, "autosave.sv6.bak");
|
|
|
|
if (platform_file_exists(path)) {
|
|
platform_file_copy(path, backupPath, true);
|
|
}
|
|
|
|
SDL_RWops* rw = SDL_RWFromFile(path, "wb+");
|
|
if (rw != NULL) {
|
|
scenario_save(rw, 0x80000000);
|
|
SDL_RWclose(rw);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E3838
|
|
*/
|
|
void rct2_exit_reason(rct_string_id title, rct_string_id body){
|
|
// Before this would set a quit message
|
|
|
|
char exit_title[255];
|
|
format_string(exit_title, title, 0);
|
|
|
|
char exit_body[255];
|
|
format_string(exit_body, body, 0);
|
|
|
|
log_error(exit_title);
|
|
log_error(exit_body);
|
|
|
|
rct2_exit();
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E3879
|
|
*/
|
|
void rct2_exit()
|
|
{
|
|
//audio_close();
|
|
//Post quit message does not work in 0x6e3879 as its windows only.
|
|
openrct2_finish();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066DB79
|
|
*/
|
|
void game_load_or_quit_no_save_prompt()
|
|
{
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_SAVE_PROMPT_MODE, uint16) < 1) {
|
|
game_do_command(0, 1, 0, 1, GAME_COMMAND_LOAD_OR_QUIT, 0, 0);
|
|
tool_cancel();
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) & 2)
|
|
load_landscape();
|
|
else
|
|
load_game();
|
|
} else if (RCT2_GLOBAL(RCT2_ADDRESS_SAVE_PROMPT_MODE, uint16) == 1) {
|
|
game_do_command(0, 1, 0, 1, GAME_COMMAND_LOAD_OR_QUIT, 0, 0);
|
|
tool_cancel();
|
|
if (RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) & INPUT_FLAG_5) {
|
|
// RCT2_CALLPROC_EBPSAFE(0x0040705E); Function not required resets cursor position.
|
|
RCT2_GLOBAL(RCT2_ADDRESS_INPUT_FLAGS, uint32) &= ~INPUT_FLAG_5;
|
|
}
|
|
gGameSpeed = 1;
|
|
title_load();
|
|
rct2_endupdate();
|
|
} else {
|
|
rct2_exit();
|
|
}
|
|
}
|
|
|
|
static GAME_COMMAND_POINTER* new_game_command_table[62] = {
|
|
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_peep_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_fence,
|
|
game_command_remove_fence,
|
|
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_fence_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
|
|
};
|