OpenRCT2/src/openrct2/Editor.cpp

580 lines
18 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Editor.h"
2018-06-22 23:25:16 +02:00
#include "Context.h"
2017-12-07 16:33:04 +01:00
#include "EditorObjectSelectionSession.h"
#include "FileClassifier.h"
2017-11-30 18:17:06 +01:00
#include "Game.h"
#include "GameState.h"
2017-12-07 16:33:04 +01:00
#include "OpenRCT2.h"
#include "ParkImporter.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/LandBuyRightsAction.h"
#include "actions/LandSetRightsAction.h"
#include "actions/ResultWithMessage.h"
2017-12-07 16:33:04 +01:00
#include "audio/audio.h"
#include "core/Path.hpp"
#include "core/String.hpp"
2021-11-24 16:19:52 +01:00
#include "entity/EntityList.h"
#include "entity/EntityRegistry.h"
2021-11-25 22:47:24 +01:00
#include "entity/Guest.h"
2022-03-08 01:14:52 +01:00
#include "entity/PatrolArea.h"
2021-11-25 22:47:24 +01:00
#include "entity/Staff.h"
2018-01-06 00:45:53 +01:00
#include "interface/Viewport.h"
#include "interface/Window_internal.h"
2018-01-06 18:32:25 +01:00
#include "localisation/Localisation.h"
#include "localisation/LocalisationService.h"
#include "management/Finance.h"
2017-10-11 21:38:26 +02:00
#include "management/NewsItem.h"
#include "object/DefaultObjects.h"
#include "object/ObjectManager.h"
#include "object/ObjectRepository.h"
2018-01-09 10:40:42 +01:00
#include "rct1/RCT1.h"
2018-03-19 23:28:40 +01:00
#include "scenario/Scenario.h"
2018-06-22 23:25:16 +02:00
#include "ui/UiContext.h"
#include "ui/WindowManager.h"
2017-12-13 13:02:24 +01:00
#include "util/Util.h"
#include "windows/Intent.h"
#include "world/Climate.h"
#include "world/Entrance.h"
#include "world/Footpath.h"
2018-03-19 23:28:40 +01:00
#include "world/Park.h"
2018-06-22 23:25:16 +02:00
#include "world/Scenery.h"
#include <algorithm>
2018-06-22 23:25:16 +02:00
#include <array>
#include <vector>
2018-06-02 01:07:14 +02:00
using namespace OpenRCT2;
namespace Editor
{
static std::array<std::vector<uint8_t>, EnumValue(ObjectType::Count)> _editorSelectedObjectFlags;
2018-06-22 23:25:16 +02:00
static void ConvertSaveToScenarioCallback(int32_t result, const utf8* path);
static void SetAllLandOwned();
static void FinaliseMainView();
static bool ReadS4OrS6(const char* path);
static bool ReadPark(const char* path);
static void ClearMapForEditing(bool fromSave);
static void ObjectListLoad()
{
auto* context = GetContext();
// Unload objects first, the repository is re-populated which owns the objects.
auto& objectManager = context->GetObjectManager();
objectManager.UnloadAll();
// Scan objects if necessary
const auto& localisationService = context->GetLocalisationService();
auto& objectRepository = context->GetObjectRepository();
objectRepository.LoadOrConstruct(localisationService.GetCurrentLanguage());
Audio::LoadAudioObjects();
// Reset loaded objects to just defaults
// Load minimum required objects (like surface and edge)
for (const auto& entry : MinimumRequiredObjects)
{
objectManager.LoadObject(entry);
}
}
static WindowBase* OpenEditorWindows()
{
2022-11-06 21:49:07 +01:00
auto* main = ContextOpenWindow(WindowClass::MainWindow);
ContextOpenWindow(WindowClass::TopToolbar);
ContextOpenWindowView(WV_EDITOR_BOTTOM_TOOLBAR);
return main;
}
/**
*
* rct2: 0x0066FFE1
*/
void Load()
{
auto& gameState = GetGameState();
Audio::StopAll();
ObjectListLoad();
2024-03-26 15:08:17 +01:00
gameStateInitAll(gameState, DEFAULT_MAP_SIZE);
gScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR;
gameState.EditorStep = EditorStep::ObjectSelection;
gameState.Park.Flags |= PARK_FLAGS_SHOW_REAL_GUEST_NAMES;
gameState.ScenarioCategory = SCENARIO_CATEGORY_OTHER;
2023-01-16 21:14:50 +01:00
ViewportInitAll();
WindowBase* mainWindow = OpenEditorWindows();
mainWindow->SetLocation(TileCoordsXYZ{ 75, 75, 14 }.ToCoordsXYZ());
LoadPalette();
gScreenAge = 0;
gameState.ScenarioName = LanguageGetString(STR_MY_NEW_SCENARIO);
}
/**
*
* rct2: 0x00672781
*/
void ConvertSaveToScenario()
{
ToolCancel();
auto intent = Intent(WindowClass::Loadsave);
intent.PutExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME);
intent.PutExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(ConvertSaveToScenarioCallback));
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
}
2018-06-22 23:25:16 +02:00
static void ConvertSaveToScenarioCallback(int32_t result, const utf8* path)
{
if (result != MODAL_RESULT_OK)
{
return;
}
if (!GetContext()->LoadParkFromFile(path))
{
return;
}
auto& gameState = GetGameState();
ScenarioReset(gameState);
gScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR;
gameState.EditorStep = EditorStep::ObjectiveSelection;
gameState.ScenarioCategory = SCENARIO_CATEGORY_OTHER;
2023-01-16 21:14:50 +01:00
ViewportInitAll();
OpenEditorWindows();
FinaliseMainView();
gScreenAge = 0;
}
/**
*
* rct2: 0x00672957
*/
void LoadTrackDesigner()
{
Audio::StopAll();
gScreenFlags = SCREEN_FLAGS_TRACK_DESIGNER;
2018-06-22 23:25:16 +02:00
gScreenAge = 0;
ObjectManagerUnloadAllObjects();
ObjectListLoad();
2024-03-26 15:08:17 +01:00
gameStateInitAll(GetGameState(), DEFAULT_MAP_SIZE);
SetAllLandOwned();
GetGameState().EditorStep = EditorStep::ObjectSelection;
2023-01-16 21:14:50 +01:00
ViewportInitAll();
WindowBase* mainWindow = OpenEditorWindows();
mainWindow->SetLocation(TileCoordsXYZ{ 75, 75, 14 }.ToCoordsXYZ());
LoadPalette();
}
/**
*
* rct2: 0x006729FD
*/
void LoadTrackManager()
{
Audio::StopAll();
gScreenFlags = SCREEN_FLAGS_TRACK_MANAGER;
2018-06-22 23:25:16 +02:00
gScreenAge = 0;
ObjectManagerUnloadAllObjects();
ObjectListLoad();
2024-03-26 15:08:17 +01:00
gameStateInitAll(GetGameState(), DEFAULT_MAP_SIZE);
SetAllLandOwned();
GetGameState().EditorStep = EditorStep::ObjectSelection;
2023-01-16 21:14:50 +01:00
ViewportInitAll();
WindowBase* mainWindow = OpenEditorWindows();
mainWindow->SetLocation(TileCoordsXYZ{ 75, 75, 14 }.ToCoordsXYZ());
LoadPalette();
}
/**
*
* rct2: 0x0068ABEC
*/
static void SetAllLandOwned()
{
2024-02-12 22:32:08 +01:00
auto& gameState = GetGameState();
MapRange range = { 2 * COORDS_XY_STEP, 2 * COORDS_XY_STEP, (gameState.MapSize.x - 3) * COORDS_XY_STEP,
(gameState.MapSize.y - 3) * COORDS_XY_STEP };
2019-03-17 09:25:51 +01:00
auto landSetRightsAction = LandSetRightsAction(range, LandSetRightSetting::SetForSale);
landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND);
GameActions::Execute(&landSetRightsAction);
auto landBuyRightsAction = LandBuyRightsAction(range, LandBuyRightSetting::BuyLand);
landBuyRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND);
GameActions::Execute(&landBuyRightsAction);
}
/**
*
* rct2: 0x006758C0
*/
2018-06-22 23:25:16 +02:00
bool LoadLandscape(const utf8* path)
{
// #4996: Make sure the object selection window closes here to prevent unload objects
// after we have loaded a new park.
WindowCloseAll();
auto extension = GetFileExtensionType(path);
switch (extension)
{
2022-01-27 12:14:19 +01:00
case FileExtension::SC6:
case FileExtension::SV6:
case FileExtension::SC4:
case FileExtension::SV4:
return ReadS4OrS6(path);
2022-01-27 12:14:19 +01:00
case FileExtension::PARK:
return ReadPark(path);
2018-06-22 23:25:16 +02:00
default:
return false;
}
}
static void AfterLoadCleanup(bool loadedFromSave)
{
ClearMapForEditing(loadedFromSave);
GetGameState().EditorStep = EditorStep::LandscapeEditor;
2018-06-22 23:25:16 +02:00
gScreenAge = 0;
gScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR;
2023-01-16 21:14:50 +01:00
ViewportInitAll();
OpenEditorWindows();
FinaliseMainView();
}
/**
*
* rct2: 0x006758FE
*/
static bool ReadS4OrS6(const char* path)
{
auto extensionS = Path::GetExtension(path);
const char* extension = extensionS.c_str();
auto loadedFromSave = false;
const auto loadSuccess = GetContext()->LoadParkFromFile(path);
if (!loadSuccess)
return false;
if (String::IEquals(extension, ".sv4") || String::IEquals(extension, ".sv6") || String::IEquals(extension, ".sv7") == 0)
{
loadedFromSave = true;
}
AfterLoadCleanup(loadedFromSave);
return true;
}
static bool ReadPark(const char* path)
{
try
{
auto context = GetContext();
auto& objManager = context->GetObjectManager();
auto importer = ParkImporter::CreateParkFile(context->GetObjectRepository());
auto loadResult = importer->Load(path);
objManager.LoadObjects(loadResult.RequiredObjects);
// TODO: Have a separate GameState and exchange once loaded.
auto& gameState = GetGameState();
importer->Import(gameState);
AfterLoadCleanup(true);
return true;
}
catch (const std::exception&)
{
return false;
}
}
static void ClearMapForEditing(bool fromSave)
{
MapRemoveAllRides();
UnlinkAllRideBanners();
RideInitAll();
//
for (auto* guest : EntityList<Guest>())
{
guest->SetName({});
}
for (auto* staff : EntityList<Staff>())
{
staff->SetName({});
}
2024-01-21 22:05:22 +01:00
auto& gameState = GetGameState();
ResetAllEntities();
2022-03-08 01:14:52 +01:00
UpdateConsolidatedPatrolAreas();
gameState.NumGuestsInPark = 0;
gameState.NumGuestsHeadingForPark = 0;
gameState.NumGuestsInParkLastWeek = 0;
gameState.GuestChangeModifier = 0;
if (fromSave)
{
gameState.Park.Flags |= PARK_FLAGS_NO_MONEY;
if (gameState.Park.EntranceFee == 0)
{
gameState.Park.Flags |= PARK_FLAGS_PARK_FREE_ENTRY;
}
else
{
gameState.Park.Flags &= ~PARK_FLAGS_PARK_FREE_ENTRY;
}
gameState.Park.Flags &= ~PARK_FLAGS_SPRITES_INITIALISED;
2024-01-22 23:28:16 +01:00
gameState.GuestInitialCash = std::clamp(gameState.GuestInitialCash, 10.00_GBP, MAX_ENTRANCE_FEE);
gameState.InitialCash = std::min<money64>(GetGameState().InitialCash, 100000);
FinanceResetCashToInitial();
gameState.BankLoan = std::clamp<money64>(gameState.BankLoan, 0.00_GBP, 5000000.00_GBP);
2024-02-13 21:36:35 +01:00
gameState.MaxBankLoan = std::clamp<money64>(gameState.MaxBankLoan, 0.00_GBP, 5000000.00_GBP);
gameState.BankLoanInterestRate = std::clamp<uint8_t>(gameState.BankLoanInterestRate, 5, MaxBankLoanInterestRate);
}
2024-01-21 22:05:22 +01:00
ClimateReset(gameState.Climate);
News::InitQueue();
}
/**
*
* rct2: 0x0067009A
*/
void OpenWindowsForCurrentStep()
{
if (!(gScreenFlags & SCREEN_FLAGS_EDITOR))
{
return;
}
switch (GetGameState().EditorStep)
{
case EditorStep::ObjectSelection:
if (WindowFindByClass(WindowClass::EditorObjectSelection) != nullptr)
2018-06-22 23:25:16 +02:00
{
return;
}
if (WindowFindByClass(WindowClass::InstallTrack) != nullptr)
2018-06-22 23:25:16 +02:00
{
return;
}
2018-06-22 23:25:16 +02:00
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
{
ObjectManagerUnloadAllObjects();
2018-06-22 23:25:16 +02:00
}
2022-11-06 21:49:07 +01:00
ContextOpenWindow(WindowClass::EditorObjectSelection);
2018-06-22 23:25:16 +02:00
break;
case EditorStep::InventionsListSetUp:
if (WindowFindByClass(WindowClass::EditorInventionList) != nullptr)
2018-06-22 23:25:16 +02:00
{
return;
}
2022-11-06 21:49:07 +01:00
ContextOpenWindow(WindowClass::EditorInventionList);
2018-06-22 23:25:16 +02:00
break;
case EditorStep::OptionsSelection:
if (WindowFindByClass(WindowClass::EditorScenarioOptions) != nullptr)
2018-06-22 23:25:16 +02:00
{
return;
}
2022-11-06 21:49:07 +01:00
ContextOpenWindow(WindowClass::EditorScenarioOptions);
2018-06-22 23:25:16 +02:00
break;
case EditorStep::ObjectiveSelection:
if (WindowFindByClass(WindowClass::EditorObjectiveOptions) != nullptr)
2018-06-22 23:25:16 +02:00
{
return;
}
2022-11-06 21:49:07 +01:00
ContextOpenWindow(WindowClass::EditorObjectiveOptions);
2018-06-22 23:25:16 +02:00
break;
case EditorStep::LandscapeEditor:
case EditorStep::SaveScenario:
case EditorStep::RollercoasterDesigner:
case EditorStep::DesignsManager:
case EditorStep::Invalid:
break;
}
}
static void FinaliseMainView()
{
2018-06-02 01:07:14 +02:00
auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
auto& gameState = GetGameState();
windowManager->SetMainView(gameState.SavedView, gameState.SavedViewZoom, gameState.SavedViewRotation);
ResetAllSpriteQuadrantPlacements();
ScenerySetDefaultPlacementConfiguration();
2017-10-07 01:28:00 +02:00
2018-06-02 01:07:14 +02:00
windowManager->BroadcastIntent(Intent(INTENT_ACTION_REFRESH_NEW_RIDES));
2017-10-07 01:28:00 +02:00
gWindowUpdateTicks = 0;
LoadPalette();
2017-11-23 00:42:12 +01:00
2018-06-02 01:07:14 +02:00
windowManager->BroadcastIntent(Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD));
}
/**
*
* rct2: 0x006AB9B8
*/
2022-07-31 14:22:58 +02:00
std::pair<ObjectType, StringId> CheckObjectSelection()
{
2018-06-22 23:25:16 +02:00
bool isTrackDesignerManager = gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER);
if (!isTrackDesignerManager)
{
if (!EditorCheckObjectGroupAtLeastOneSurfaceSelected(false))
{
return { ObjectType::FootpathSurface, STR_AT_LEAST_ONE_FOOTPATH_NON_QUEUE_SURFACE_OBJECT_MUST_BE_SELECTED };
}
if (!EditorCheckObjectGroupAtLeastOneSurfaceSelected(true))
{
return { ObjectType::FootpathSurface, STR_AT_LEAST_ONE_FOOTPATH_QUEUE_SURFACE_OBJECT_MUST_BE_SELECTED };
}
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::FootpathRailings))
{
return { ObjectType::FootpathRailings, STR_AT_LEAST_ONE_FOOTPATH_RAILING_OBJECT_MUST_BE_SELECTED };
}
}
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::Ride))
{
return { ObjectType::Ride, STR_AT_LEAST_ONE_RIDE_OBJECT_MUST_BE_SELECTED };
}
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::Station))
{
return { ObjectType::Station, STR_AT_LEAST_ONE_STATION_OBJECT_MUST_BE_SELECTED };
}
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::TerrainSurface))
{
return { ObjectType::TerrainSurface, STR_AT_LEAST_ONE_TERRAIN_SURFACE_OBJECT_MUST_BE_SELECTED };
}
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::TerrainEdge))
{
return { ObjectType::TerrainEdge, STR_AT_LEAST_ONE_TERRAIN_EDGE_OBJECT_MUST_BE_SELECTED };
}
if (!isTrackDesignerManager)
{
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::ParkEntrance))
{
return { ObjectType::ParkEntrance, STR_PARK_ENTRANCE_TYPE_MUST_BE_SELECTED };
}
if (!EditorCheckObjectGroupAtLeastOneSelected(ObjectType::Water))
{
return { ObjectType::Water, STR_WATER_TYPE_MUST_BE_SELECTED };
}
}
return { ObjectType::None, STR_NONE };
}
/**
*
* rct2: 0x0066FEAC
*/
ResultWithMessage CheckPark()
{
2024-03-26 19:01:50 +01:00
auto& gameState = GetGameState();
int32_t parkSize = Park::UpdateSize(gameState);
if (parkSize == 0)
{
return { false, STR_PARK_MUST_OWN_SOME_LAND };
}
if (gameState.Park.Entrances.empty())
{
return { false, STR_NO_PARK_ENTRANCES };
}
for (const auto& parkEntrance : gameState.Park.Entrances)
{
int32_t direction = DirectionReverse(parkEntrance.direction);
2022-10-04 08:51:27 +02:00
switch (FootpathIsConnectedToMapEdge(parkEntrance, direction, 0))
{
2018-06-22 23:25:16 +02:00
case FOOTPATH_SEARCH_NOT_FOUND:
return { false, STR_PARK_ENTRANCE_WRONG_DIRECTION_OR_NO_PATH };
2018-06-22 23:25:16 +02:00
case FOOTPATH_SEARCH_INCOMPLETE:
case FOOTPATH_SEARCH_TOO_COMPLEX:
return { false, STR_PARK_ENTRANCE_PATH_INCOMPLETE_OR_COMPLEX };
2018-06-22 23:25:16 +02:00
case FOOTPATH_SEARCH_SUCCESS:
// Run the search again and unown the path
2022-10-04 08:51:27 +02:00
FootpathIsConnectedToMapEdge(parkEntrance, direction, (1 << 5));
2018-06-22 23:25:16 +02:00
break;
}
}
if (gameState.PeepSpawns.empty())
{
return { false, STR_PEEP_SPAWNS_NOT_SET };
}
return { true, STR_NONE };
}
uint8_t GetSelectedObjectFlags(ObjectType objectType, size_t index)
2018-02-11 18:56:12 +01:00
{
uint8_t result = 0;
auto& list = _editorSelectedObjectFlags[EnumValue(objectType)];
2018-02-11 18:56:12 +01:00
if (list.size() > index)
{
result = list[index];
}
return result;
}
void ClearSelectedObject(ObjectType objectType, size_t index, uint32_t flags)
2018-02-11 18:56:12 +01:00
{
auto& list = _editorSelectedObjectFlags[EnumValue(objectType)];
2018-02-11 18:56:12 +01:00
if (list.size() <= index)
{
list.resize(index + 1);
}
list[index] &= ~flags;
}
void SetSelectedObject(ObjectType objectType, size_t index, uint32_t flags)
2018-02-11 18:56:12 +01:00
{
if (index != OBJECT_ENTRY_INDEX_NULL)
2018-02-11 18:56:12 +01:00
{
assert(static_cast<int32_t>(objectType) < object_entry_group_counts[EnumValue(ObjectType::Paths)]);
auto& list = _editorSelectedObjectFlags[EnumValue(objectType)];
if (list.size() <= index)
{
list.resize(index + 1);
}
list[index] |= flags;
2018-02-11 18:56:12 +01:00
}
}
2018-06-22 23:25:16 +02:00
} // namespace Editor
void EditorOpenWindowsForCurrentStep()
{
2018-02-01 18:49:14 +01:00
Editor::OpenWindowsForCurrentStep();
}