OpenRCT2/src/openrct2/management/Research.cpp

1111 lines
33 KiB
C++
Raw Normal View History

2014-10-05 16:23:52 +02:00
/*****************************************************************************
* Copyright (c) 2014-2024 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 "../Date.h"
2018-06-22 23:02:14 +02:00
#include "../Game.h"
#include "../GameState.h"
2018-06-22 23:02:14 +02:00
#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/BitSet.hpp"
#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"
2021-12-12 00:06:06 +01:00
#include "../localisation/Formatter.h"
2018-01-06 18:32:25 +01:00
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
#include "../object/ObjectEntryManager.h"
2018-01-02 20:36:42 +01:00
#include "../object/ObjectList.h"
#include "../object/RideObject.h"
#include "../object/SceneryGroupEntry.h"
#include "../profiling/Profiling.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"
#include "../ride/RideEntry.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
using namespace OpenRCT2;
static constexpr int32_t _researchRate[] = {
0,
160,
250,
400,
};
2014-10-05 16:23:52 +02:00
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
*/
2024-01-29 21:33:39 +01:00
void ResearchResetItems(GameState_t& gameState)
{
2024-01-29 21:33:39 +01:00
gameState.ResearchItemsUninvented.clear();
gameState.ResearchItemsInvented.clear();
}
/**
*
* rct2: 0x00684BAE
*/
void ResearchUpdateUncompletedTypes()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
int32_t uncompletedResearchTypes = 0;
2024-01-29 21:33:39 +01:00
for (auto const& researchItem : gameState.ResearchItemsUninvented)
{
uncompletedResearchTypes |= EnumToFlag(researchItem.category);
}
2024-01-29 21:33:39 +01:00
gameState.ResearchUncompletedCategories = uncompletedResearchTypes;
}
/**
*
* rct2: 0x00684D2A
*/
static void ResearchCalculateExpectedDate()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
if (gameState.ResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH
|| gameState.ResearchFundingLevel == RESEARCH_FUNDING_NONE)
{
2024-01-29 21:33:39 +01:00
gameState.ResearchExpectedDay = 255;
}
else
{
auto& date = GetDate();
2024-01-29 21:33:39 +01:00
int32_t progressRemaining = gameState.ResearchProgressStage == RESEARCH_STAGE_COMPLETING_DESIGN ? 0x10000 : 0x20000;
progressRemaining -= gameState.ResearchProgress;
int32_t daysRemaining = (progressRemaining / _researchRate[gameState.ResearchFundingLevel]) * 128;
int32_t expectedDay = date.GetMonthTicks() + (daysRemaining & 0xFFFF);
2018-06-22 23:02:14 +02:00
int32_t dayQuotient = expectedDay / 0x10000;
int32_t dayRemainder = expectedDay % 0x10000;
int32_t expectedMonth = DateGetMonth(date.GetMonthsElapsed() + dayQuotient + (daysRemaining >> 16));
expectedDay = (dayRemainder * Date::GetDaysInMonth(expectedMonth)) >> 16;
2024-01-29 21:33:39 +01:00
gameState.ResearchExpectedDay = expectedDay;
gameState.ResearchExpectedMonth = expectedMonth;
}
}
static void ResearchInvalidateRelatedWindows()
2014-10-08 00:42:17 +02:00
{
WindowInvalidateByClass(WindowClass::ConstructRide);
WindowInvalidateByClass(WindowClass::Research);
2014-10-08 00:42:17 +02:00
}
static void ResearchMarkAsFullyCompleted()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
gameState.ResearchProgress = 0;
gameState.ResearchProgressStage = RESEARCH_STAGE_FINISHED_ALL;
ResearchInvalidateRelatedWindows();
// Reset funding to 0 if no more rides.
2024-01-29 21:33:39 +01:00
auto gameAction = ParkSetResearchFundingAction(gameState.ResearchPriorities, 0);
GameActions::Execute(&gameAction);
}
2014-10-08 00:42:17 +02:00
/**
*
* rct2: 0x00684BE5
*/
static void ResearchNextDesign()
2014-10-08 00:42:17 +02:00
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
if (gameState.ResearchItemsUninvented.empty())
{
ResearchMarkAsFullyCompleted();
2019-06-16 23:32:31 +02:00
return;
}
// Try to find a research item of a matching type, if none found, use any first item
2024-01-29 21:33:39 +01:00
auto it = std::find_if(
gameState.ResearchItemsUninvented.begin(), gameState.ResearchItemsUninvented.end(),
[&gameState](const auto& e) { return (gameState.ResearchPriorities & EnumToFlag(e.category)) != 0; });
if (it == gameState.ResearchItemsUninvented.end())
{
2024-01-29 21:33:39 +01:00
it = gameState.ResearchItemsUninvented.begin();
}
2024-01-29 21:33:39 +01:00
gameState.ResearchNextItem = *it;
gameState.ResearchProgress = 0;
gameState.ResearchProgressStage = RESEARCH_STAGE_DESIGNING;
ResearchInvalidateRelatedWindows();
2014-10-08 00:42:17 +02:00
}
static void MarkResearchItemInvented(const ResearchItem& researchItem)
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
gameState.ResearchItemsUninvented.erase(
std::remove(gameState.ResearchItemsUninvented.begin(), gameState.ResearchItemsUninvented.end(), researchItem),
gameState.ResearchItemsUninvented.end());
2024-01-29 21:33:39 +01:00
if (std::find(gameState.ResearchItemsInvented.begin(), gameState.ResearchItemsInvented.end(), researchItem)
== gameState.ResearchItemsInvented.end())
{
2024-01-29 21:33:39 +01:00
gameState.ResearchItemsInvented.push_back(researchItem);
}
}
2014-10-08 01:42:11 +02:00
/**
*
* rct2: 0x006848D4
*/
void ResearchFinishItem(const ResearchItem& researchItem)
2014-10-08 01:42:11 +02:00
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
gameState.ResearchLastItem = researchItem;
ResearchInvalidateRelatedWindows();
if (researchItem.type == Research::EntryType::Ride)
{
// Ride
auto base_ride_type = researchItem.baseRideType;
ObjectEntryIndex rideEntryIndex = researchItem.entryIndex;
const auto* rideEntry = GetRideEntryByIndex(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);
base_ride_type = rideEntry->GetFirstNonNullRideType();
2021-03-12 22:26:22 +01:00
}
2022-07-31 14:22:58 +02:00
StringId availabilityString;
RideTypeSetInvented(base_ride_type);
RideEntrySetInvented(rideEntryIndex);
bool seenRideEntry[MAX_RIDE_OBJECTS]{};
2024-01-29 21:33:39 +01:00
for (auto const& researchItem3 : gameState.ResearchItemsUninvented)
{
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])
{
const auto* rideEntry2 = GetRideEntryByIndex(i);
if (rideEntry2 != nullptr)
{
for (uint8_t j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++)
{
if (rideEntry2->ride_type[j] == base_ride_type)
{
RideEntrySetInvented(i);
ResearchInsertRideEntry(i, true);
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 = GetRideNaming(base_ride_type, *rideEntry);
availabilityString = STR_NEWS_ITEM_RESEARCH_NEW_RIDE_AVAILABLE;
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(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 = GetRideNaming(base_ride_type, *rideEntry);
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(baseRideNaming.Name);
ft.Add<StringId>(rideEntry->naming.Name);
}
2017-09-19 12:18:17 +02:00
if (!gSilentResearch)
{
if (gConfigNotifications.RideResearched)
2017-09-19 12:18:17 +02:00
{
News::AddItemToQueue(News::ItemType::Research, availabilityString, researchItem.rawValue, ft);
}
}
ResearchInvalidateRelatedWindows();
}
}
else
{
// Scenery
const auto* sceneryGroupEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(researchItem.entryIndex);
2017-11-20 10:15:52 +01:00
if (sceneryGroupEntry != nullptr)
{
SceneryGroupSetInvented(researchItem.entryIndex);
2020-08-27 14:35:37 +02:00
Formatter ft;
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(sceneryGroupEntry->name);
if (!gSilentResearch)
{
if (gConfigNotifications.RideResearched)
{
News::AddItemToQueue(
News::ItemType::Research, STR_NEWS_ITEM_RESEARCH_NEW_SCENERY_SET_AVAILABLE, researchItem.rawValue, ft);
}
}
ResearchInvalidateRelatedWindows();
SceneryInit();
}
}
2014-10-08 01:42:11 +02:00
}
2014-10-05 16:23:52 +02:00
/**
*
* rct2: 0x00684C7A
*/
void ResearchUpdate()
2014-10-05 16:23:52 +02:00
{
PROFILED_FUNCTION();
int32_t editorScreenFlags, researchLevel, currentResearchProgress;
editorScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER;
if (gScreenFlags & editorScreenFlags)
{
return;
}
auto& gameState = GetGameState();
if (gameState.CurrentTicks % 32 != 0)
{
return;
}
if ((gameState.Park.Flags & PARK_FLAGS_NO_MONEY) && gameState.ResearchFundingLevel == RESEARCH_FUNDING_NONE)
{
researchLevel = RESEARCH_FUNDING_NORMAL;
2018-06-22 23:02:14 +02:00
}
else
{
2024-01-29 21:33:39 +01:00
researchLevel = gameState.ResearchFundingLevel;
}
2024-01-29 21:33:39 +01:00
currentResearchProgress = gameState.ResearchProgress;
currentResearchProgress += _researchRate[researchLevel];
if (currentResearchProgress <= 0xFFFF)
{
2024-01-29 21:33:39 +01:00
gameState.ResearchProgress = currentResearchProgress;
}
else
{
2024-01-29 21:33:39 +01:00
switch (gameState.ResearchProgressStage)
{
2018-06-22 23:02:14 +02:00
case RESEARCH_STAGE_INITIAL_RESEARCH:
ResearchNextDesign();
ResearchCalculateExpectedDate();
2018-06-22 23:02:14 +02:00
break;
case RESEARCH_STAGE_DESIGNING:
2024-01-29 21:33:39 +01:00
gameState.ResearchProgress = 0;
gameState.ResearchProgressStage = RESEARCH_STAGE_COMPLETING_DESIGN;
ResearchCalculateExpectedDate();
ResearchInvalidateRelatedWindows();
2018-06-22 23:02:14 +02:00
break;
case RESEARCH_STAGE_COMPLETING_DESIGN:
2024-01-29 21:33:39 +01:00
MarkResearchItemInvented(*gameState.ResearchNextItem);
ResearchFinishItem(*gameState.ResearchNextItem);
gameState.ResearchProgress = 0;
gameState.ResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH;
ResearchCalculateExpectedDate();
ResearchUpdateUncompletedTypes();
ResearchInvalidateRelatedWindows();
2018-06-22 23:02:14 +02:00
break;
case RESEARCH_STAGE_FINISHED_ALL:
2024-01-29 21:33:39 +01:00
gameState.ResearchFundingLevel = RESEARCH_FUNDING_NONE;
2018-06-22 23:02:14 +02:00
break;
}
}
2014-11-01 01:13:15 +01:00
}
/**
*
* rct2: 0x00684AC3
*/
void ResearchResetCurrentItem()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
SetEveryRideTypeNotInvented();
SetEveryRideEntryNotInvented();
// The following two instructions together make all items not tied to a scenery group available.
SetAllSceneryItemsInvented();
SetAllSceneryGroupsNotInvented();
2024-01-29 21:33:39 +01:00
for (const auto& researchItem : gameState.ResearchItemsInvented)
{
ResearchFinishItem(researchItem);
}
2024-01-29 21:33:39 +01:00
gameState.ResearchLastItem = std::nullopt;
gameState.ResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH;
gameState.ResearchProgress = 0;
}
/**
*
* rct2: 0x006857FA
*/
static void ResearchInsertUnresearched(ResearchItem&& item)
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
// First check to make sure that entry is not already accounted for
if (item.Exists())
{
return;
}
2024-01-29 21:33:39 +01:00
gameState.ResearchItemsUninvented.push_back(std::move(item));
}
/**
*
* rct2: 0x00685826
*/
static void ResearchInsertResearched(ResearchItem&& item)
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
// 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;
}
2024-01-29 21:33:39 +01:00
gameState.ResearchItemsInvented.push_back(std::move(item));
}
/**
*
* rct2: 0x006857CF
*/
void ResearchRemove(const ResearchItem& researchItem)
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
gameState.ResearchItemsUninvented.erase(
std::remove(gameState.ResearchItemsUninvented.begin(), gameState.ResearchItemsUninvented.end(), researchItem),
gameState.ResearchItemsUninvented.end());
gameState.ResearchItemsInvented.erase(
std::remove(gameState.ResearchItemsInvented.begin(), gameState.ResearchItemsInvented.end(), researchItem),
gameState.ResearchItemsInvented.end());
}
void ResearchInsert(ResearchItem&& item, bool researched)
{
if (researched)
{
ResearchInsertResearched(std::move(item));
}
else
{
ResearchInsertUnresearched(std::move(item));
}
}
/**
*
* rct2: 0x00685675
*/
void ResearchPopulateListRandom()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
ResearchResetItems(gameState);
// Rides
for (int32_t i = 0; i < MAX_RIDE_OBJECTS; i++)
{
const auto* rideEntry = GetRideEntryByIndex(i);
if (rideEntry == nullptr)
{
continue;
}
int32_t researched = (ScenarioRand() & 0xFF) > 128;
for (auto rideType : rideEntry->ride_type)
{
if (rideType != RIDE_TYPE_NULL)
{
ResearchCategory category = GetRideTypeDescriptor(rideType).GetResearchCategory();
ResearchInsertRideEntry(rideType, i, category, researched);
}
}
}
// Scenery
for (uint32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
{
const auto* sceneryGroupEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(i);
2017-11-20 10:15:52 +01:00
if (sceneryGroupEntry == nullptr)
{
continue;
}
int32_t researched = (ScenarioRand() & 0xFF) > 85;
ResearchInsertSceneryGroupEntry(i, researched);
}
}
bool ResearchInsertRideEntry(ride_type_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);
ResearchInsert(std::move(tmpItem), researched);
return true;
}
return false;
}
void ResearchInsertRideEntry(ObjectEntryIndex entryIndex, bool researched)
{
const auto* rideEntry = GetRideEntryByIndex(entryIndex);
if (rideEntry == nullptr)
return;
for (auto rideType : rideEntry->ride_type)
{
if (rideType != RIDE_TYPE_NULL)
{
ResearchCategory category = GetRideTypeDescriptor(rideType).GetResearchCategory();
ResearchInsertRideEntry(rideType, entryIndex, category, researched);
}
}
}
bool ResearchInsertSceneryGroupEntry(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, 0, ResearchCategory::SceneryGroup, 0);
ResearchInsert(std::move(tmpItem), researched);
2020-04-30 12:34:00 +02:00
return true;
}
return false;
}
2023-04-14 22:57:38 +02:00
bool ResearchIsInvented(ObjectType objectType, ObjectEntryIndex index)
{
switch (objectType)
{
case ObjectType::Ride:
return RideEntryIsInvented(index);
case ObjectType::SceneryGroup:
return SceneryGroupIsInvented(index);
case ObjectType::SmallScenery:
return SceneryIsInvented({ SCENERY_TYPE_SMALL, index });
2023-04-14 22:57:38 +02:00
case ObjectType::LargeScenery:
return SceneryIsInvented({ SCENERY_TYPE_LARGE, index });
2023-04-14 22:57:38 +02:00
case ObjectType::Walls:
return SceneryIsInvented({ SCENERY_TYPE_WALL, index });
2023-04-14 22:57:38 +02:00
case ObjectType::Banners:
return SceneryIsInvented({ SCENERY_TYPE_BANNER, index });
case ObjectType::PathAdditions:
return SceneryIsInvented({ SCENERY_TYPE_PATH_ITEM, index });
2023-04-14 22:57:38 +02:00
default:
return true;
}
}
bool RideTypeIsInvented(uint32_t rideType)
{
2021-03-12 22:26:22 +01:00
return RideTypeIsValid(rideType) ? _researchedRideTypes[rideType] : false;
}
bool RideEntryIsInvented(ObjectEntryIndex rideEntryIndex)
{
if (rideEntryIndex >= std::size(_researchedRideEntries))
return false;
return _researchedRideEntries[rideEntryIndex];
}
void RideTypeSetInvented(uint32_t rideType)
{
2021-03-12 22:26:22 +01:00
if (RideTypeIsValid(rideType))
{
_researchedRideTypes[rideType] = true;
}
}
void RideEntrySetInvented(ObjectEntryIndex rideEntryIndex)
{
if (rideEntryIndex >= std::size(_researchedRideEntries))
LOG_ERROR("Tried setting ride entry %u as invented", rideEntryIndex);
else
_researchedRideEntries[rideEntryIndex] = true;
}
2016-07-09 22:06:54 +02:00
bool SceneryIsInvented(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");
2021-09-15 22:22:15 +02:00
return false;
2016-07-09 22:06:54 +02:00
}
void ScenerySetInvented(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
}
void ScenerySetNotInvented(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 SceneryGroupIsInvented(int32_t sgIndex)
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
const auto sgEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(sgIndex);
if (sgEntry == nullptr || sgEntry->SceneryEntries.empty())
{
return false;
}
// All scenery is temporarily invented when in the scenario editor
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
{
return true;
}
2024-03-03 22:44:15 +01:00
if (GetGameState().Cheats.IgnoreResearchStatus)
{
return true;
}
return std::none_of(
2024-01-29 21:33:39 +01:00
std::begin(gameState.ResearchItemsUninvented), std::end(gameState.ResearchItemsUninvented),
[sgIndex](const ResearchItem& item) {
return item.type == Research::EntryType::Scenery && item.entryIndex == sgIndex;
});
}
void SceneryGroupSetInvented(int32_t sgIndex)
2018-01-10 14:26:03 +01:00
{
const auto sgEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(sgIndex);
if (sgEntry != nullptr)
2018-01-10 14:26:03 +01:00
{
for (const auto& entry : sgEntry->SceneryEntries)
2018-01-10 14:26:03 +01:00
{
ScenerySetInvented(entry);
2018-01-10 14:26:03 +01:00
}
}
}
void SetAllSceneryGroupsNotInvented()
{
for (int32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; ++i)
{
const auto* scenery_set = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(i);
if (scenery_set == nullptr)
{
continue;
}
for (const auto& sceneryEntry : scenery_set->SceneryEntries)
{
ScenerySetNotInvented(sceneryEntry);
}
}
}
void SetAllSceneryItemsInvented()
{
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 SetAllSceneryItemsNotInvented()
2018-01-10 13:55:30 +01:00
{
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 SetEveryRideTypeInvented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideTypes), std::end(_researchedRideTypes), true);
}
void SetEveryRideTypeNotInvented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideTypes), std::end(_researchedRideTypes), false);
}
void SetEveryRideEntryInvented()
{
2018-06-22 23:02:14 +02:00
std::fill(std::begin(_researchedRideEntries), std::end(_researchedRideEntries), true);
}
void SetEveryRideEntryNotInvented()
{
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
*/
2022-07-31 14:22:58 +02:00
StringId ResearchItem::GetName() const
2017-09-19 12:18:17 +02:00
{
if (type == Research::EntryType::Ride)
2017-09-19 12:18:17 +02:00
{
const auto* rideEntry = GetRideEntryByIndex(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
const auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(entryIndex);
2021-09-15 22:22:15 +02:00
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 ResearchRemoveFlags()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
for (auto& researchItem : gameState.ResearchItemsUninvented)
{
researchItem.flags &= ~(RESEARCH_ENTRY_FLAG_RIDE_ALWAYS_RESEARCHED | RESEARCH_ENTRY_FLAG_SCENERY_SET_ALWAYS_RESEARCHED);
2019-06-16 23:32:31 +02:00
}
2024-01-29 21:33:39 +01:00
for (auto& researchItem : gameState.ResearchItemsInvented)
2019-06-16 23:32:31 +02:00
{
researchItem.flags &= ~(RESEARCH_ENTRY_FLAG_RIDE_ALWAYS_RESEARCHED | RESEARCH_ENTRY_FLAG_SCENERY_SET_ALWAYS_RESEARCHED);
}
}
static void ResearchRemoveNullItems(std::vector<ResearchItem>& items)
{
2021-11-14 18:07:57 +01:00
const auto it = std::remove_if(std::begin(items), std::end(items), [](const ResearchItem& researchItem) {
if (researchItem.type == Research::EntryType::Ride)
{
return GetRideEntryByIndex(researchItem.entryIndex) == nullptr;
}
2019-06-16 23:32:31 +02:00
else
{
return OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(researchItem.entryIndex) == nullptr;
2019-06-16 23:32:31 +02:00
}
2021-11-14 18:07:57 +01:00
});
items.erase(it, std::end(items));
}
static void ResearchMarkItemAsResearched(const ResearchItem& item)
{
if (item.type == Research::EntryType::Ride)
2019-06-16 23:32:31 +02:00
{
const auto* rideEntry = GetRideEntryByIndex(item.entryIndex);
if (rideEntry != nullptr)
{
RideEntrySetInvented(item.entryIndex);
for (auto rideType : rideEntry->ride_type)
2019-06-16 23:32:31 +02:00
{
if (rideType != RIDE_TYPE_NULL)
{
RideTypeSetInvented(rideType);
}
}
}
}
else if (item.type == Research::EntryType::Scenery)
{
const auto sgEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(item.entryIndex);
if (sgEntry != nullptr)
{
for (const auto& sceneryEntry : sgEntry->SceneryEntries)
2019-06-16 23:32:31 +02:00
{
ScenerySetInvented(sceneryEntry);
}
}
}
}
static void ResearchRebuildInventedTables()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
SetEveryRideTypeNotInvented();
SetEveryRideEntryInvented();
SetEveryRideEntryNotInvented();
SetAllSceneryItemsNotInvented();
2024-01-29 21:33:39 +01:00
for (const auto& item : gameState.ResearchItemsInvented)
{
// Ignore item, if the research of it is in progress
2024-01-29 21:33:39 +01:00
if (gameState.ResearchProgressStage == RESEARCH_STAGE_DESIGNING
|| gameState.ResearchProgressStage == RESEARCH_STAGE_COMPLETING_DESIGN)
{
2024-01-29 21:33:39 +01:00
if (item == gameState.ResearchNextItem)
{
continue;
}
}
ResearchMarkItemAsResearched(item);
}
MarkAllUnrestrictedSceneryAsInvented();
}
static void ResearchAddAllMissingItems(bool isResearched)
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
// Mark base ridetypes as seen if they exist in the invented research list.
bool seenBaseEntry[MAX_RIDE_OBJECTS]{};
2024-01-29 21:33:39 +01:00
for (auto const& researchItem : gameState.ResearchItemsInvented)
{
ObjectEntryIndex index = researchItem.baseRideType;
seenBaseEntry[index] = true;
}
// Unlock and add research entries to the invented list for ride types whose base ridetype has been seen.
for (ObjectEntryIndex i = 0; i < MAX_RIDE_OBJECTS; i++)
{
const auto* rideEntry = GetRideEntryByIndex(i);
if (rideEntry != nullptr)
{
for (uint8_t j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++)
{
if (seenBaseEntry[rideEntry->ride_type[j]])
{
RideEntrySetInvented(i);
ResearchInsertRideEntry(i, true);
break;
}
}
}
}
// Mark base ridetypes as seen if they exist in the uninvented research list.
2024-01-29 21:33:39 +01:00
for (auto const& researchItem : gameState.ResearchItemsUninvented)
{
ObjectEntryIndex index = researchItem.baseRideType;
seenBaseEntry[index] = true;
}
// Only add Rides to uninvented research that haven't had their base ridetype seen.
// This prevents rct2 grouped rides from only unlocking the first train.
for (ObjectEntryIndex i = 0; i < MAX_RIDE_OBJECTS; i++)
{
const auto* rideEntry = GetRideEntryByIndex(i);
if (rideEntry != nullptr)
{
bool baseSeen = false;
for (uint8_t j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++)
{
if (seenBaseEntry[rideEntry->ride_type[j]])
{
baseSeen = true;
break;
}
}
if (!baseSeen)
{
ResearchInsertRideEntry(i, isResearched);
}
}
}
for (ObjectEntryIndex i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
{
const auto* groupEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(i);
if (groupEntry != nullptr)
{
ResearchInsertSceneryGroupEntry(i, isResearched);
}
}
}
void ResearchFix()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
// Remove null entries from the research list
2024-01-29 21:33:39 +01:00
ResearchRemoveNullItems(gameState.ResearchItemsInvented);
ResearchRemoveNullItems(gameState.ResearchItemsUninvented);
// Add missing entries to the research list
// If research is complete, mark all the missing items as available
2024-01-29 21:33:39 +01:00
ResearchAddAllMissingItems(gameState.ResearchProgressStage == RESEARCH_STAGE_FINISHED_ALL);
// Now rebuild all the tables that say whether a ride or scenery item is invented
ResearchRebuildInventedTables();
ResearchUpdateUncompletedTypes();
}
void ResearchItemsMakeAllUnresearched()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
gameState.ResearchItemsUninvented.insert(
gameState.ResearchItemsUninvented.end(), std::make_move_iterator(gameState.ResearchItemsInvented.begin()),
std::make_move_iterator(gameState.ResearchItemsInvented.end()));
gameState.ResearchItemsInvented.clear();
}
void ResearchItemsMakeAllResearched()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
gameState.ResearchItemsInvented.insert(
gameState.ResearchItemsInvented.end(), std::make_move_iterator(gameState.ResearchItemsUninvented.begin()),
std::make_move_iterator(gameState.ResearchItemsUninvented.end()));
gameState.ResearchItemsUninvented.clear();
}
/**
*
* rct2: 0x00685A93
*/
void ResearchItemsShuffle()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
std::shuffle(
std::begin(gameState.ResearchItemsUninvented), std::end(gameState.ResearchItemsUninvented),
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
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
for (auto const& researchItem : gameState.ResearchItemsUninvented)
2019-06-16 23:32:31 +02:00
{
if (researchItem == *this)
2019-06-16 23:32:31 +02:00
{
return true;
}
}
2024-01-29 21:33:39 +01:00
for (auto const& researchItem : gameState.ResearchItemsInvented)
2019-06-16 23:32:31 +02:00
{
if (researchItem == *this)
2019-06-16 23:32:31 +02:00
{
return true;
}
}
return false;
}
// clang-format off
static constexpr StringId _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
2022-07-31 14:22:58 +02:00
StringId ResearchItem::GetCategoryInventionString() const
{
const auto categoryValue = EnumValue(category);
Guard::Assert(categoryValue <= 6, "Unsupported category invention string");
return _editorInventionsResearchCategories[categoryValue];
}
// clang-format off
static constexpr StringId _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
2022-07-31 14:22:58 +02:00
StringId 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 BitSet<RIDE_TYPE_COUNT> _seenRideType = {};
static void ResearchUpdateFirstOfType(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;
}
researchItem->flags &= ~RESEARCH_ENTRY_FLAG_FIRST_OF_TYPE;
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 ResearchMarkRideTypeAsSeen(const ResearchItem& researchItem)
{
auto rideType = researchItem.baseRideType;
if (rideType >= RIDE_TYPE_COUNT)
return;
_seenRideType[rideType] = true;
}
void ResearchDetermineFirstOfType()
{
2024-01-29 21:33:39 +01:00
auto& gameState = GetGameState();
_seenRideType.reset();
2024-01-29 21:33:39 +01:00
for (const auto& researchItem : gameState.ResearchItemsInvented)
{
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;
2024-01-29 21:33:39 +01:00
// The last research item will also be present in gameState.ResearchItemsInvented.
// Avoid marking its ride type as "invented" prematurely.
2024-01-29 21:33:39 +01:00
if (gameState.ResearchLastItem.has_value() && !gameState.ResearchLastItem->IsNull()
&& researchItem == gameState.ResearchLastItem.value())
continue;
2024-01-29 21:33:39 +01:00
// The next research item is (sometimes?) also present in gameState.ResearchItemsInvented, even though it isn't invented
// yet(!)
if (gameState.ResearchNextItem.has_value() && !gameState.ResearchNextItem->IsNull()
&& researchItem == gameState.ResearchNextItem.value())
continue;
ResearchMarkRideTypeAsSeen(researchItem);
}
2024-01-29 21:33:39 +01:00
if (gameState.ResearchLastItem.has_value())
{
2024-01-29 21:33:39 +01:00
ResearchUpdateFirstOfType(&gameState.ResearchLastItem.value());
ResearchMarkRideTypeAsSeen(gameState.ResearchLastItem.value());
}
2024-01-29 21:33:39 +01:00
if (gameState.ResearchNextItem.has_value())
{
2024-01-29 21:33:39 +01:00
ResearchUpdateFirstOfType(&gameState.ResearchNextItem.value());
ResearchMarkRideTypeAsSeen(gameState.ResearchNextItem.value());
}
2024-01-29 21:33:39 +01:00
for (auto& researchItem : gameState.ResearchItemsUninvented)
{
2024-01-29 21:33:39 +01:00
// The next research item is (sometimes?) also present in gameState.ResearchItemsUninvented
if (gameState.ResearchNextItem.has_value() && !gameState.ResearchNextItem->IsNull()
&& researchItem.baseRideType == gameState.ResearchNextItem.value().baseRideType)
{
// Copy the "first of type" flag.
2024-01-29 21:33:39 +01:00
researchItem.flags = gameState.ResearchNextItem->flags;
continue;
}
ResearchUpdateFirstOfType(&researchItem);
ResearchMarkRideTypeAsSeen(researchItem);
}
}