OpenRCT2/src/openrct2.c

473 lines
13 KiB
C
Raw Normal View History

#pragma region Copyright (c) 2014-2016 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 "audio/mixer.h"
#include "config.h"
#include "editor.h"
#include "game.h"
2015-07-19 01:57:42 +02:00
#include "interface/chat.h"
#include "interface/themes.h"
2015-07-04 21:00:32 +02:00
#include "interface/window.h"
2015-07-09 19:00:46 +02:00
#include "interface/viewport.h"
#include "intro.h"
#include "localisation/localisation.h"
2015-05-25 21:36:40 +02:00
#include "network/http.h"
2015-02-12 12:30:57 +01:00
#include "network/network.h"
#include "object_list.h"
#include "openrct2.h"
#include "platform/crash.h"
#include "platform/platform.h"
#include "rct2/interop.h"
2015-07-10 21:29:50 +02:00
#include "ride/ride.h"
#include "title.h"
#include "util/util.h"
#include "version.h"
2015-02-08 04:19:24 +01:00
#include "world/mapgen.h"
int gExitCode;
2016-09-17 22:07:17 +02:00
int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE;
2015-08-01 17:40:15 +02:00
utf8 gOpenRCT2StartupActionPath[512] = { 0 };
utf8 gExePath[MAX_PATH];
utf8 gCustomUserDataPath[MAX_PATH] = { 0 };
utf8 gCustomOpenrctDataPath[MAX_PATH] = { 0 };
utf8 gCustomRCT2DataPath[MAX_PATH] = { 0 };
utf8 gCustomPassword[MAX_PATH] = { 0 };
2015-05-29 21:45:21 +02:00
// This should probably be changed later and allow a custom selection of things to initialise like SDL_INIT
bool gOpenRCT2Headless = false;
2015-06-13 14:30:50 +02:00
bool gOpenRCT2ShowChangelog;
bool gOpenRCT2SilentBreakpad;
2015-06-13 14:30:50 +02:00
#ifndef DISABLE_NETWORK
// OpenSSL's message digest context used for calculating sprite checksums
EVP_MD_CTX *gHashCTX = NULL;
#endif // DISABLE_NETWORK
2014-10-09 15:31:51 +02:00
/** If set, will end the OpenRCT2 game loop. Intentially private to this module so that the flag can not be set back to 0. */
int _finished;
// Used for object movement tweening
static rct_xyz16 _spritelocations1[MAX_SPRITES], _spritelocations2[MAX_SPRITES];
2014-10-09 15:31:51 +02:00
static void openrct2_loop();
void openrct2_write_full_version_info(utf8 *buffer, size_t bufferSize)
{
utf8 *ch = buffer;
// Name and version
safe_strcpy(ch, OPENRCT2_NAME ", v" OPENRCT2_VERSION, bufferSize - (ch - buffer));
ch = strchr(ch, '\0');
// Build information
if (!str_is_null_or_empty(gGitBranch)) {
snprintf(ch, bufferSize - (ch - buffer), "-%s", gGitBranch);
ch = strchr(ch, '\0');
}
if (!str_is_null_or_empty(gCommitSha1Short)) {
snprintf(ch, bufferSize - (ch - buffer), " build %s", gCommitSha1Short);
ch = strchr(ch, '\0');
}
if (!str_is_null_or_empty(gBuildServer)) {
snprintf(ch, bufferSize - (ch - buffer), " provided by %s", gBuildServer);
ch = strchr(ch, '\0');
}
#if DEBUG
snprintf(ch, bufferSize - (ch - buffer), " (DEBUG)");
#endif
}
static void openrct2_copy_files_over(const utf8 *originalDirectory, const utf8 *newDirectory, const utf8 *extension)
{
utf8 *ch, filter[MAX_PATH], oldPath[MAX_PATH], newPath[MAX_PATH];
int fileEnumHandle;
file_info fileInfo;
if (!platform_ensure_directory_exists(newDirectory)) {
log_error("Could not create directory %s.", newDirectory);
return;
}
// Create filter path
safe_strcpy(filter, originalDirectory, sizeof(filter));
ch = strchr(filter, '*');
if (ch != NULL)
*ch = 0;
safe_strcat_path(filter, "*", sizeof(filter));
path_append_extension(filter, extension, sizeof(filter));
fileEnumHandle = platform_enumerate_files_begin(filter);
while (platform_enumerate_files_next(fileEnumHandle, &fileInfo)) {
safe_strcpy(newPath, newDirectory, sizeof(newPath));
safe_strcat_path(newPath, fileInfo.path, sizeof(newPath));
safe_strcpy(oldPath, originalDirectory, sizeof(oldPath));
ch = strchr(oldPath, '*');
if (ch != NULL)
*ch = 0;
safe_strcat_path(oldPath, fileInfo.path, sizeof(oldPath));
if (!platform_file_exists(newPath))
platform_file_copy(oldPath, newPath, false);
}
platform_enumerate_files_end(fileEnumHandle);
fileEnumHandle = platform_enumerate_directories_begin(originalDirectory);
while (platform_enumerate_directories_next(fileEnumHandle, filter)) {
safe_strcpy(newPath, newDirectory, sizeof(newPath));
safe_strcat_path(newPath, filter, sizeof(newPath));
2016-01-18 20:49:52 +01:00
safe_strcpy(oldPath, originalDirectory, MAX_PATH);
ch = strchr(oldPath, '*');
if (ch != NULL)
*ch = 0;
safe_strcat_path(oldPath, filter, sizeof(oldPath));
if (!platform_ensure_directory_exists(newPath)) {
log_error("Could not create directory %s.", newPath);
return;
}
openrct2_copy_files_over(oldPath, newPath, extension);
}
platform_enumerate_directories_end(fileEnumHandle);
}
static void openrct2_set_exe_path()
{
platform_get_exe_path(gExePath, sizeof(gExePath));
log_verbose("Setting exe path to %s", gExePath);
}
/**
* Copy saved games and landscapes to user directory
*/
static void openrct2_copy_original_user_files_over()
{
utf8 path[MAX_PATH];
platform_get_user_directory(path, "save", sizeof(path));
2016-06-19 23:47:06 +02:00
openrct2_copy_files_over((utf8*)gRCT2AddressSavedGamesPath, path, ".sv6");
platform_get_user_directory(path, "landscape", sizeof(path));
2016-06-19 23:47:06 +02:00
openrct2_copy_files_over((utf8*)gRCT2AddressLandscapesPath, path, ".sc6");
}
bool openrct2_initialise()
{
utf8 userPath[MAX_PATH];
#ifndef DISABLE_NETWORK
gHashCTX = EVP_MD_CTX_create();
assert(gHashCTX != NULL);
#endif // DISABLE_NETWORK
platform_resolve_openrct_data_path();
platform_resolve_user_data_path();
platform_get_user_directory(userPath, NULL, sizeof(userPath));
if (!platform_ensure_directory_exists(userPath)) {
log_fatal("Could not create user directory (do you have write access to your documents folder?)");
return false;
}
crash_init();
if (!rct2_interop_setup_segment()) {
2015-09-24 20:03:11 +02:00
log_fatal("Unable to load RCT2 data sector");
return false;
Make it work on Linux Right now the project is decompiled to the point where it is feasible to try porting it to another platform. It doesn't work 100% correctly, but it's nearing this state. To port it to Linux I mmapped the openrct2.exe into expected places, disabled two offending calls (RCT2_CALLPROC_EBPSAFE(0x0040701D) and RCT2_CALLPROC_X(0x006E7FF3…)), replaced memory management routines with generic ones and removed all the function-pointer calls. A basic, non-exhaustive check is included to verify that memory is loaded correctly in place. That last bit is probably the most intrusive one, but had to be done, as the calling convention on Linux differs from the one on Windows. It could possibly be emulated (just like RCT2_CALLFUNC_X) until dependency on exe is dropped. It is possible to completely remove calls out to original code by commenting out contents of RCT2_CALLFUNC_X, right now this will yield working UI, but no rendering of peeps, rides or rest of world. This can be used as a benchmark or test platform for correctness of implementation. The data sections will still be required for now. Assets are expected to be in specific places, so to launch it, following needs to satisified: * $build/data/ has to have contents of $RCT2/Data/ * $build/data/ (same as above) has to have contents of $repo/data/ * $build/ObjData/ has to have contents of $RCT2/ObjData/ * $build/../openrct2.exe has to be $repo/openrct2.exe (as of 976ea0d) Keep in mind you can symlink stuff and that filesystems are case sensitive! You can copy more of required data to possibly improve your experience. Pretty much all of this commit will possibly have to be reverted by the time OpenRCT2 gains independence. Remember to build with -DDISABLE_NETWORK=ON -DDISABLE_HTTP_TWITCH=ON
2015-09-22 23:36:05 +02:00
}
openrct2_set_exe_path();
config_set_defaults();
if (!config_open_default()) {
if (!config_find_or_browse_install_directory()) {
gConfigGeneral.last_run_version = strndup(OPENRCT2_VERSION, strlen(OPENRCT2_VERSION));
config_save_default();
utf8 path[MAX_PATH];
config_get_default_path(path, sizeof(path));
log_fatal("An RCT2 install directory must be specified! Please edit \"game_path\" in %s.", path);
return false;
}
}
2015-06-13 14:30:50 +02:00
gOpenRCT2ShowChangelog = true;
if (gConfigGeneral.last_run_version != NULL && (strcmp(gConfigGeneral.last_run_version, OPENRCT2_VERSION) == 0))
gOpenRCT2ShowChangelog = false;
gConfigGeneral.last_run_version = strndup(OPENRCT2_VERSION, strlen(OPENRCT2_VERSION));
config_save_default();
// TODO add configuration option to allow multiple instances
2015-02-12 12:30:57 +01:00
// if (!gOpenRCT2Headless && !platform_lock_single_instance()) {
// log_fatal("OpenRCT2 is already running.");
// return false;
// }
if (!rct2_init_directories()) {
return false;
}
if (!rct2_startup_checks()) {
return false;
}
2015-05-29 21:45:21 +02:00
if (!gOpenRCT2Headless) {
audio_init();
2015-11-16 23:39:47 +01:00
audio_populate_devices();
2015-05-29 21:45:21 +02:00
}
if (!language_open(gConfigGeneral.language)) {
log_error("Failed to open configured language...");
if (!language_open(LANGUAGE_ENGLISH_UK)) {
log_fatal("Failed to open fallback language...");
return false;
}
}
2015-05-25 21:36:40 +02:00
http_init();
2015-05-30 22:00:13 +02:00
theme_manager_initialise();
2015-06-24 18:22:12 +02:00
title_sequences_set_default();
title_sequences_load_presets();
2015-05-30 22:00:13 +02:00
rct2_interop_setup_hooks();
if (!rct2_init())
return false;
2015-07-19 01:57:42 +02:00
chat_init();
openrct2_copy_original_user_files_over();
return true;
}
/**
* Launches the game, after command line arguments have been parsed and processed.
*/
void openrct2_launch()
{
if (openrct2_initialise()) {
gIntroState = INTRO_STATE_NONE;
if((gOpenRCT2StartupAction == STARTUP_ACTION_TITLE) && gConfigGeneral.play_intro)
gOpenRCT2StartupAction = STARTUP_ACTION_INTRO;
2015-06-03 18:11:19 +02:00
switch (gOpenRCT2StartupAction) {
case STARTUP_ACTION_INTRO:
gIntroState = INTRO_STATE_PUBLISHER_BEGIN;
title_load();
2015-06-03 18:11:19 +02:00
break;
case STARTUP_ACTION_TITLE:
title_load();
2015-06-03 18:11:19 +02:00
break;
case STARTUP_ACTION_OPEN:
assert(gOpenRCT2StartupActionPath != NULL);
if (!rct2_open_file(gOpenRCT2StartupActionPath)) {
fprintf(stderr, "Failed to load '%s'", gOpenRCT2StartupActionPath);
title_load();
break;
}
2015-06-03 18:11:19 +02:00
2016-04-23 12:16:46 +02:00
gScreenFlags = SCREEN_FLAGS_PLAYING;
#ifndef DISABLE_NETWORK
if (gNetworkStart == NETWORK_MODE_SERVER) {
if (gNetworkStartPort == 0) {
gNetworkStartPort = gConfigNetwork.default_port;
}
if (str_is_null_or_empty(gCustomPassword)) {
network_set_password(gConfigNetwork.default_password);
}
else {
network_set_password(gCustomPassword);
}
network_begin_server(gNetworkStartPort);
}
#endif // DISABLE_NETWORK
2015-06-03 18:11:19 +02:00
break;
case STARTUP_ACTION_EDIT:
if (strlen(gOpenRCT2StartupActionPath) == 0) {
editor_load();
} else {
if (!editor_load_landscape(gOpenRCT2StartupActionPath)) {
title_load();
}
2015-06-03 18:11:19 +02:00
}
break;
2015-02-12 12:30:57 +01:00
}
#ifndef DISABLE_NETWORK
2015-07-10 21:53:41 +02:00
if (gNetworkStart == NETWORK_MODE_CLIENT) {
if (gNetworkStartPort == 0) {
gNetworkStartPort = gConfigNetwork.default_port;
}
2015-02-12 12:30:57 +01:00
network_begin_client(gNetworkStartHost, gNetworkStartPort);
}
#endif // DISABLE_NETWORK
2015-02-12 12:30:57 +01:00
openrct2_loop();
}
openrct2_dispose();
// HACK Some threads are still running which causes the game to not terminate. Investigation required!
exit(gExitCode);
}
2015-05-25 21:36:40 +02:00
void openrct2_dispose()
{
2015-02-12 12:30:57 +01:00
network_close();
2015-05-25 21:36:40 +02:00
http_dispose();
language_close_all();
rct2_dispose();
config_release();
#ifndef DISABLE_NETWORK
EVP_MD_CTX_destroy(gHashCTX);
#endif // DISABLE_NETWORK
rct2_interop_dispose();
platform_free();
2014-10-09 15:31:51 +02:00
}
/**
* Determines whether its worth tweening a sprite or not when frame smoothing is on.
*/
static bool sprite_should_tween(rct_sprite *sprite)
{
2016-05-09 01:08:03 +02:00
switch (sprite->unknown.linked_list_type_offset >> 1) {
case SPRITE_LIST_VEHICLE:
case SPRITE_LIST_PEEP:
case SPRITE_LIST_UNKNOWN:
return true;
2016-05-09 01:08:03 +02:00
}
return false;
}
2014-10-09 15:31:51 +02:00
/**
* Run the main game loop until the finished flag is set at 40fps (25ms interval).
*/
static void openrct2_loop()
{
uint32 currentTick, ticksElapsed, lastTick = 0;
static uint32 uncapTick = 0;
2015-07-04 21:00:32 +02:00
static int fps = 0;
static uint32 secondTick = 0;
2014-10-09 15:31:51 +02:00
log_verbose("begin openrct2 loop");
2014-10-09 15:31:51 +02:00
_finished = 0;
do {
bool is_minimised = (SDL_GetWindowFlags(gWindow) & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_HIDDEN)) != 0;
if (gConfigGeneral.uncap_fps && gGameSpeed <= 4 && !gOpenRCT2Headless && !is_minimised) {
2015-07-04 21:00:32 +02:00
currentTick = SDL_GetTicks();
if (uncapTick == 0) {
// Reset sprite locations
2015-07-04 21:00:32 +02:00
uncapTick = SDL_GetTicks();
openrct2_reset_object_tween_locations();
2015-07-04 21:00:32 +02:00
}
2015-08-05 14:01:25 +02:00
// Limit number of updates per loop (any long pauses or debugging can make this update for a very long time)
if (currentTick - uncapTick > 25 * 60) {
uncapTick = currentTick - 25 - 1;
}
2015-10-03 21:22:28 +02:00
platform_process_messages();
2015-07-04 21:00:32 +02:00
while (uncapTick <= currentTick && currentTick - uncapTick > 25) {
// Get the original position of each sprite
store_sprite_locations(_spritelocations1);
// Update the game so the sprite positions update
2015-07-04 21:00:32 +02:00
rct2_update();
// Get the next position of each sprite
store_sprite_locations(_spritelocations2);
2015-07-04 21:00:32 +02:00
uncapTick += 25;
}
// Tween the position of each sprite from the last position to the new position based on the time between the last
// tick and the next tick.
2015-07-04 21:00:32 +02:00
float nudge = 1 - ((float)(currentTick - uncapTick) / 25);
for (uint16 i = 0; i < MAX_SPRITES; i++) {
if (!sprite_should_tween(get_sprite(i)))
2015-07-04 21:00:32 +02:00
continue;
2016-07-25 20:18:35 +02:00
sprite_set_coordinates(
_spritelocations2[i].x + (sint16)((_spritelocations1[i].x - _spritelocations2[i].x) * nudge),
_spritelocations2[i].y + (sint16)((_spritelocations1[i].y - _spritelocations2[i].y) * nudge),
_spritelocations2[i].z + (sint16)((_spritelocations1[i].z - _spritelocations2[i].z) * nudge),
get_sprite(i)
);
invalidate_sprite_2(get_sprite(i));
2015-07-04 21:00:32 +02:00
}
platform_draw();
2015-07-04 21:00:32 +02:00
fps++;
if (SDL_GetTicks() - secondTick >= 1000) {
fps = 0;
secondTick = SDL_GetTicks();
}
// Restore the real positions of the sprites so they aren't left at the mid-tween positions
2015-07-04 21:00:32 +02:00
for (uint16 i = 0; i < MAX_SPRITES; i++) {
if (!sprite_should_tween(get_sprite(i)))
2015-07-04 21:00:32 +02:00
continue;
invalidate_sprite_2(get_sprite(i));
2016-07-25 20:18:35 +02:00
sprite_set_coordinates(_spritelocations2[i].x, _spritelocations2[i].y, _spritelocations2[i].z, get_sprite(i));
2015-07-04 21:00:32 +02:00
}
} else {
uncapTick = 0;
2015-07-04 21:00:32 +02:00
currentTick = SDL_GetTicks();
ticksElapsed = currentTick - lastTick;
if (ticksElapsed < 25) {
2016-10-18 23:13:10 +02:00
SDL_Delay(25 - ticksElapsed);
lastTick += 25;
} else {
lastTick = currentTick;
2015-07-04 21:00:32 +02:00
}
2014-10-09 15:31:51 +02:00
2015-07-04 21:00:32 +02:00
platform_process_messages();
2015-07-04 21:00:32 +02:00
rct2_update();
if (!is_minimised) {
platform_draw();
}
2015-07-04 21:00:32 +02:00
}
2014-10-09 15:31:51 +02:00
} while (!_finished);
}
/**
* Causes the OpenRCT2 game loop to finish.
*/
void openrct2_finish()
{
_finished = 1;
}
void openrct2_reset_object_tween_locations()
{
for (uint16 i = 0; i < MAX_SPRITES; i++) {
_spritelocations1[i].x = _spritelocations2[i].x = get_sprite(i)->unknown.x;
_spritelocations1[i].y = _spritelocations2[i].y = get_sprite(i)->unknown.y;
_spritelocations1[i].z = _spritelocations2[i].z = get_sprite(i)->unknown.z;
}
}