1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-19 04:53:12 +01:00
Files
OpenRCT2/src/scenario.c
Alexander Overvoorde bb059a2741 Resolves #4559: Changes land rights buttons to be disabled if no tiles for sale remain
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.
2016-10-31 20:21:10 +01:00

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;
}
}