OpenRCT2/src/openrct2/management/Research.cpp

1046 lines
29 KiB
C++
Raw Normal View History

2014-10-05 16:23:52 +02:00
/*****************************************************************************
2020-07-21 15:04:34 +02:00
* Copyright (c) 2014-2020 OpenRCT2 developers
2014-10-05 16:23:52 +02:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2014-10-05 16:23:52 +02:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
2014-10-05 16:23:52 +02:00
*****************************************************************************/
2018-06-22 23:02:14 +02:00
#include "Research.h"
#include "../Game.h"
#include "../OpenRCT2.h"
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include "../actions/ParkSetResearchFundingAction.h"
2017-02-18 16:45:10 +01:00
#include "../config/Config.h"
#include "../core/Guard.hpp"
2018-06-22 23:02:14 +02:00
#include "../core/Memory.hpp"
2018-01-06 01:05:16 +01:00
#include "../interface/Window.h"
2018-01-06 18:32:25 +01:00
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
2018-01-02 20:36:42 +01:00
#include "../object/ObjectList.h"
2018-01-09 10:40:42 +01:00
#include "../rct1/RCT1.h"
2017-12-31 13:21:34 +01:00
#include "../ride/Ride.h"
2018-01-10 00:00:09 +01:00
#include "../ride/RideData.h"
2017-10-16 12:02:23 +02:00
#include "../ride/TrackData.h"
2018-06-22 23:02:14 +02:00
#include "../scenario/Scenario.h"
#include "../util/Util.h"
#include "../world/Park.h"
2018-01-11 10:59:26 +01:00
#include "../world/Scenery.h"
2017-10-06 22:37:06 +02:00
#include "Finance.h"
2018-06-22 23:02:14 +02:00
#include "NewsItem.h"
#include <algorithm>
2018-11-21 23:16:04 +01:00
#include <iterator>
2014-10-05 16:23:52 +02:00
static constexpr const int32_t _researchRate[] = {
0,
160,
250,
400,
};
2014-10-05 16:23:52 +02:00
2018-06-22 23:02:14 +02:00
uint8_t gResearchFundingLevel;
uint8_t gResearchPriorities;
uint16_t gResearchProgress;
2018-06-22 23:02:14 +02:00
uint8_t gResearchProgressStage;
std::optional<ResearchItem> gResearchLastItem;
2018-06-22 23:02:14 +02:00
uint8_t gResearchExpectedMonth;
uint8_t gResearchExpectedDay;
std::optional<ResearchItem> gResearchNextItem;
2016-08-14 17:16:54 +02:00
2019-06-16 23:32:31 +02:00
std::vector<ResearchItem> gResearchItemsUninvented;
std::vector<ResearchItem> gResearchItemsInvented;
2014-10-08 00:42:17 +02:00
// 0x00EE787C
uint8_t gResearchUncompletedCategories;
static bool _researchedRideTypes[RIDE_TYPE_COUNT];
static bool _researchedRideEntries[MAX_RIDE_OBJECTS];
2020-03-13 14:44:34 +01:00
static bool _researchedSceneryItems[SCENERY_TYPE_COUNT][UINT16_MAX];
bool gSilentResearch = false;
/**
*
* rct2: 0x006671AD, part of 0x00667132
*/
void research_reset_items()
{
2019-06-16 23:32:31 +02:00
gResearchItemsUninvented.clear();
gResearchItemsInvented.clear();
}
/**
*
* rct2: 0x00684BAE
*/
void ResearchUpdateUncompletedTypes()
{
int32_t uncompletedResearchTypes = 0;
2019-06-16 23:32:31 +02:00
for (auto const& researchItem : gResearchItemsUninvented)
{
uncompletedResearchTypes |= EnumToFlag(researchItem.category);
}
gResearchUncompletedCategories = uncompletedResearchTypes;
}
/**
*
* rct2: 0x00684D2A
*/
static void research_calculate_expected_date()
{
if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || gResearchFundingLevel == RESEARCH_FUNDING_NONE)
{
gResearchExpectedDay = 255;
}
else
{
int32_t progressRemaining = gResearchProgressStage == RESEARCH_STAGE_COMPLETING_DESIGN ? 0x10000 : 0x20000;
progressRemaining -= gResearchProgress;
int32_t daysRemaining = (progressRemaining / _researchRate[gResearchFundingLevel]) * 128;
2018-06-22 23:02:14 +02:00
int32_t expectedDay = gDateMonthTicks + (daysRemaining & 0xFFFF);
int32_t dayQuotient = expectedDay / 0x10000;
int32_t dayRemainder = expectedDay % 0x10000;
int32_t expectedMonth = date_get_month(gDateMonthsElapsed + dayQuotient + (daysRemaining >> 16));
expectedDay = (dayRemainder * days_in_month[expectedMonth]) >> 16;
2018-06-22 23:02:14 +02:00
gResearchExpectedDay = expectedDay;
gResearchExpectedMonth = expectedMonth;
}
}
2014-10-08 00:42:17 +02:00
static void research_invalidate_related_windows()
{
window_invalidate_by_class(WC_CONSTRUCT_RIDE);
window_invalidate_by_class(WC_RESEARCH);
2014-10-08 00:42:17 +02:00
}
static void research_mark_as_fully_completed()
{
gResearchProgress = 0;
gResearchProgressStage = RESEARCH_STAGE_FINISHED_ALL;
research_invalidate_related_windows();
// Reset funding to 0 if no more rides.
auto gameAction = ParkSetResearchFundingAction(gResearchPriorities, 0);
GameActions::Execute(&gameAction);
}
2014-10-08 00:42:17 +02:00
/**
*
* rct2: 0x00684BE5
*/
static void research_next_design()
{
2019-06-16 23:32:31 +02:00
if (gResearchItemsUninvented.empty())
{
research_mark_as_fully_completed();
2019-06-16 23:32:31 +02:00
return;
}
2019-06-16 23:32:31 +02:00
ResearchItem researchItem;
bool ignoreActiveResearchTypes = false;
auto it = gResearchItemsUninvented.begin();
for (;;)
{
2019-06-16 23:32:31 +02:00
researchItem = *it;
if (it == gResearchItemsUninvented.end())
{
if (!ignoreActiveResearchTypes)
{
2019-06-16 23:32:31 +02:00
ignoreActiveResearchTypes = true;
it = gResearchItemsUninvented.begin();
continue;
}
2021-09-15 22:22:15 +02:00
research_mark_as_fully_completed();
return;
}
2021-09-15 22:22:15 +02:00
if (ignoreActiveResearchTypes || (gResearchPriorities & EnumToFlag(researchItem.category)))
{
break;
}
2019-06-16 23:32:31 +02:00
it++;
}
2019-06-16 23:32:31 +02:00
gResearchNextItem = researchItem;
2018-06-22 23:02:14 +02:00
gResearchProgress = 0;
gResearchProgressStage = RESEARCH_STAGE_DESIGNING;
2019-06-16 23:32:31 +02:00
gResearchItemsUninvented.erase(it);
gResearchItemsInvented.push_back(std::move(researchItem));
research_invalidate_related_windows();
2014-10-08 00:42:17 +02:00
}
2014-10-08 01:42:11 +02:00
/**
*
* rct2: 0x006848D4
*/
void research_finish_item(ResearchItem* researchItem)
2014-10-08 01:42:11 +02:00
{
gResearchLastItem = *researchItem;
research_invalidate_related_windows();
if (researchItem->type == Research::EntryType::Ride)
{
// Ride
uint32_t base_ride_type = researchItem->baseRideType;
ObjectEntryIndex rideEntryIndex = researchItem->entryIndex;
2018-06-22 23:02:14 +02:00
rct_ride_entry* rideEntry = get_ride_entry(rideEntryIndex);
if (rideEntry != nullptr && base_ride_type != RIDE_TYPE_NULL)
{
2021-03-12 23:05:30 +01:00
if (!RideTypeIsValid(base_ride_type))
2021-03-12 22:26:22 +01:00
{
log_warning("Invalid ride type: %d", base_ride_type);
2021-03-12 22:26:22 +01:00
base_ride_type = ride_entry_get_first_non_null_ride_type(rideEntry);
}
rct_string_id availabilityString;
ride_type_set_invented(base_ride_type);
ride_entry_set_invented(rideEntryIndex);
bool seenRideEntry[MAX_RIDE_OBJECTS]{};
2019-06-16 23:32:31 +02:00
for (auto const& researchItem3 : gResearchItemsUninvented)
{
ObjectEntryIndex index = researchItem3.entryIndex;
2019-06-16 23:32:31 +02:00
seenRideEntry[index] = true;
}
for (auto const& researchItem3 : gResearchItemsInvented)
{
ObjectEntryIndex index = researchItem3.entryIndex;
2019-06-16 23:32:31 +02:00
seenRideEntry[index] = true;
}
// RCT2 made non-separated vehicles available at once, by removing all but one from research.
// To ensure old files keep working, look for ride entries not in research, and make them available as well.
for (int32_t i = 0; i < MAX_RIDE_OBJECTS; i++)
{
if (!seenRideEntry[i])
{
2018-06-22 23:02:14 +02:00
rct_ride_entry* rideEntry2 = get_ride_entry(i);
if (rideEntry2 != nullptr)
{
for (uint8_t j = 0; j < MAX_RIDE_TYPES_PER_RIDE_ENTRY; j++)
{
if (rideEntry2->ride_type[j] == base_ride_type)
{
ride_entry_set_invented(i);
break;
}
}
}
}
}
2020-08-27 14:35:37 +02:00
Formatter ft;
// If a vehicle is the first to be invented for its ride type, show the ride type/group name.
// Independently listed vehicles (like all flat rides and shops) should always be announced as such.
if (GetRideTypeDescriptor(base_ride_type).HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY)
|| researchItem->flags & RESEARCH_ENTRY_FLAG_FIRST_OF_TYPE)
{
RideNaming naming = get_ride_naming(base_ride_type, rideEntry);
availabilityString = STR_NEWS_ITEM_RESEARCH_NEW_RIDE_AVAILABLE;
ft.Add<rct_string_id>(naming.Name);
}
// If the vehicle should not be listed separately and it isn't the first to be invented for its ride group,
// report it as a new vehicle for the existing ride group.
else
{
availabilityString = STR_NEWS_ITEM_RESEARCH_NEW_VEHICLE_AVAILABLE;
RideNaming baseRideNaming = get_ride_naming(base_ride_type, rideEntry);
ft.Add<rct_string_id>(baseRideNaming.Name);
ft.Add<rct_string_id>(rideEntry->naming.Name);
}
2017-09-19 12:18:17 +02:00
if (!gSilentResearch)
{
if (gConfigNotifications.ride_researched)
{
2020-08-27 14:35:37 +02:00
News::AddItemToQueue(News::ItemType::Research, availabilityString, researchItem->rawValue, ft);
}
}
research_invalidate_related_windows();
}
}
else
{
// Scenery
2018-06-22 23:02:14 +02:00
rct_scenery_group_entry* sceneryGroupEntry = get_scenery_group_entry(researchItem->entryIndex);
2017-11-20 10:15:52 +01:00
if (sceneryGroupEntry != nullptr)
{
2018-01-10 14:26:03 +01:00
scenery_group_set_invented(researchItem->entryIndex);
2020-08-27 14:35:37 +02:00
Formatter ft;
ft.Add<rct_string_id>(sceneryGroupEntry->name);
if (!gSilentResearch)
{
if (gConfigNotifications.ride_researched)
{
News::AddItemToQueue(
2020-08-27 14:35:37 +02:00
News::ItemType::Research, STR_NEWS_ITEM_RESEARCH_NEW_SCENERY_SET_AVAILABLE, researchItem->rawValue, ft);
}
}
research_invalidate_related_windows();
init_scenery();
}
}
2014-10-08 01:42:11 +02:00
}
2014-10-05 16:23:52 +02:00
/**
*
* rct2: 0x00684C7A
*/
void research_update()
{
int32_t editorScreenFlags, researchLevel, currentResearchProgress;
editorScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER;
if (gScreenFlags & editorScreenFlags)
{
return;
}
if (gCurrentTicks % 32 != 0)
{
return;
}
2018-06-22 23:02:14 +02:00
if ((gParkFlags & PARK_FLAGS_NO_MONEY) && gResearchFundingLevel == RESEARCH_FUNDING_NONE)
{
researchLevel = RESEARCH_FUNDING_NORMAL;
2018-06-22 23:02:14 +02:00
}
else
{
researchLevel = gResearchFundingLevel;
}
currentResearchProgress = gResearchProgress;
currentResearchProgress += _researchRate[researchLevel];
if (currentResearchProgress <= 0xFFFF)
{
gResearchProgress = currentResearchProgress;
}
else
{
switch (gResearchProgressStage)
{
2018-06-22 23:02:14 +02:00
case RESEARCH_STAGE_INITIAL_RESEARCH:
research_next_design();
research_calculate_expected_date();
break;
case RESEARCH_STAGE_DESIGNING:
gResearchProgress = 0;
gResearchProgressStage = RESEARCH_STAGE_COMPLETING_DESIGN;
research_calculate_expected_date();
research_invalidate_related_windows();
break;
case RESEARCH_STAGE_COMPLETING_DESIGN:
research_finish_item(&*gResearchNextItem);
2018-06-22 23:02:14 +02:00
gResearchProgress = 0;
gResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH;
research_calculate_expected_date();
ResearchUpdateUncompletedTypes();
2018-06-22 23:02:14 +02:00
research_invalidate_related_windows();
break;
case RESEARCH_STAGE_FINISHED_ALL:
gResearchFundingLevel = RESEARCH_FUNDING_NONE;
break;
}
}
2014-11-01 01:13:15 +01:00
}
/**
*
* rct2: 0x00684AC3
*/
void research_reset_current_item()
{
set_every_ride_type_not_invented();
set_every_ride_entry_not_invented();
// The following two instructions together make all items not tied to a scenery group available.
2018-01-10 11:03:48 +01:00
set_all_scenery_items_invented();
set_all_scenery_groups_not_invented();
2019-06-16 23:32:31 +02:00
for (auto& researchItem : gResearchItemsInvented)
{
2019-06-16 23:32:31 +02:00
research_finish_item(&researchItem);
}
gResearchLastItem = std::nullopt;
2018-06-22 23:02:14 +02:00
gResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH;
gResearchProgress = 0;
}
/**
*
* rct2: 0x006857FA
*/
static void research_insert_unresearched(ResearchItem&& item)
{
// First check to make sure that entry is not already accounted for
if (item.Exists())
{
return;
}
gResearchItemsUninvented.push_back(std::move(item));
}
/**
*
* rct2: 0x00685826
*/
static void research_insert_researched(ResearchItem&& item)
{
// First check to make sure that entry is not already accounted for
2019-06-16 23:32:31 +02:00
if (item.Exists())
{
2019-06-16 23:32:31 +02:00
return;
}
gResearchItemsInvented.push_back(std::move(item));
}
/**
*
* rct2: 0x006857CF
*/
void ResearchRemove(const ResearchItem& researchItem)
{
2019-06-16 23:32:31 +02:00
for (auto it = gResearchItemsUninvented.begin(); it != gResearchItemsUninvented.end(); it++)
{
2019-06-16 23:32:31 +02:00
auto& researchItem2 = *it;
2021-10-15 16:21:14 +02:00
if (researchItem2 == researchItem)
2019-06-16 23:32:31 +02:00
{
gResearchItemsUninvented.erase(it);
return;
}
}
for (auto it = gResearchItemsInvented.begin(); it != gResearchItemsInvented.end(); it++)
{
auto& researchItem2 = *it;
2021-10-15 16:21:14 +02:00
if (researchItem2 == researchItem)
{
2019-06-17 20:27:34 +02:00
gResearchItemsInvented.erase(it);
return;
}
}
}
void research_insert(ResearchItem&& item, bool researched)
{
if (researched)
{
research_insert_researched(std::move(item));
}
else
{
research_insert_unresearched(std::move(item));
}
}
/**
*
* rct2: 0x00685675
*/
void research_populate_list_random()
{
research_reset_items();
// Rides
for (int32_t i = 0; i < MAX_RIDE_OBJECTS; i++)
{
2018-06-22 23:02:14 +02:00
rct_ride_entry* rideEntry = get_ride_entry(i);
if (rideEntry == nullptr)
{
continue;
}
int32_t researched = (scenario_rand() & 0xFF) > 128;
for (auto rideType : rideEntry->ride_type)
{
if (rideType != RIDE_TYPE_NULL)
{
ResearchCategory category = GetRideTypeDescriptor(rideType).GetResearchCategory();
research_insert_ride_entry(rideType, i, category, researched);
}
}
}
// Scenery
for (uint32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
{
2018-06-22 23:02:14 +02:00
rct_scenery_group_entry* sceneryGroupEntry = get_scenery_group_entry(i);
2017-11-20 10:15:52 +01:00
if (sceneryGroupEntry == nullptr)
{
continue;
}
int32_t researched = (scenario_rand() & 0xFF) > 85;
research_insert_scenery_group_entry(i, researched);
}
}
bool research_insert_ride_entry(uint8_t rideType, ObjectEntryIndex entryIndex, ResearchCategory category, bool researched)
{
2020-04-30 12:34:00 +02:00
if (rideType != RIDE_TYPE_NULL && entryIndex != OBJECT_ENTRY_INDEX_NULL)
{
auto tmpItem = ResearchItem(Research::EntryType::Ride, entryIndex, rideType, category, 0);
research_insert(std::move(tmpItem), researched);
return true;
}
return false;
}
2020-03-20 19:28:39 +01:00
void research_insert_ride_entry(ObjectEntryIndex entryIndex, bool researched)
{
2018-06-22 23:02:14 +02:00
rct_ride_entry* rideEntry = get_ride_entry(entryIndex);
if (rideEntry == nullptr)
return;
for (auto rideType : rideEntry->ride_type)
{
if (rideType != RIDE_TYPE_NULL)
{
ResearchCategory category = GetRideTypeDescriptor(rideType).GetResearchCategory();
research_insert_ride_entry(rideType, entryIndex, category, researched);
}
}
}
2020-04-30 12:34:00 +02:00
bool research_insert_scenery_group_entry(ObjectEntryIndex entryIndex, bool researched)
{
2020-04-30 12:34:00 +02:00
if (entryIndex != OBJECT_ENTRY_INDEX_NULL)
{
auto tmpItem = ResearchItem(
Research::EntryType::Scenery, entryIndex, RIDE_TYPE_NULL, ResearchCategory::SceneryGroup, 0);
research_insert(std::move(tmpItem), researched);
2020-04-30 12:34:00 +02:00
return true;
}
return false;
}
bool ride_type_is_invented(uint32_t rideType)
{
2021-03-12 22:26:22 +01:00
return RideTypeIsValid(rideType) ? _researchedRideTypes[rideType] : false;
}
bool ride_entry_is_invented(int32_t rideEntryIndex)
{
return _researchedRideEntries[rideEntryIndex];
}
void ride_type_set_invented(uint32_t rideType)
{
2021-03-12 22:26:22 +01:00
if (RideTypeIsValid(rideType))
{
_researchedRideTypes[rideType] = true;
}
}
void ride_entry_set_invented(int32_t rideEntryIndex)
{
_researchedRideEntries[rideEntryIndex] = true;
}
2016-07-09 22:06:54 +02:00
2020-03-15 13:28:20 +01:00
bool scenery_is_invented(const ScenerySelection& sceneryItem)
2016-07-09 22:06:54 +02:00
{
if (sceneryItem.SceneryType < SCENERY_TYPE_COUNT)
{
return _researchedSceneryItems[sceneryItem.SceneryType][sceneryItem.EntryIndex];
}
2021-09-15 22:22:15 +02:00
log_warning("Invalid Scenery Type");
return false;
2016-07-09 22:06:54 +02:00
}
2020-03-15 13:28:20 +01:00
void scenery_set_invented(const ScenerySelection& sceneryItem)
2018-01-10 11:03:48 +01:00
{
if (sceneryItem.SceneryType < SCENERY_TYPE_COUNT)
{
_researchedSceneryItems[sceneryItem.SceneryType][sceneryItem.EntryIndex] = true;
}
else
{
log_warning("Invalid Scenery Type");
}
2018-01-10 11:03:48 +01:00
}
2020-03-15 13:28:20 +01:00
void scenery_set_not_invented(const ScenerySelection& sceneryItem)
2018-01-10 11:03:48 +01:00
{
if (sceneryItem.SceneryType < SCENERY_TYPE_COUNT)
{
_researchedSceneryItems[sceneryItem.SceneryType][sceneryItem.EntryIndex] = false;
}
else
{
log_warning("Invalid Scenery Type");
}
2018-01-10 11:03:48 +01:00
}
bool scenery_group_is_invented(int32_t sgIndex)
{
2017-11-19 21:39:13 +01:00
const auto sgEntry = get_scenery_group_entry(sgIndex);
if (sgEntry == nullptr || sgEntry->entry_count == 0)
{
return false;
}
// All scenery is temporarily invented when in the scenario editor
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
{
return true;
}
if (gCheatsIgnoreResearchStatus)
{
return true;
}
return std::none_of(
std::begin(gResearchItemsUninvented), std::end(gResearchItemsUninvented), [sgIndex](const ResearchItem& item) {
return item.type == Research::EntryType::Scenery && item.entryIndex == sgIndex;
});
}
void scenery_group_set_invented(int32_t sgIndex)
2018-01-10 14:26:03 +01:00
{
const auto sgEntry = get_scenery_group_entry(sgIndex);
if (sgEntry != nullptr && sgEntry->entry_count > 0)
{
for (auto i = 0; i < sgEntry->entry_count; i++)
{
auto sceneryEntryIndex = sgEntry->scenery_entries[i];
scenery_set_invented(sceneryEntryIndex);
}
}
}
void set_all_scenery_groups_not_invented()
{
for (int32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; ++i)
{
2018-06-22 23:02:14 +02:00
rct_scenery_group_entry* scenery_set = get_scenery_group_entry(i);
if (scenery_set == nullptr)
{
continue;
}
for (int32_t j = 0; j < scenery_set->entry_count; ++j)
{
scenery_set_not_invented(scenery_set->scenery_entries[j]);
}
}
}
void set_all_scenery_items_invented()
{
2020-03-13 14:44:34 +01:00
for (auto sceneryType = 0; sceneryType < SCENERY_TYPE_COUNT; sceneryType++)
{
std::fill(std::begin(_researchedSceneryItems[sceneryType]), std::end(_researchedSceneryItems[sceneryType]), true);
}
2018-01-10 13:55:30 +01:00
}
void set_all_scenery_items_not_invented()
{
2020-03-13 14:44:34 +01:00
for (auto sceneryType = 0; sceneryType < SCENERY_TYPE_COUNT; sceneryType++)
{
std::fill(std::begin(_researchedSceneryItems[sceneryType]), std::end(_researchedSceneryItems[sceneryType]), false);
2020-03-13 14:44:34 +01:00
}
}
void set_every_ride_type_invented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideTypes), std::end(_researchedRideTypes), true);
}
void set_every_ride_type_not_invented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideTypes), std::end(_researchedRideTypes), false);
}
void set_every_ride_entry_invented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideEntries), std::end(_researchedRideEntries), true);
}
void set_every_ride_entry_not_invented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideEntries), std::end(_researchedRideEntries), false);
}
2017-09-19 12:18:17 +02:00
/**
*
* rct2: 0x0068563D
*/
rct_string_id ResearchItem::GetName() const
2017-09-19 12:18:17 +02:00
{
if (type == Research::EntryType::Ride)
2017-09-19 12:18:17 +02:00
{
rct_ride_entry* rideEntry = get_ride_entry(entryIndex);
if (rideEntry == nullptr)
2017-09-19 12:18:17 +02:00
{
return STR_EMPTY;
2017-09-19 12:18:17 +02:00
}
2021-09-15 22:22:15 +02:00
return rideEntry->naming.Name;
2017-09-19 12:18:17 +02:00
}
2021-09-15 22:22:15 +02:00
rct_scenery_group_entry* sceneryEntry = get_scenery_group_entry(entryIndex);
if (sceneryEntry == nullptr)
2017-09-19 12:18:17 +02:00
{
2021-09-15 22:22:15 +02:00
return STR_EMPTY;
2017-09-19 12:18:17 +02:00
}
2021-09-15 22:22:15 +02:00
return sceneryEntry->name;
2017-09-19 12:18:17 +02:00
}
/**
*
* rct2: 0x00685A79
* Do not use the research list outside of the inventions list window with the flags
2019-06-16 23:32:31 +02:00
* Clears flags like "always researched".
*/
void research_remove_flags()
{
2019-06-16 23:32:31 +02:00
for (auto& researchItem : gResearchItemsUninvented)
{
researchItem.flags &= ~(RESEARCH_ENTRY_FLAG_RIDE_ALWAYS_RESEARCHED | RESEARCH_ENTRY_FLAG_SCENERY_SET_ALWAYS_RESEARCHED);
2019-06-16 23:32:31 +02:00
}
for (auto& researchItem : gResearchItemsInvented)
{
researchItem.flags &= ~(RESEARCH_ENTRY_FLAG_RIDE_ALWAYS_RESEARCHED | RESEARCH_ENTRY_FLAG_SCENERY_SET_ALWAYS_RESEARCHED);
}
}
static void ResearchRemoveNullItems(std::vector<ResearchItem>& items)
{
for (auto it = items.begin(); it != items.end();)
{
2019-06-16 23:32:31 +02:00
auto& researchItem = *it;
if (researchItem.type == Research::EntryType::Ride)
{
const auto* rideEntry = get_ride_entry(researchItem.entryIndex);
2019-06-16 23:32:31 +02:00
if (rideEntry == nullptr)
{
it = items.erase(it);
2019-06-16 23:32:31 +02:00
}
else
{
it++;
}
}
2019-06-16 23:32:31 +02:00
else
{
const auto* sceneryGroupEntry = get_scenery_group_entry(researchItem.entryIndex);
2019-06-16 23:32:31 +02:00
if (sceneryGroupEntry == nullptr)
{
it = items.erase(it);
2019-06-16 23:32:31 +02:00
}
else
{
it++;
}
}
}
}
static void research_mark_item_as_researched(const ResearchItem& item)
{
if (item.type == Research::EntryType::Ride)
2019-06-16 23:32:31 +02:00
{
const auto* rideEntry = get_ride_entry(item.entryIndex);
if (rideEntry != nullptr)
{
ride_entry_set_invented(item.entryIndex);
for (auto rideType : rideEntry->ride_type)
2019-06-16 23:32:31 +02:00
{
if (rideType != RIDE_TYPE_NULL)
{
ride_type_set_invented(rideType);
}
}
}
}
else if (item.type == Research::EntryType::Scenery)
{
const auto sgEntry = get_scenery_group_entry(item.entryIndex);
if (sgEntry != nullptr)
{
for (auto i = 0; i < sgEntry->entry_count; i++)
2019-06-16 23:32:31 +02:00
{
auto sceneryEntryIndex = sgEntry->scenery_entries[i];
scenery_set_invented(sceneryEntryIndex);
}
}
}
}
static void ResearchRebuildInventedTables()
{
set_every_ride_type_not_invented();
set_every_ride_entry_invented();
set_every_ride_entry_not_invented();
set_all_scenery_items_not_invented();
for (const auto& item : gResearchItemsInvented)
{
// Ignore item, if the research of it is in progress
if (gResearchProgressStage == RESEARCH_STAGE_DESIGNING || gResearchProgressStage == RESEARCH_STAGE_COMPLETING_DESIGN)
{
if (item == gResearchNextItem)
{
continue;
}
}
research_mark_item_as_researched(item);
}
}
static void ResearchAddAllMissingItems(bool isResearched)
{
for (ObjectEntryIndex i = 0; i < MAX_RIDE_OBJECTS; i++)
{
const auto* rideEntry = get_ride_entry(i);
if (rideEntry != nullptr)
{
research_insert_ride_entry(i, isResearched);
}
}
for (ObjectEntryIndex i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
{
const auto* groupEntry = get_scenery_group_entry(i);
if (groupEntry != nullptr)
{
research_insert_scenery_group_entry(i, isResearched);
}
}
}
void ResearchFix()
{
// Remove null entries from the research list
ResearchRemoveNullItems(gResearchItemsInvented);
ResearchRemoveNullItems(gResearchItemsUninvented);
// Add missing entries to the research list
// If research is complete, mark all the missing items as available
ResearchAddAllMissingItems(gResearchProgressStage == RESEARCH_STAGE_FINISHED_ALL);
// Now rebuild all the tables that say whether a ride or scenery item is invented
ResearchRebuildInventedTables();
ResearchUpdateUncompletedTypes();
}
void research_items_make_all_unresearched()
{
2019-06-18 20:14:26 +02:00
gResearchItemsUninvented.insert(
gResearchItemsUninvented.end(), std::make_move_iterator(gResearchItemsInvented.begin()),
std::make_move_iterator(gResearchItemsInvented.end()));
gResearchItemsInvented.clear();
}
void research_items_make_all_researched()
{
2019-06-18 20:14:26 +02:00
gResearchItemsInvented.insert(
gResearchItemsInvented.end(), std::make_move_iterator(gResearchItemsUninvented.begin()),
std::make_move_iterator(gResearchItemsUninvented.end()));
gResearchItemsUninvented.clear();
}
/**
*
* rct2: 0x00685A93
*/
void research_items_shuffle()
{
2019-06-18 20:14:26 +02:00
std::shuffle(std::begin(gResearchItemsUninvented), std::end(gResearchItemsUninvented), std::default_random_engine{});
}
bool ResearchItem::IsAlwaysResearched() const
{
return (flags & (RESEARCH_ENTRY_FLAG_RIDE_ALWAYS_RESEARCHED | RESEARCH_ENTRY_FLAG_SCENERY_SET_ALWAYS_RESEARCHED)) != 0;
}
2020-01-26 14:12:57 +01:00
bool ResearchItem::IsNull() const
{
2020-04-27 17:49:03 +02:00
return entryIndex == OBJECT_ENTRY_INDEX_NULL;
}
void ResearchItem::SetNull()
{
entryIndex = OBJECT_ENTRY_INDEX_NULL;
}
2019-06-16 23:32:31 +02:00
bool ResearchItem::Exists() const
{
for (auto const& researchItem : gResearchItemsUninvented)
{
if (researchItem == *this)
2019-06-16 23:32:31 +02:00
{
return true;
}
}
for (auto const& researchItem : gResearchItemsInvented)
{
if (researchItem == *this)
2019-06-16 23:32:31 +02:00
{
return true;
}
}
return false;
}
// clang-format off
static constexpr const rct_string_id _editorInventionsResearchCategories[] = {
STR_RESEARCH_NEW_TRANSPORT_RIDES,
STR_RESEARCH_NEW_GENTLE_RIDES,
STR_RESEARCH_NEW_ROLLER_COASTERS,
STR_RESEARCH_NEW_THRILL_RIDES,
STR_RESEARCH_NEW_WATER_RIDES,
STR_RESEARCH_NEW_SHOPS_AND_STALLS,
STR_RESEARCH_NEW_SCENERY_AND_THEMING,
};
// clang-format on
rct_string_id ResearchItem::GetCategoryInventionString() const
{
const auto categoryValue = EnumValue(category);
Guard::Assert(categoryValue <= 6, "Unsupported category invention string");
return _editorInventionsResearchCategories[categoryValue];
}
// clang-format off
static constexpr const rct_string_id _researchCategoryNames[] = {
STR_RESEARCH_CATEGORY_TRANSPORT,
STR_RESEARCH_CATEGORY_GENTLE,
STR_RESEARCH_CATEGORY_ROLLERCOASTER,
STR_RESEARCH_CATEGORY_THRILL,
STR_RESEARCH_CATEGORY_WATER,
STR_RESEARCH_CATEGORY_SHOP,
STR_RESEARCH_CATEGORY_SCENERY_GROUP,
};
// clang-format on
rct_string_id ResearchItem::GetCategoryName() const
{
const auto categoryValue = EnumValue(category);
Guard::Assert(categoryValue <= 6, "Unsupported category name");
return _researchCategoryNames[categoryValue];
}
bool ResearchItem::operator==(const ResearchItem& rhs) const
{
return (entryIndex == rhs.entryIndex && baseRideType == rhs.baseRideType && type == rhs.type);
}
static std::bitset<RIDE_TYPE_COUNT> _seenRideType = {};
static void research_update_first_of_type(ResearchItem* researchItem)
{
if (researchItem->IsNull())
return;
if (researchItem->type != Research::EntryType::Ride)
return;
auto rideType = researchItem->baseRideType;
if (rideType >= RIDE_TYPE_COUNT)
{
log_error("Research item has non-existent ride type index %d", rideType);
return;
}
const auto& rtd = GetRideTypeDescriptor(rideType);
if (rtd.HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY))
{
researchItem->flags |= RESEARCH_ENTRY_FLAG_FIRST_OF_TYPE;
return;
}
if (!_seenRideType[rideType])
researchItem->flags |= RESEARCH_ENTRY_FLAG_FIRST_OF_TYPE;
}
static void research_mark_ride_type_as_seen(const ResearchItem& researchItem)
{
auto rideType = researchItem.baseRideType;
if (rideType >= RIDE_TYPE_COUNT)
return;
_seenRideType[rideType] = true;
}
void research_determine_first_of_type()
{
_seenRideType.reset();
for (const auto& researchItem : gResearchItemsInvented)
{
if (researchItem.type != Research::EntryType::Ride)
continue;
auto rideType = researchItem.baseRideType;
if (rideType >= RIDE_TYPE_COUNT)
continue;
const auto& rtd = GetRideTypeDescriptor(rideType);
if (rtd.HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY))
continue;
// The last research item will also be present in gResearchItemsInvented.
// Avoid marking its ride type as "invented" prematurely.
if (gResearchLastItem.has_value() && !gResearchLastItem->IsNull() && researchItem == gResearchLastItem.value())
continue;
// The next research item is (sometimes?) also present in gResearchItemsInvented, even though it isn't invented yet(!)
if (gResearchNextItem.has_value() && !gResearchNextItem->IsNull() && researchItem == gResearchNextItem.value())
continue;
research_mark_ride_type_as_seen(researchItem);
}
if (gResearchLastItem.has_value())
{
research_update_first_of_type(&gResearchLastItem.value());
research_mark_ride_type_as_seen(gResearchLastItem.value());
}
if (gResearchNextItem.has_value())
{
research_update_first_of_type(&gResearchNextItem.value());
research_mark_ride_type_as_seen(gResearchNextItem.value());
}
for (auto& researchItem : gResearchItemsUninvented)
{
// The next research item is (sometimes?) also present in gResearchItemsUninvented
if (gResearchNextItem.has_value() && !gResearchNextItem->IsNull() && researchItem == gResearchNextItem.value())
{
// Copy the "first of type" flag.
researchItem.flags = gResearchNextItem->flags;
continue;
}
research_update_first_of_type(&researchItem);
2021-01-15 17:34:47 +01:00
research_mark_ride_type_as_seen(researchItem);
}
}