mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-19 04:53:12 +01:00
This commit changes the land rights button in the park window to be disabled if there are no more land rights and construction rights for sale. It also disables the individual land rights and construction rights buttons in the land rights window itself if that specific type of tile is no longer for sale. This is implemented by introducing two new global variables called gLandRemainingOwnershipSales and gLandRemainingConstructionSales. These are updated by map_count_remaining_land_rights() to contain the number of land rights and construction rights that remain for sale. This function is called when a scenario is loaded and whenever the game command to buy land rights or construction rights is invoked. It also introduces three new tooltip strings that explain why the buttons are disabled.
1017 lines
27 KiB
C
1017 lines
27 KiB
C
#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 "cheats.h"
|
|
#include "config.h"
|
|
#include "game.h"
|
|
#include "world/climate.h"
|
|
#include "interface/viewport.h"
|
|
#include "localisation/date.h"
|
|
#include "localisation/localisation.h"
|
|
#include "management/award.h"
|
|
#include "management/finance.h"
|
|
#include "management/marketing.h"
|
|
#include "management/research.h"
|
|
#include "management/news_item.h"
|
|
#include "network/network.h"
|
|
#include "object.h"
|
|
#include "object_list.h"
|
|
#include "openrct2.h"
|
|
#include "peep/staff.h"
|
|
#include "platform/platform.h"
|
|
#include "ride/ride.h"
|
|
#include "scenario.h"
|
|
#include "ScenarioRepository.h"
|
|
#include "ScenarioSources.h"
|
|
#include "title.h"
|
|
#include "util/sawyercoding.h"
|
|
#include "util/util.h"
|
|
#include "world/map.h"
|
|
#include "world/park.h"
|
|
#include "world/scenery.h"
|
|
#include "world/sprite.h"
|
|
#include "world/water.h"
|
|
|
|
const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = {
|
|
STR_BEGINNER_PARKS,
|
|
STR_CHALLENGING_PARKS,
|
|
STR_EXPERT_PARKS,
|
|
STR_REAL_PARKS,
|
|
STR_OTHER_PARKS,
|
|
|
|
STR_DLC_PARKS,
|
|
STR_BUILD_YOUR_OWN_PARKS,
|
|
};
|
|
|
|
static char _scenarioPath[MAX_PATH];
|
|
const char *_scenarioFileName = "";
|
|
|
|
rct_s6_info gS6Info;
|
|
char gScenarioName[64];
|
|
char gScenarioDetails[256];
|
|
char gScenarioCompletedBy[32];
|
|
char gScenarioSavePath[MAX_PATH];
|
|
char gScenarioExpansionPacks[3256];
|
|
int gFirstTimeSave = 1;
|
|
uint16 gSavedAge;
|
|
uint32 gLastAutoSaveTick = 0;
|
|
|
|
#if defined(NO_RCT2)
|
|
uint32 gScenarioTicks;
|
|
#endif
|
|
uint32 gScenarioSrand0;
|
|
uint32 gScenarioSrand1;
|
|
|
|
uint8 gScenarioObjectiveType;
|
|
uint8 gScenarioObjectiveYear;
|
|
uint16 gScenarioObjectiveNumGuests;
|
|
money32 gScenarioObjectiveCurrency;
|
|
|
|
uint16 gScenarioParkRatingWarningDays;
|
|
money32 gScenarioCompletedCompanyValue;
|
|
money32 gScenarioCompanyValueRecord;
|
|
|
|
static int scenario_create_ducks();
|
|
static void scenario_objective_check();
|
|
|
|
/**
|
|
* Loads only the basic information from a scenario.
|
|
* rct2: 0x006761D6
|
|
*/
|
|
bool scenario_load_basic(const char *path, rct_s6_header *header, rct_s6_info *info)
|
|
{
|
|
log_verbose("loading scenario details, %s", path);
|
|
|
|
SDL_RWops* rw = SDL_RWFromFile(path, "rb");
|
|
if (rw != NULL) {
|
|
// Read first chunk
|
|
size_t loaded_size = sawyercoding_read_chunk_with_size(rw, (uint8*)header, sizeof(rct_s6_header));
|
|
if (loaded_size != sizeof(rct_s6_header)) {
|
|
log_error("Failed to read header from scenario %s", path);
|
|
SDL_RWclose(rw);
|
|
return false;
|
|
}
|
|
if (header->type == S6_TYPE_SCENARIO) {
|
|
// Read second chunk
|
|
loaded_size = sawyercoding_read_chunk_with_size(rw, (uint8*)info, sizeof(rct_s6_info));
|
|
SDL_RWclose(rw);
|
|
if (loaded_size != sizeof(rct_s6_info)) {
|
|
log_error("Failed to read info from scenario %s", path);
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
log_error("invalid scenario, %s", path);
|
|
SDL_RWclose(rw);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
log_error("unable to open scenario, %s", path);
|
|
return false;
|
|
}
|
|
|
|
int scenario_load_and_play_from_path(const char *path)
|
|
{
|
|
window_close_construction_windows();
|
|
|
|
if (!scenario_load(path))
|
|
return 0;
|
|
|
|
reset_sprite_spatial_index();
|
|
reset_all_sprite_quadrant_placements();
|
|
|
|
size_t len = strnlen(path, MAX_PATH) + 1;
|
|
safe_strcpy(_scenarioPath, path, len);
|
|
if (len - 1 == MAX_PATH)
|
|
{
|
|
_scenarioPath[MAX_PATH - 1] = '\0';
|
|
log_warning("truncated string %s", _scenarioPath);
|
|
}
|
|
_scenarioFileName = path_get_filename(_scenarioPath);
|
|
|
|
gFirstTimeSave = 1;
|
|
|
|
log_verbose("starting scenario, %s", path);
|
|
scenario_begin();
|
|
if (network_get_mode() == NETWORK_MODE_SERVER) {
|
|
network_send_map();
|
|
}
|
|
if (network_get_mode() == NETWORK_MODE_CLIENT) {
|
|
network_close();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void scenario_begin()
|
|
{
|
|
rct_window *mainWindow;
|
|
|
|
audio_stop_title_music();
|
|
|
|
gScreenFlags = SCREEN_FLAGS_PLAYING;
|
|
audio_stop_all_music_and_sounds();
|
|
viewport_init_all();
|
|
game_create_windows();
|
|
mainWindow = window_get_main();
|
|
|
|
mainWindow->viewport_target_sprite = -1;
|
|
mainWindow->saved_view_x = gSavedViewX;
|
|
mainWindow->saved_view_y = gSavedViewY;
|
|
|
|
uint8 zoomDifference = gSavedViewZoom - mainWindow->viewport->zoom;
|
|
mainWindow->viewport->zoom = gSavedViewZoom;
|
|
gCurrentRotation = gSavedViewRotation;
|
|
if (zoomDifference != 0) {
|
|
if (zoomDifference < 0) {
|
|
zoomDifference = -zoomDifference;
|
|
mainWindow->viewport->view_width >>= zoomDifference;
|
|
mainWindow->viewport->view_height >>= zoomDifference;
|
|
} else {
|
|
mainWindow->viewport->view_width <<= zoomDifference;
|
|
mainWindow->viewport->view_height <<= zoomDifference;
|
|
}
|
|
}
|
|
mainWindow->saved_view_x -= mainWindow->viewport->view_width >> 1;
|
|
mainWindow->saved_view_y -= mainWindow->viewport->view_height >> 1;
|
|
window_invalidate(mainWindow);
|
|
|
|
reset_all_sprite_quadrant_placements();
|
|
window_new_ride_init_vars();
|
|
|
|
// Set the scenario pseudo-random seeds
|
|
gScenarioSrand0 ^= platform_get_ticks();
|
|
gScenarioSrand1 ^= platform_get_ticks();
|
|
|
|
gWindowUpdateTicks = 0;
|
|
gParkFlags &= ~PARK_FLAGS_NO_MONEY;
|
|
if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO)
|
|
gParkFlags |= PARK_FLAGS_NO_MONEY;
|
|
sub_684AC3();
|
|
scenery_set_default_placement_configuration();
|
|
news_item_init_queue();
|
|
if (gScenarioObjectiveType != OBJECTIVE_NONE)
|
|
window_park_objective_open();
|
|
|
|
gParkRating = calculate_park_rating();
|
|
gParkValue = calculate_park_value();
|
|
gCompanyValue = calculate_company_value();
|
|
gHistoricalProfit = gInitialCash - gBankLoan;
|
|
gCashEncrypted = ENCRYPT_MONEY(gInitialCash);
|
|
|
|
safe_strcpy(gScenarioDetails, gS6Info.details, 256);
|
|
safe_strcpy(gScenarioName, gS6Info.name, 64);
|
|
|
|
{
|
|
utf8 normalisedName[64];
|
|
scenario_normalise_name(normalisedName, sizeof(normalisedName), gS6Info.name);
|
|
|
|
rct_string_id localisedStringIds[3];
|
|
if (language_get_localised_scenario_strings(normalisedName, localisedStringIds)) {
|
|
if (localisedStringIds[0] != STR_NONE) {
|
|
safe_strcpy(gScenarioName, language_get_string(localisedStringIds[0]), 32);
|
|
}
|
|
if (localisedStringIds[1] != STR_NONE) {
|
|
park_set_name(language_get_string(localisedStringIds[1]));
|
|
}
|
|
if (localisedStringIds[2] != STR_NONE) {
|
|
safe_strcpy(gScenarioDetails, language_get_string(localisedStringIds[2]), 256);
|
|
}
|
|
} else {
|
|
rct_stex_entry* stex = g_stexEntries[0];
|
|
if ((intptr_t)stex != -1) {
|
|
char *buffer = gCommonStringFormatBuffer;
|
|
|
|
// Set localised park name
|
|
format_string(buffer, 256, stex->park_name, 0);
|
|
park_set_name(buffer);
|
|
|
|
// Set localised scenario name
|
|
format_string(buffer, 256, stex->scenario_name, 0);
|
|
safe_strcpy(gScenarioName, buffer, 64);
|
|
|
|
// Set localised scenario details
|
|
format_string(buffer, 256, stex->details, 0);
|
|
safe_strcpy(gScenarioDetails, buffer, 256);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the last saved game path
|
|
char parkName[128];
|
|
format_string(parkName, 128, gParkName, &gParkNameArgs);
|
|
|
|
platform_get_user_directory(gScenarioSavePath, "save", sizeof(gScenarioSavePath));
|
|
safe_strcat_path(gScenarioSavePath, parkName, sizeof(gScenarioSavePath));
|
|
path_append_extension(gScenarioSavePath, ".sv6", sizeof(gScenarioSavePath));
|
|
|
|
safe_strcpy(gRCT2AddressSavedGamesPath2, gRCT2AddressSavedGamesPath, MAX_PATH);
|
|
safe_strcat_path(gRCT2AddressSavedGamesPath2, gScenarioSavePath, MAX_PATH);
|
|
path_append_extension(gRCT2AddressSavedGamesPath2, ".SV6", MAX_PATH);
|
|
|
|
gCurrentExpenditure = 0;
|
|
gCurrentProfit = 0;
|
|
gWeeklyProfitAverageDividend = 0;
|
|
gWeeklyProfitAverageDivisor = 0;
|
|
gScenarioCompletedCompanyValue = MONEY32_UNDEFINED;
|
|
gTotalAdmissions = 0;
|
|
gTotalIncomeFromAdmissions = 0;
|
|
safe_strcpy(gScenarioCompletedBy, "?", sizeof(gScenarioCompletedBy));
|
|
park_reset_history();
|
|
finance_reset_history();
|
|
award_reset();
|
|
reset_all_ride_build_dates();
|
|
date_reset();
|
|
duck_remove_all();
|
|
park_calculate_size();
|
|
map_count_remaining_land_rights();
|
|
staff_reset_stats();
|
|
gLastEntranceStyle = RIDE_ENTRANCE_STYLE_PLAIN;
|
|
memset(gMarketingCampaignDaysLeft, 0, 20);
|
|
gParkRatingCasualtyPenalty = 0;
|
|
|
|
// Open park with free entry when there is no money
|
|
if (gParkFlags & PARK_FLAGS_NO_MONEY) {
|
|
gParkFlags |= PARK_FLAGS_PARK_OPEN;
|
|
gParkEntranceFee = 0;
|
|
}
|
|
|
|
gParkFlags |= PARK_FLAGS_18;
|
|
|
|
load_palette();
|
|
|
|
gfx_invalidate_screen();
|
|
gScreenAge = 0;
|
|
gGameSpeed = 1;
|
|
}
|
|
|
|
static void scenario_end()
|
|
{
|
|
rct_window* w;
|
|
window_close_by_class(WC_DROPDOWN);
|
|
|
|
for (w = g_window_list; w < gWindowNextSlot; w++){
|
|
if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)))
|
|
window_close(w);
|
|
}
|
|
window_park_objective_open();
|
|
}
|
|
|
|
void scenario_set_filename(const char *value)
|
|
{
|
|
substitute_path(_scenarioPath, sizeof(_scenarioPath), gRCT2AddressScenariosPath, value);
|
|
_scenarioFileName = path_get_filename(_scenarioPath);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066A752
|
|
*/
|
|
void scenario_failure()
|
|
{
|
|
gScenarioCompletedCompanyValue = 0x80000001;
|
|
scenario_end();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066A75E
|
|
*/
|
|
void scenario_success()
|
|
{
|
|
const money32 companyValue = gCompanyValue;
|
|
|
|
gScenarioCompletedCompanyValue = companyValue;
|
|
peep_applause();
|
|
|
|
if (scenario_repository_try_record_highscore(_scenarioFileName, companyValue, NULL))
|
|
{
|
|
// Allow name entry
|
|
gParkFlags |= PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
|
|
gScenarioCompanyValueRecord = companyValue;
|
|
}
|
|
scenario_end();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006695E8
|
|
*/
|
|
void scenario_success_submit_name(const char *name)
|
|
{
|
|
if (scenario_repository_try_record_highscore(_scenarioFileName, gScenarioCompanyValueRecord, name))
|
|
{
|
|
safe_strcpy(gScenarioCompletedBy, name, 32);
|
|
}
|
|
gParkFlags &= ~PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
|
|
}
|
|
|
|
/**
|
|
* Send a warning when entrance price is too high.
|
|
* rct2: 0x0066A80E
|
|
*/
|
|
static void scenario_entrance_fee_too_high_check()
|
|
{
|
|
uint16 x = 0, y = 0;
|
|
money16 totalRideValue = gTotalRideValue;
|
|
money16 max_fee = totalRideValue + (totalRideValue / 2);
|
|
|
|
if ((gParkFlags & PARK_FLAGS_PARK_OPEN) && park_get_entrance_fee() > max_fee) {
|
|
for (int i = 0; gParkEntranceX[i] != SPRITE_LOCATION_NULL; i++) {
|
|
x = gParkEntranceX[i] + 16;
|
|
y = gParkEntranceY[i] + 16;
|
|
}
|
|
|
|
uint32 packed_xy = (y << 16) | x;
|
|
if (gConfigNotifications.park_warnings) {
|
|
news_item_add_to_queue(NEWS_ITEM_BLANK, STR_ENTRANCE_FEE_TOO_HI, packed_xy);
|
|
}
|
|
}
|
|
}
|
|
|
|
void scenario_autosave_check()
|
|
{
|
|
// Milliseconds since last save
|
|
uint32 timeSinceSave = SDL_GetTicks() - gLastAutoSaveTick;
|
|
|
|
bool shouldSave = false;
|
|
switch (gConfigGeneral.autosave_frequency) {
|
|
case AUTOSAVE_EVERY_MINUTE:
|
|
shouldSave = timeSinceSave >= 1 * 60 * 1000;
|
|
break;
|
|
case AUTOSAVE_EVERY_5MINUTES:
|
|
shouldSave = timeSinceSave >= 5 * 60 * 1000;
|
|
break;
|
|
case AUTOSAVE_EVERY_15MINUTES:
|
|
shouldSave = timeSinceSave >= 15 * 60 * 1000;
|
|
break;
|
|
case AUTOSAVE_EVERY_30MINUTES:
|
|
shouldSave = timeSinceSave >= 30 * 60 * 1000;
|
|
break;
|
|
case AUTOSAVE_EVERY_HOUR:
|
|
shouldSave = timeSinceSave >= 60 * 60 * 1000;
|
|
break;
|
|
}
|
|
|
|
if (shouldSave) {
|
|
gLastAutoSaveTick = SDL_GetTicks();
|
|
game_autosave();
|
|
}
|
|
}
|
|
|
|
static void scenario_day_update()
|
|
{
|
|
finance_update_daily_profit();
|
|
peep_update_days_in_queue();
|
|
switch (gScenarioObjectiveType) {
|
|
case OBJECTIVE_10_ROLLERCOASTERS:
|
|
case OBJECTIVE_GUESTS_AND_RATING:
|
|
case OBJECTIVE_10_ROLLERCOASTERS_LENGTH:
|
|
case OBJECTIVE_FINISH_5_ROLLERCOASTERS:
|
|
case OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE:
|
|
scenario_objective_check();
|
|
break;
|
|
}
|
|
|
|
// Lower the casualty penalty
|
|
uint16 casualtyPenaltyModifier = (gParkFlags & PARK_FLAGS_NO_MONEY) ? 40 : 7;
|
|
gParkRatingCasualtyPenalty = max(0, gParkRatingCasualtyPenalty - casualtyPenaltyModifier);
|
|
|
|
gToolbarDirtyFlags |= BTM_TB_DIRTY_FLAG_DATE;
|
|
}
|
|
|
|
static void scenario_week_update()
|
|
{
|
|
int month = gDateMonthsElapsed & 7;
|
|
|
|
finance_pay_wages();
|
|
finance_pay_research();
|
|
finance_pay_interest();
|
|
marketing_update();
|
|
peep_problem_warnings_update();
|
|
ride_check_all_reachable();
|
|
ride_update_favourited_stat();
|
|
|
|
rct_water_type* water_type = (rct_water_type*)object_entry_groups[OBJECT_TYPE_WATER].chunks[0];
|
|
|
|
if (month <= MONTH_APRIL && (intptr_t)water_type != -1 && water_type->var_0E & 1) {
|
|
// 100 attempts at finding some water to create a few ducks at
|
|
for (int i = 0; i < 100; i++) {
|
|
if (scenario_create_ducks())
|
|
break;
|
|
}
|
|
}
|
|
park_update_histories();
|
|
park_calculate_size();
|
|
}
|
|
|
|
static void scenario_fortnight_update()
|
|
{
|
|
finance_pay_ride_upkeep();
|
|
}
|
|
|
|
static void scenario_month_update()
|
|
{
|
|
finance_shift_expenditure_table();
|
|
scenario_objective_check();
|
|
scenario_entrance_fee_too_high_check();
|
|
award_update_all();
|
|
}
|
|
|
|
static void scenario_update_daynight_cycle()
|
|
{
|
|
float currentDayNightCycle = gDayNightCycle;
|
|
gDayNightCycle = 0;
|
|
|
|
if (gScreenFlags == SCREEN_FLAGS_PLAYING && gConfigGeneral.day_night_cycle) {
|
|
float monthFraction = gDateMonthTicks / (float)0x10000;
|
|
if (monthFraction < (1 / 8.0f)) {
|
|
gDayNightCycle = 0.0f;
|
|
} else if (monthFraction < (3 / 8.0f)) {
|
|
gDayNightCycle = (monthFraction - (1 / 8.0f)) / (2 / 8.0f);
|
|
} else if (monthFraction < (5 / 8.0f)) {
|
|
gDayNightCycle = 1.0f;
|
|
} else if (monthFraction < (7 / 8.0f)) {
|
|
gDayNightCycle = 1.0f - ((monthFraction - (5 / 8.0f)) / (2 / 8.0f));
|
|
} else {
|
|
gDayNightCycle = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Only update palette if day / night cycle has changed
|
|
if (gDayNightCycle != currentDayNightCycle) {
|
|
platform_update_palette(gGamePalette, 10, 236);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scenario and finance related update iteration.
|
|
* rct2: 0x006C44B1
|
|
*/
|
|
void scenario_update()
|
|
{
|
|
if (!(gScreenFlags & ~SCREEN_FLAGS_PLAYING)) {
|
|
uint32 currentMonthTick = gDateMonthTicks;
|
|
uint32 nextMonthTick = currentMonthTick + 4;
|
|
uint8 currentMonth = gDateMonthsElapsed & 7;
|
|
uint8 currentDaysInMonth = (uint8)days_in_month[currentMonth];
|
|
|
|
if ((currentDaysInMonth * nextMonthTick) >> 16 != (currentDaysInMonth * currentMonthTick) >> 16) {
|
|
scenario_day_update();
|
|
}
|
|
if (nextMonthTick % 0x4000 == 0) {
|
|
scenario_week_update();
|
|
}
|
|
if (nextMonthTick % 0x8000 == 0) {
|
|
scenario_fortnight_update();
|
|
}
|
|
|
|
gDateMonthTicks = (uint16)nextMonthTick;
|
|
if (nextMonthTick >= 0x10000) {
|
|
gDateMonthsElapsed++;
|
|
scenario_month_update();
|
|
}
|
|
}
|
|
|
|
scenario_update_daynight_cycle();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006744A9
|
|
*/
|
|
static int scenario_create_ducks()
|
|
{
|
|
int i, j, r, c, x, y, waterZ, centreWaterZ, x2, y2;
|
|
|
|
r = scenario_rand();
|
|
x = ((r >> 16) & 0xFFFF) & 0x7F;
|
|
y = (r & 0xFFFF) & 0x7F;
|
|
x = (x + 64) * 32;
|
|
y = (y + 64) * 32;
|
|
|
|
if (!map_is_location_in_park(x, y))
|
|
return 0;
|
|
|
|
centreWaterZ = (map_element_height(x, y) >> 16) & 0xFFFF;
|
|
if (centreWaterZ == 0)
|
|
return 0;
|
|
|
|
// Check 7x7 area around centre tile
|
|
x2 = x - (32 * 3);
|
|
y2 = y - (32 * 3);
|
|
c = 0;
|
|
for (i = 0; i < 7; i++) {
|
|
for (j = 0; j < 7; j++) {
|
|
waterZ = (map_element_height(x2, y2) >> 16) & 0xFFFF;
|
|
if (waterZ == centreWaterZ)
|
|
c++;
|
|
|
|
x2 += 32;
|
|
}
|
|
x2 -= 224;
|
|
y2 += 32;
|
|
}
|
|
|
|
// Must be at least 25 water tiles of the same height in 7x7 area
|
|
if (c < 25)
|
|
return 0;
|
|
|
|
// Set x, y to the centre of the tile
|
|
x += 16;
|
|
y += 16;
|
|
c = (scenario_rand() & 3) + 2;
|
|
for (i = 0; i < c; i++) {
|
|
r = scenario_rand();
|
|
x2 = (r >> 16) & 0x7F;
|
|
y2 = (r & 0xFFFF) & 0x7F;
|
|
create_duck(x + x2 - 64, y + y2 - 64);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E37D2
|
|
*
|
|
* @return eax
|
|
*/
|
|
unsigned int scenario_rand()
|
|
{
|
|
#ifdef DEBUG_DESYNC
|
|
if (!gInUpdateCode) {
|
|
log_warning("scenario_rand called from outside game update");
|
|
assert(false);
|
|
}
|
|
#endif
|
|
|
|
uint32 originalSrand0 = gScenarioSrand0;
|
|
gScenarioSrand0 += ror32(gScenarioSrand1 ^ 0x1234567F, 7);
|
|
return gScenarioSrand1 = ror32(originalSrand0, 3);
|
|
}
|
|
|
|
unsigned int scenario_rand_max(unsigned int max)
|
|
{
|
|
if (max < 2) return 0;
|
|
if ((max & (max - 1)) == 0)
|
|
return scenario_rand() & (max - 1);
|
|
unsigned int rand, cap = ~((unsigned int)0) - (~((unsigned int)0) % max) - 1;
|
|
do {
|
|
rand = scenario_rand();
|
|
} while (rand > cap);
|
|
return rand % max;
|
|
}
|
|
|
|
/**
|
|
* Prepare rides, for the finish five rollercoasters objective.
|
|
* rct2: 0x006788F7
|
|
*/
|
|
static void scenario_prepare_rides_for_save()
|
|
{
|
|
int i;
|
|
rct_ride *ride;
|
|
map_element_iterator it;
|
|
|
|
int isFiveCoasterObjective = gScenarioObjectiveType == OBJECTIVE_FINISH_5_ROLLERCOASTERS;
|
|
|
|
// Set all existing track to be indestructible
|
|
map_element_iterator_begin(&it);
|
|
do {
|
|
if (map_element_get_type(it.element) == MAP_ELEMENT_TYPE_TRACK) {
|
|
if (isFiveCoasterObjective)
|
|
it.element->flags |= 0x40;
|
|
else
|
|
it.element->flags &= ~0x40;
|
|
}
|
|
} while (map_element_iterator_next(&it));
|
|
|
|
// Set all existing rides to have indestructible track
|
|
FOR_ALL_RIDES(i, ride) {
|
|
if (isFiveCoasterObjective)
|
|
ride->lifecycle_flags |= RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK;
|
|
else
|
|
ride->lifecycle_flags &= ~RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006726C7
|
|
*/
|
|
int scenario_prepare_for_save()
|
|
{
|
|
gS6Info.entry.flags = 255;
|
|
|
|
rct_stex_entry* stex = g_stexEntries[0];
|
|
if ((intptr_t)stex != -1) {
|
|
char buffer[256];
|
|
format_string(buffer, 256, stex->scenario_name, NULL);
|
|
safe_strcpy(gS6Info.name, buffer, sizeof(gS6Info.name));
|
|
|
|
memcpy(&gS6Info.entry, &object_entry_groups[OBJECT_TYPE_SCENARIO_TEXT].entries[0], sizeof(rct_object_entry));
|
|
}
|
|
|
|
if (gS6Info.name[0] == 0)
|
|
format_string(gS6Info.name, 64, gParkName, &gParkNameArgs);
|
|
|
|
gS6Info.objective_type = gScenarioObjectiveType;
|
|
gS6Info.objective_arg_1 = gScenarioObjectiveYear;
|
|
gS6Info.objective_arg_2 = gScenarioObjectiveCurrency;
|
|
gS6Info.objective_arg_3 = gScenarioObjectiveNumGuests;
|
|
|
|
scenario_prepare_rides_for_save();
|
|
|
|
if (gScenarioObjectiveType == OBJECTIVE_GUESTS_AND_RATING)
|
|
gParkFlags |= PARK_FLAGS_PARK_OPEN;
|
|
|
|
// Fix #2385: saved scenarios did not initialise temperatures to selected climate
|
|
climate_reset(gClimate);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006AA039
|
|
*/
|
|
static int scenario_write_available_objects(FILE *file)
|
|
{
|
|
const int totalEntries = OBJECT_ENTRY_COUNT;
|
|
const int bufferLength = totalEntries * sizeof(rct_object_entry);
|
|
|
|
// Initialise buffers
|
|
uint8 *buffer = malloc(bufferLength);
|
|
if (buffer == NULL) {
|
|
log_error("out of memory");
|
|
return 0;
|
|
}
|
|
uint8 *dstBuffer = malloc(bufferLength + sizeof(sawyercoding_chunk_header));
|
|
if (dstBuffer == NULL) {
|
|
free(buffer);
|
|
log_error("out of memory");
|
|
return 0;
|
|
}
|
|
|
|
// Write entries
|
|
rct_object_entry *dstEntry = (rct_object_entry*)buffer;
|
|
for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) {
|
|
void *entryData = get_loaded_object_chunk(i);
|
|
if (entryData == (void*)-1) {
|
|
memset(dstEntry, 0xFF, sizeof(rct_object_entry));
|
|
} else {
|
|
*dstEntry = *get_loaded_object_entry(i);
|
|
}
|
|
dstEntry++;
|
|
}
|
|
|
|
// Write chunk
|
|
sawyercoding_chunk_header chunkHeader;
|
|
chunkHeader.encoding = CHUNK_ENCODING_ROTATE;
|
|
chunkHeader.length = bufferLength;
|
|
size_t encodedLength = sawyercoding_write_chunk_buffer(dstBuffer, buffer, chunkHeader);
|
|
fwrite(dstBuffer, encodedLength, 1, file);
|
|
|
|
// Free buffers
|
|
free(dstBuffer);
|
|
free(buffer);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Modifies the given S6 data so that ghost elements, rides with no track elements or unused banners / user strings are saved.
|
|
*/
|
|
void scenario_fix_ghosts(rct_s6_data *s6)
|
|
{
|
|
// Remove all ghost elements
|
|
rct_map_element *destinationElement = s6->map_elements;
|
|
|
|
for (int y = 0; y < 256; y++) {
|
|
for (int x = 0; x < 256; x++) {
|
|
rct_map_element *originalElement = map_get_first_element_at(x, y);
|
|
do {
|
|
if (originalElement->flags & MAP_ELEMENT_FLAG_GHOST) {
|
|
int bannerIndex = map_element_get_banner_index(originalElement);
|
|
if (bannerIndex != -1) {
|
|
rct_banner *banner = &s6->banners[bannerIndex];
|
|
if (banner->type != BANNER_NULL) {
|
|
banner->type = BANNER_NULL;
|
|
if (is_user_string_id(banner->string_idx))
|
|
s6->custom_strings[(banner->string_idx % MAX_USER_STRINGS) * USER_STRING_MAX_LENGTH] = 0;
|
|
}
|
|
}
|
|
} else {
|
|
*destinationElement++ = *originalElement;
|
|
}
|
|
} while (!map_element_is_last_for_tile(originalElement++));
|
|
|
|
// Set last element flag in case the original last element was never added
|
|
(destinationElement - 1)->flags |= MAP_ELEMENT_FLAG_LAST_TILE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void scenario_remove_trackless_rides(rct_s6_data *s6)
|
|
{
|
|
bool rideHasTrack[MAX_RIDES];
|
|
ride_all_has_any_track_elements(rideHasTrack);
|
|
for (int i = 0; i < MAX_RIDES; i++) {
|
|
rct_ride *ride = &s6->rides[i];
|
|
|
|
if (rideHasTrack[i] || ride->type == RIDE_TYPE_NULL) {
|
|
continue;
|
|
}
|
|
|
|
ride->type = RIDE_TYPE_NULL;
|
|
if (is_user_string_id(ride->name)) {
|
|
s6->custom_strings[(ride->name % MAX_USER_STRINGS) * USER_STRING_MAX_LENGTH] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void scenario_objective_check_guests_by()
|
|
{
|
|
uint8 objectiveYear = gScenarioObjectiveYear;
|
|
sint16 parkRating = gParkRating;
|
|
sint16 guestsInPark = gNumGuestsInPark;
|
|
sint16 objectiveGuests = gScenarioObjectiveNumGuests;
|
|
sint16 currentMonthYear = gDateMonthsElapsed;
|
|
|
|
if (currentMonthYear == 8 * objectiveYear){
|
|
if (parkRating >= 600 && guestsInPark >= objectiveGuests)
|
|
scenario_success();
|
|
else
|
|
scenario_failure();
|
|
}
|
|
}
|
|
|
|
static void scenario_objective_check_park_value_by()
|
|
{
|
|
uint8 objectiveYear = gScenarioObjectiveYear;
|
|
sint16 currentMonthYear = gDateMonthsElapsed;
|
|
money32 objectiveParkValue = gScenarioObjectiveCurrency;
|
|
money32 parkValue = gParkValue;
|
|
|
|
if (currentMonthYear == 8 * objectiveYear) {
|
|
if (parkValue >= objectiveParkValue)
|
|
scenario_success();
|
|
else
|
|
scenario_failure();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if there are 10 rollercoasters of different subtype with
|
|
* excitement >= 600 .
|
|
* rct2:
|
|
**/
|
|
static void scenario_objective_check_10_rollercoasters()
|
|
{
|
|
int i, rcs = 0;
|
|
uint8 type_already_counted[256];
|
|
rct_ride* ride;
|
|
|
|
memset(type_already_counted, 0, 256);
|
|
|
|
FOR_ALL_RIDES(i, ride) {
|
|
uint8 subtype_id = ride->subtype;
|
|
rct_ride_entry *rideType = get_ride_entry(subtype_id);
|
|
if (rideType == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (rideType != NULL &&
|
|
(rideType->category[0] == RIDE_GROUP_ROLLERCOASTER || rideType->category[1] == RIDE_GROUP_ROLLERCOASTER) &&
|
|
ride->status == RIDE_STATUS_OPEN &&
|
|
ride->excitement >= RIDE_RATING(6,00) && type_already_counted[subtype_id] == 0){
|
|
type_already_counted[subtype_id]++;
|
|
rcs++;
|
|
}
|
|
}
|
|
|
|
if (rcs >= 10)
|
|
scenario_success();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0066A13C
|
|
*/
|
|
static void scenario_objective_check_guests_and_rating()
|
|
{
|
|
if (gParkRating < 700 && gDateMonthsElapsed >= 1) {
|
|
gScenarioParkRatingWarningDays++;
|
|
if (gScenarioParkRatingWarningDays == 1) {
|
|
if (gConfigNotifications.park_rating_warnings) {
|
|
news_item_add_to_queue(NEWS_ITEM_GRAPH, STR_PARK_RATING_WARNING_4_WEEKS_REMAINING, 0);
|
|
}
|
|
} else if (gScenarioParkRatingWarningDays == 8) {
|
|
if (gConfigNotifications.park_rating_warnings) {
|
|
news_item_add_to_queue(NEWS_ITEM_GRAPH, STR_PARK_RATING_WARNING_3_WEEKS_REMAINING, 0);
|
|
}
|
|
} else if (gScenarioParkRatingWarningDays == 15) {
|
|
if (gConfigNotifications.park_rating_warnings) {
|
|
news_item_add_to_queue(NEWS_ITEM_GRAPH, STR_PARK_RATING_WARNING_2_WEEKS_REMAINING, 0);
|
|
}
|
|
} else if (gScenarioParkRatingWarningDays == 22) {
|
|
if (gConfigNotifications.park_rating_warnings) {
|
|
news_item_add_to_queue(NEWS_ITEM_GRAPH, STR_PARK_RATING_WARNING_1_WEEK_REMAINING, 0);
|
|
}
|
|
} else if (gScenarioParkRatingWarningDays == 29) {
|
|
news_item_add_to_queue(NEWS_ITEM_GRAPH, STR_PARK_HAS_BEEN_CLOSED_DOWN, 0);
|
|
gParkFlags &= ~PARK_FLAGS_PARK_OPEN;
|
|
scenario_failure();
|
|
gGuestInitialHappiness = 50;
|
|
}
|
|
} else if (gScenarioCompletedCompanyValue != 0x80000001) {
|
|
gScenarioParkRatingWarningDays = 0;
|
|
}
|
|
|
|
if (gParkRating >= 700)
|
|
if (gNumGuestsInPark >= gScenarioObjectiveNumGuests)
|
|
scenario_success();
|
|
}
|
|
|
|
static void scenario_objective_check_monthly_ride_income()
|
|
{
|
|
money32 *expenditureLastMonth = &gExpenditureTable[1 * RCT_EXPENDITURE_TYPE_COUNT];
|
|
money32 lastMonthRideIncome = expenditureLastMonth[RCT_EXPENDITURE_TYPE_PARK_RIDE_TICKETS];
|
|
if (lastMonthRideIncome >= gScenarioObjectiveCurrency) {
|
|
scenario_success();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if there are 10 rollercoasters of different subtype with
|
|
* excitement > 700 and a minimum length;
|
|
* rct2: 0x0066A6B5
|
|
*/
|
|
static void scenario_objective_check_10_rollercoasters_length()
|
|
{
|
|
int i, rcs = 0;
|
|
uint8 type_already_counted[256];
|
|
sint16 objective_length = gScenarioObjectiveNumGuests;
|
|
rct_ride* ride;
|
|
|
|
memset(type_already_counted, 0, 256);
|
|
|
|
FOR_ALL_RIDES(i, ride) {
|
|
uint8 subtype_id = ride->subtype;
|
|
rct_ride_entry *rideType = get_ride_entry(subtype_id);
|
|
if (rideType == NULL) {
|
|
continue;
|
|
}
|
|
if ((rideType->category[0] == RIDE_GROUP_ROLLERCOASTER || rideType->category[1] == RIDE_GROUP_ROLLERCOASTER) &&
|
|
ride->status == RIDE_STATUS_OPEN &&
|
|
ride->excitement >= RIDE_RATING(7,00) && type_already_counted[subtype_id] == 0){
|
|
|
|
if ((ride_get_total_length(ride) >> 16) > objective_length) {
|
|
type_already_counted[subtype_id]++;
|
|
rcs++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rcs >= 10)
|
|
scenario_success();
|
|
}
|
|
|
|
static void scenario_objective_check_finish_5_rollercoasters()
|
|
{
|
|
int i;
|
|
rct_ride* ride;
|
|
|
|
money32 objectiveRideExcitement = gScenarioObjectiveCurrency;
|
|
|
|
// ORIGINAL BUG?:
|
|
// This does not check if the rides are even rollercoasters nevermind the right rollercoasters to be finished.
|
|
// It also did not exclude null rides.
|
|
int rcs = 0;
|
|
FOR_ALL_RIDES(i, ride)
|
|
if (ride->status != RIDE_STATUS_CLOSED && ride->excitement >= objectiveRideExcitement)
|
|
rcs++;
|
|
|
|
if (rcs >= 5)
|
|
scenario_success();
|
|
}
|
|
|
|
static void scenario_objective_check_replay_loan_and_park_value()
|
|
{
|
|
money32 objectiveParkValue = gScenarioObjectiveCurrency;
|
|
money32 parkValue = gParkValue;
|
|
money32 currentLoan = gBankLoan;
|
|
|
|
if (currentLoan <= 0 && parkValue >= objectiveParkValue)
|
|
scenario_success();
|
|
}
|
|
|
|
static void scenario_objective_check_monthly_food_income()
|
|
{
|
|
money32 *expenditureLastMonth = &gExpenditureTable[1 * RCT_EXPENDITURE_TYPE_COUNT];
|
|
sint32 lastMonthProfit =
|
|
expenditureLastMonth[RCT_EXPENDITURE_TYPE_SHOP_SHOP_SALES] +
|
|
expenditureLastMonth[RCT_EXPENDITURE_TYPE_SHOP_STOCK] +
|
|
expenditureLastMonth[RCT_EXPENDITURE_TYPE_FOODDRINK_SALES] +
|
|
expenditureLastMonth[RCT_EXPENDITURE_TYPE_FOODDRINK_STOCK];
|
|
|
|
if (lastMonthProfit >= gScenarioObjectiveCurrency) {
|
|
scenario_success();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the win/lose conditions of the current objective.
|
|
* rct2: 0x0066A4B2
|
|
*/
|
|
static void scenario_objective_check()
|
|
{
|
|
if (gScenarioCompletedCompanyValue != MONEY32_UNDEFINED) {
|
|
return;
|
|
}
|
|
|
|
switch (gScenarioObjectiveType) {
|
|
case OBJECTIVE_GUESTS_BY:
|
|
scenario_objective_check_guests_by();
|
|
break;
|
|
case OBJECTIVE_PARK_VALUE_BY:
|
|
scenario_objective_check_park_value_by();
|
|
break;
|
|
case OBJECTIVE_10_ROLLERCOASTERS:
|
|
scenario_objective_check_10_rollercoasters();
|
|
break;
|
|
case OBJECTIVE_GUESTS_AND_RATING:
|
|
scenario_objective_check_guests_and_rating();
|
|
break;
|
|
case OBJECTIVE_MONTHLY_RIDE_INCOME:
|
|
scenario_objective_check_monthly_ride_income();
|
|
break;
|
|
case OBJECTIVE_10_ROLLERCOASTERS_LENGTH:
|
|
scenario_objective_check_10_rollercoasters_length();
|
|
break;
|
|
case OBJECTIVE_FINISH_5_ROLLERCOASTERS:
|
|
scenario_objective_check_finish_5_rollercoasters();
|
|
break;
|
|
case OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE:
|
|
scenario_objective_check_replay_loan_and_park_value();
|
|
break;
|
|
case OBJECTIVE_MONTHLY_FOOD_INCOME:
|
|
scenario_objective_check_monthly_food_income();
|
|
break;
|
|
}
|
|
}
|