#pragma region Copyright (c) 2014-2017 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 "../config/Config.h" #include "../core/Guard.hpp" #include "../core/Util.hpp" #include "../Game.h" #include "../interface/Window.h" #include "../localisation/Date.h" #include "../localisation/Localisation.h" #include "../localisation/StringIds.h" #include "../object/ObjectList.h" #include "../OpenRCT2.h" #include "../scenario/Scenario.h" #include "../rct1/RCT1.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/RideGroupManager.h" #include "../ride/TrackData.h" #include "../world/Scenery.h" #include "NewsItem.h" #include "Finance.h" #include "Research.h" #include "../core/Memory.hpp" #include "../util/Util.h" static constexpr const sint32 _researchRate[] = {0, 160, 250, 400}; uint8 gResearchFundingLevel; uint8 gResearchPriorities; uint16 gResearchProgress; uint8 gResearchProgressStage; rct_research_item gResearchLastItem; uint8 gResearchExpectedMonth; uint8 gResearchExpectedDay; rct_research_item gResearchNextItem; // 0x01358844[500] rct_research_item gResearchItems[MAX_RESEARCH_ITEMS]; // 0x00EE787C uint8 gResearchUncompletedCategories; static bool _researchedRideTypes[RIDE_TYPE_COUNT]; static bool _researchedRideEntries[MAX_RIDE_OBJECTS]; static bool _researchedSceneryItems[MAX_RESEARCHED_SCENERY_ITEMS]; bool gSilentResearch = false; /** * * rct2: 0x006671AD, part of 0x00667132 */ void research_reset_items() { gResearchItems[0].rawValue = RESEARCHED_ITEMS_SEPARATOR; gResearchItems[1].rawValue = RESEARCHED_ITEMS_END; gResearchItems[2].rawValue = RESEARCHED_ITEMS_END_2; } /** * * rct2: 0x00684BAE */ void research_update_uncompleted_types() { sint32 uncompletedResearchTypes = 0; rct_research_item * researchItem = gResearchItems; while (researchItem++->rawValue != RESEARCHED_ITEMS_SEPARATOR); for (; researchItem->rawValue != RESEARCHED_ITEMS_END; researchItem++) { uncompletedResearchTypes |= (1 << 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 { sint32 progressRemaining = gResearchProgressStage == RESEARCH_STAGE_COMPLETING_DESIGN ? 0x10000 : 0x20000; progressRemaining -= gResearchProgress; sint32 daysRemaining = (progressRemaining / _researchRate[gResearchFundingLevel]) * 128; sint32 expectedDay = gDateMonthTicks + (daysRemaining & 0xFFFF); sint32 dayQuotient = expectedDay / 0x10000; sint32 dayRemainder = expectedDay % 0x10000; sint32 expectedMonth = date_get_month(gDateMonthsElapsed + dayQuotient + (daysRemaining >> 16)); expectedDay = (dayRemainder * days_in_month[expectedMonth]) >> 16; gResearchExpectedDay = expectedDay; gResearchExpectedMonth = expectedMonth; } } static void research_invalidate_related_windows() { window_invalidate_by_class(WC_CONSTRUCT_RIDE); window_invalidate_by_class(WC_RESEARCH); } /** * * rct2: 0x00684BE5 */ static void research_next_design() { rct_research_item * firstUnresearchedItem, * researchItem, tmp; sint32 ignoreActiveResearchTypes; // Skip already researched items firstUnresearchedItem = gResearchItems; while (firstUnresearchedItem->rawValue != RESEARCHED_ITEMS_SEPARATOR) { firstUnresearchedItem++; } ignoreActiveResearchTypes = 0; researchItem = firstUnresearchedItem; for (;;) { researchItem++; if (researchItem->rawValue == RESEARCHED_ITEMS_END) { if (!ignoreActiveResearchTypes) { ignoreActiveResearchTypes = 1; researchItem = firstUnresearchedItem; continue; } else { gResearchProgress = 0; gResearchProgressStage = RESEARCH_STAGE_FINISHED_ALL; research_invalidate_related_windows(); // Reset funding to 0 if no more rides. research_set_funding(0); return; } } else if (ignoreActiveResearchTypes || (gResearchPriorities & (1 << researchItem->category))) { break; } } gResearchNextItem = *researchItem; gResearchProgress = 0; gResearchProgressStage = RESEARCH_STAGE_DESIGNING; // Bubble research item up until it is above the researched items separator do { tmp = *researchItem; *researchItem = *(researchItem - 1); *(researchItem - 1) = tmp; researchItem--; } while ((researchItem + 1)->rawValue != RESEARCHED_ITEMS_SEPARATOR); research_invalidate_related_windows(); } /** * * rct2: 0x006848D4 */ void research_finish_item(rct_research_item * researchItem) { gResearchLastItem = *researchItem; research_invalidate_related_windows(); if (researchItem->type == RESEARCH_ENTRY_TYPE_RIDE) { // Ride uint32 base_ride_type = researchItem->baseRideType; sint32 rideEntryIndex = researchItem->entryIndex; rct_ride_entry * rideEntry = get_ride_entry(rideEntryIndex); if (rideEntry != nullptr && base_ride_type != RIDE_TYPE_NULL) { bool ride_group_was_invented_before = false; bool ride_type_was_invented_before = ride_type_is_invented(base_ride_type); rct_string_id availabilityString; // Determine if the ride group this entry belongs to was invented before. if (RideGroupManager::RideTypeHasRideGroups(base_ride_type)) { const RideGroup * rideGroup = RideGroupManager::GetRideGroup(base_ride_type, rideEntry); if (RideGroupManager::RideGroupIsInvented(rideGroup)) { ride_group_was_invented_before = true; } } ride_type_set_invented(base_ride_type); openrct2_assert(base_ride_type < Util::CountOf(RideTypePossibleTrackConfigurations), "Invalid base_ride_type = %d", base_ride_type); ride_entry_set_invented(rideEntryIndex); if (!(rideEntry->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE)) { for (sint32 i = 0; i < MAX_RESEARCHED_TRACK_TYPES; i++) { rct_ride_entry * rideEntry2 = get_ride_entry(i); if (rideEntry2 == nullptr) { continue; } if ((rideEntry2->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE)) { continue; } for (auto rideType : rideEntry2->ride_type) { if (rideType == base_ride_type) { ride_entry_set_invented(i); break; } } } } // If a vehicle should be listed separately (maze, mini golf, flat rides, shops) if (RideGroupManager::RideTypeIsIndependent(base_ride_type)) { availabilityString = STR_NEWS_ITEM_RESEARCH_NEW_RIDE_AVAILABLE; set_format_arg(0, rct_string_id, rideEntry->naming.name); } // If a vehicle is the first to be invented for its ride group, show the ride group name. else if (!ride_type_was_invented_before || (RideGroupManager::RideTypeHasRideGroups(base_ride_type) && !ride_group_was_invented_before)) { rct_ride_name naming = get_ride_naming(base_ride_type, rideEntry); availabilityString = STR_NEWS_ITEM_RESEARCH_NEW_RIDE_AVAILABLE; set_format_arg(0, 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; rct_ride_name baseRideNaming = get_ride_naming(base_ride_type, rideEntry); set_format_arg(0, rct_string_id, baseRideNaming.name); set_format_arg(2, rct_string_id, rideEntry->naming.name); } if (!gSilentResearch) { if (gConfigNotifications.ride_researched) { news_item_add_to_queue(NEWS_ITEM_RESEARCH, availabilityString, researchItem->rawValue); } } research_invalidate_related_windows(); } } else { // Scenery rct_scenery_group_entry * sceneryGroupEntry = get_scenery_group_entry(researchItem->entryIndex); if (sceneryGroupEntry != nullptr) { scenery_group_set_invented(researchItem->entryIndex); set_format_arg(0, rct_string_id, sceneryGroupEntry->name); if (!gSilentResearch) { if (gConfigNotifications.ride_researched) { news_item_add_to_queue(NEWS_ITEM_RESEARCH, STR_NEWS_ITEM_RESEARCH_NEW_SCENERY_SET_AVAILABLE, researchItem->rawValue); } } research_invalidate_related_windows(); init_scenery(); } } } /** * * rct2: 0x00684C7A */ void research_update() { sint32 editorScreenFlags, researchLevel, currentResearchProgress; editorScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER; if (gScreenFlags & editorScreenFlags) { return; } if (gScenarioTicks % 32 != 0) { return; } researchLevel = gResearchFundingLevel; currentResearchProgress = gResearchProgress; currentResearchProgress += _researchRate[researchLevel]; if (currentResearchProgress <= 0xFFFF) { gResearchProgress = currentResearchProgress; } else { switch (gResearchProgressStage) { 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); gResearchProgress = 0; gResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH; research_calculate_expected_date(); research_update_uncompleted_types(); research_invalidate_related_windows(); break; } } } void research_process_random_items() { rct_research_item * research = gResearchItems; for (; research->rawValue != RESEARCHED_ITEMS_END; research++) { } research++; for (; research->rawValue != RESEARCHED_ITEMS_END_2; research += 2) { if (scenario_rand() & 1) { continue; } rct_research_item * edx = nullptr; rct_research_item * ebp = nullptr; rct_research_item * inner_research = gResearchItems; do { if (research->rawValue == inner_research->rawValue) { edx = inner_research; } if ((research + 1)->rawValue == inner_research->rawValue) { ebp = inner_research; } } while ((inner_research++)->rawValue != RESEARCHED_ITEMS_END); assert(edx != nullptr); edx->rawValue = research->rawValue; assert(ebp != nullptr); ebp->rawValue = (research + 1)->rawValue; uint8 cat = edx->category; edx->category = ebp->category; ebp->category = cat; } } /** * * rct2: 0x00684AC3 */ void research_reset_current_item() { research_process_random_items(); 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. set_all_scenery_items_invented(); set_all_scenery_groups_not_invented(); for (rct_research_item * research = gResearchItems; research->rawValue != RESEARCHED_ITEMS_SEPARATOR; research++) { research_finish_item(research); } gResearchLastItem.rawValue = RESEARCHED_ITEMS_SEPARATOR; gResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH; gResearchProgress = 0; } /** * * rct2: 0x006857FA */ static void research_insert_unresearched(sint32 rawValue, sint32 category) { rct_research_item * researchItem, * researchItem2; researchItem = gResearchItems; do { if (researchItem->rawValue == RESEARCHED_ITEMS_END) { // Insert slot researchItem2 = researchItem; while (researchItem2->rawValue != RESEARCHED_ITEMS_END_2) { researchItem2++; } memmove(researchItem + 1, researchItem, (researchItem2 - researchItem + 1) * sizeof(rct_research_item)); // Place new item researchItem->rawValue = rawValue; researchItem->category = category; break; } } while (rawValue != (researchItem++)->rawValue); } /** * * rct2: 0x00685826 */ static void research_insert_researched(sint32 rawValue, uint8 category) { rct_research_item * researchItem, * researchItem2; researchItem = gResearchItems; // First check to make sure that entry is not already accounted for for (; researchItem->rawValue != RESEARCHED_ITEMS_END; researchItem++) { if ((researchItem->rawValue & 0xFFFFFF) == (rawValue & 0xFFFFFF)) { return; } } researchItem = gResearchItems; do { if (researchItem->rawValue == RESEARCHED_ITEMS_SEPARATOR) { // Insert slot researchItem2 = researchItem; while (researchItem2->rawValue != RESEARCHED_ITEMS_END_2) { researchItem2++; } memmove(researchItem + 1, researchItem, (researchItem2 - researchItem + 1) * sizeof(rct_research_item)); // Place new item researchItem->rawValue = rawValue; researchItem->category = category; break; } } while (rawValue != (researchItem++)->rawValue); } /** * * rct2: 0x006857CF */ void research_remove(rct_research_item * researchItem) { for (rct_research_item * researchItem2 = gResearchItems; researchItem2->rawValue != RESEARCHED_ITEMS_END; researchItem2++) { if (researchItem2->rawValue == researchItem->rawValue) { do { *researchItem2 = *(researchItem2 + 1); } while (researchItem2++->rawValue != RESEARCHED_ITEMS_END_2); return; } } } void research_insert(sint32 researched, sint32 rawValue, uint8 category) { if (researched) { research_insert_researched(rawValue, category); } else { research_insert_unresearched(rawValue, category); } } /** * * rct2: 0x00685675 */ void research_populate_list_random() { // Rides for (sint32 i = 0; i < MAX_RIDE_OBJECTS; i++) { rct_ride_entry * rideEntry = get_ride_entry(i); if (rideEntry == nullptr) { continue; } sint32 researched = (scenario_rand() & 0xFF) > 128; for (auto rideType : rideEntry->ride_type) { if (rideType != RIDE_TYPE_NULL) { research_insert(researched, RESEARCH_ENTRY_RIDE_MASK | (rideType << 8) | i, rideEntry->category[0]); } } } // Scenery for (sint32 i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++) { rct_scenery_group_entry * sceneryGroupEntry = get_scenery_group_entry(i); if (sceneryGroupEntry == nullptr) { continue; } sint32 researched = (scenario_rand() & 0xFF) > 85; research_insert(researched, i, RESEARCH_CATEGORY_SCENERY_GROUP); } } void research_populate_list_researched() { // Rides for (sint32 i = 0; i < MAX_RIDE_OBJECTS; i++) { rct_ride_entry * rideEntry = get_ride_entry(i); if (rideEntry == nullptr) { continue; } for (auto rideType : rideEntry->ride_type) { if (rideType != RIDE_TYPE_NULL) { research_insert(true, RESEARCH_ENTRY_RIDE_MASK | (rideType << 8) | i, rideEntry->category[0]); } } } // Scenery for (sint32 i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++) { rct_scenery_group_entry * sceneryGroupEntry = get_scenery_group_entry(i); if (sceneryGroupEntry == nullptr) { continue; } research_insert(true, i, RESEARCH_CATEGORY_SCENERY_GROUP); } } void research_set_funding(sint32 amount) { game_do_command(0, GAME_COMMAND_FLAG_APPLY, 0, amount, GAME_COMMAND_SET_RESEARCH_FUNDING, 0, 0); } void research_set_priority(sint32 activeCategories) { game_do_command(0, (1 << 8) | GAME_COMMAND_FLAG_APPLY, 0, activeCategories, GAME_COMMAND_SET_RESEARCH_FUNDING, 0, 0); } /** * * rct2: 0x00684A7F */ void game_command_set_research_funding(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi, sint32 * ebp) { sint32 setPriorities = (*ebx & (1 << 8)) != 0; uint32 fundingAmount = *edx; sint32 activeCategories = *edx; gCommandExpenditureType = RCT_EXPENDITURE_TYPE_RESEARCH; if (*ebx & GAME_COMMAND_FLAG_APPLY) { if (!setPriorities) { if (fundingAmount >= Util::CountOf(_researchRate)) { *ebx = MONEY32_UNDEFINED; log_warning("Invalid research rate %d", fundingAmount); return; } gResearchFundingLevel = fundingAmount; } else { gResearchPriorities = activeCategories; } window_invalidate_by_class(WC_FINANCES); window_invalidate_by_class(WC_RESEARCH); } *ebx = 0; } void research_insert_ride_entry(uint8 entryIndex, bool researched) { rct_ride_entry * rideEntry = get_ride_entry(entryIndex); uint8 category = rideEntry->category[0]; for (auto rideType : rideEntry->ride_type) { if (rideType != RIDE_TYPE_NULL) { research_insert(researched, RESEARCH_ENTRY_RIDE_MASK | (rideType << 8) | entryIndex, category); } } } void research_insert_scenery_group_entry(uint8 entryIndex, bool researched) { research_insert(researched, entryIndex, RESEARCH_CATEGORY_SCENERY_GROUP); } bool ride_type_is_invented(sint32 rideType) { return _researchedRideTypes[rideType]; } bool ride_entry_is_invented(sint32 rideEntryIndex) { return _researchedRideEntries[rideEntryIndex]; } bool track_piece_is_available_for_ride_type(uint8 rideType, sint32 trackType) { return RideTypePossibleTrackConfigurations[rideType] & (1ULL << trackType); } void ride_type_set_invented(sint32 rideType) { _researchedRideTypes[rideType] = true; } void ride_entry_set_invented(sint32 rideEntryIndex) { _researchedRideEntries[rideEntryIndex] = true; } bool scenery_is_invented(uint16 sceneryItem) { return _researchedSceneryItems[sceneryItem]; } void scenery_set_invented(uint16 sceneryItem) { _researchedSceneryItems[sceneryItem] = true; } void scenery_set_not_invented(uint16 sceneryItem) { _researchedSceneryItems[sceneryItem] = false; } bool scenery_group_is_invented(sint32 sgIndex) { auto invented = false; const auto sgEntry = get_scenery_group_entry(sgIndex); if (sgEntry != nullptr && sgEntry->entry_count > 0) { if (gCheatsIgnoreResearchStatus) { invented = true; } else { for (auto i = 0; i < sgEntry->entry_count; i++) { auto sceneryEntryIndex = sgEntry->scenery_entries[i]; if (scenery_is_invented(sceneryEntryIndex)) { invented = true; break; } } } } return invented; } void scenery_group_set_invented(sint32 sgIndex) { 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 (sint32 i = 0; i < MAX_SCENERY_GROUP_OBJECTS; ++i) { rct_scenery_group_entry * scenery_set = get_scenery_group_entry(i); if (scenery_set == nullptr) { continue; } for (sint32 j = 0; j < scenery_set->entry_count; ++j) { scenery_set_not_invented(scenery_set->scenery_entries[j]); } } } void set_all_scenery_items_invented() { Memory::Set(_researchedSceneryItems, true, sizeof(_researchedSceneryItems)); } void set_all_scenery_items_not_invented() { Memory::Set(_researchedSceneryItems, false, sizeof(_researchedSceneryItems)); } void set_every_ride_type_invented() { Memory::Set(_researchedRideTypes, true, sizeof(_researchedRideTypes)); } void set_every_ride_type_not_invented() { Memory::Set(_researchedRideTypes, false, sizeof(_researchedRideTypes)); } void set_every_ride_entry_invented() { Memory::Set(_researchedRideEntries, true, sizeof(_researchedRideEntries)); } void set_every_ride_entry_not_invented() { Memory::Set(_researchedRideEntries, false, sizeof(_researchedRideEntries)); } /** * * rct2: 0x0068563D */ rct_string_id research_item_get_name(rct_research_item * researchItem) { if (researchItem->type == RESEARCH_ENTRY_TYPE_RIDE) { rct_ride_entry * rideEntry = get_ride_entry(researchItem->entryIndex); if (rideEntry == nullptr) { return STR_EMPTY; } else { return rideEntry->naming.name; } } else { rct_scenery_group_entry * sceneryEntry = get_scenery_group_entry(researchItem->entryIndex); if (sceneryEntry == nullptr) { return STR_EMPTY; } else { return sceneryEntry->name; } } } /** * This will return the name of the base ride type or ride group, as seen in the research window. */ rct_string_id research_get_friendly_base_ride_type_name(uint8 trackType, rct_ride_entry * rideEntry) { if (RideGroupManager::RideTypeHasRideGroups(trackType)) { const RideGroup * rideGroup = RideGroupManager::GetRideGroup(trackType, rideEntry); return rideGroup->Naming.name; } else { return RideNaming[trackType].name; } } /** * * rct2: 0x00685A79 * Do not use the research list outside of the inventions list window with the flags */ void research_remove_flags() { for (rct_research_item * research = gResearchItems; research->rawValue != RESEARCHED_ITEMS_END_2; research++) { // Clear the always researched flags. if (research->rawValue > RESEARCHED_ITEMS_SEPARATOR) { research->flags = 0; } } } void research_fix() { // Fix invalid research items for (sint32 i = 0; i < MAX_RESEARCH_ITEMS; i++) { rct_research_item * researchItem = &gResearchItems[i]; if (researchItem->rawValue == RESEARCHED_ITEMS_SEPARATOR) continue; if (researchItem->rawValue == RESEARCHED_ITEMS_END) { if (i == MAX_RESEARCH_ITEMS - 1) { (--researchItem)->rawValue = RESEARCHED_ITEMS_END; } (++researchItem)->rawValue = RESEARCHED_ITEMS_END_2; break; } if (researchItem->rawValue == RESEARCHED_ITEMS_END_2) break; if (researchItem->type == RESEARCH_ENTRY_TYPE_RIDE) { rct_ride_entry * rideEntry = get_ride_entry(researchItem->entryIndex); if (rideEntry == nullptr) { research_remove(researchItem); i--; } } else { rct_scenery_group_entry * sceneryGroupEntry = get_scenery_group_entry(researchItem->rawValue); if (sceneryGroupEntry == nullptr) { research_remove(researchItem); i--; } } } research_update_uncompleted_types(); if (gResearchUncompletedCategories == 0) gResearchProgressStage = RESEARCH_STAGE_FINISHED_ALL; // Sometimes ride entries are not in the research table. // If all research is done, simply insert all of them as researched. // For good measure, also include scenery groups. if (gResearchProgressStage == RESEARCH_STAGE_FINISHED_ALL) { for (uint8 i = 0; i < MAX_RIDE_OBJECTS; i++) { const rct_ride_entry * rideEntry = get_ride_entry(i); if (rideEntry != nullptr) { research_insert_ride_entry(i, true); ride_entry_set_invented(i); for (uint8 j = 0; j < MAX_RIDE_TYPES_PER_RIDE_ENTRY; j++) { ride_type_set_invented(rideEntry->ride_type[j]); } } } for (uint8 i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++) { const rct_scenery_group_entry * groupEntry = get_scenery_group_entry(i); if (groupEntry != nullptr) research_insert_scenery_group_entry(i, true); } } } void research_items_make_all_unresearched() { rct_research_item * researchItem, * nextResearchItem, researchItemTemp; sint32 sorted; do { sorted = 1; for (researchItem = gResearchItems; researchItem->rawValue != RESEARCHED_ITEMS_SEPARATOR; researchItem++) { if (research_item_is_always_researched(researchItem)) continue; nextResearchItem = researchItem + 1; if (nextResearchItem->rawValue == RESEARCHED_ITEMS_SEPARATOR || research_item_is_always_researched(nextResearchItem)) { // Bubble up always researched item or separator researchItemTemp = *researchItem; *researchItem = *nextResearchItem; *nextResearchItem = researchItemTemp; sorted = 0; if (researchItem->rawValue == RESEARCHED_ITEMS_SEPARATOR) break; } } } while (!sorted); } void research_items_make_all_researched() { rct_research_item * researchItem, researchItemTemp; // Find separator for (researchItem = gResearchItems; researchItem->rawValue != RESEARCHED_ITEMS_SEPARATOR; researchItem++) { } // Move separator below all items for (; (researchItem + 1)->rawValue != RESEARCHED_ITEMS_END; researchItem++) { // Swap separator with research item researchItemTemp = *researchItem; *researchItem = *(researchItem + 1); *(researchItem + 1) = researchItemTemp; } } /** * * rct2: 0x00685A93 */ void research_items_shuffle() { rct_research_item * researchItem, * researchOrderBase, researchItemTemp; sint32 i, numNonResearchedItems; // Skip pre-researched items for (researchItem = gResearchItems; researchItem->rawValue != RESEARCHED_ITEMS_SEPARATOR; researchItem++) {} researchItem++; researchOrderBase = researchItem; // Count non pre-researched items numNonResearchedItems = 0; for (; researchItem->rawValue != RESEARCHED_ITEMS_END; researchItem++) numNonResearchedItems++; // Shuffle list for (i = 0; i < numNonResearchedItems; i++) { sint32 ri = util_rand() % numNonResearchedItems; if (ri == i) continue; researchItemTemp = researchOrderBase[i]; researchOrderBase[i] = researchOrderBase[ri]; researchOrderBase[ri] = researchItemTemp; } } bool research_item_is_always_researched(rct_research_item * researchItem) { return (researchItem->flags & (RESEARCH_ENTRY_FLAG_RIDE_ALWAYS_RESEARCHED | RESEARCH_ENTRY_FLAG_SCENERY_SET_ALWAYS_RESEARCHED)) != 0; }