Merge pull request #17426 from Broxzier/refactor/variadic-title-commands

This commit is contained in:
Hielke Morsink 2022-06-28 22:55:47 +02:00 committed by GitHub
commit e39fd9226f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1476 additions and 1028 deletions

View File

@ -710,7 +710,7 @@ public:
{ {
auto context = GetContext(); auto context = GetContext();
auto gameState = context->GetGameState(); auto gameState = context->GetGameState();
_titleSequencePlayer = CreateTitleSequencePlayer(*gameState); _titleSequencePlayer = OpenRCT2::Title::CreateTitleSequencePlayer(*gameState);
} }
return _titleSequencePlayer.get(); return _titleSequencePlayer.get();
} }

View File

@ -24,20 +24,39 @@
# include <openrct2/title/TitleSequence.h> # include <openrct2/title/TitleSequence.h>
# include <openrct2/title/TitleSequenceManager.h> # include <openrct2/title/TitleSequenceManager.h>
# include <openrct2/title/TitleSequencePlayer.h> # include <openrct2/title/TitleSequencePlayer.h>
# include <type_traits>
# include <variant>
namespace OpenRCT2::Scripting namespace OpenRCT2::Scripting
{ {
enum class TitleScript : uint8_t
{
Undefined = 0xFF,
Wait = 0,
Location,
Rotate,
Zoom,
Follow,
Restart,
Load,
End,
Speed,
Loop,
EndLoop,
LoadSc,
};
static const DukEnumMap<TitleScript> TitleScriptMap({ static const DukEnumMap<TitleScript> TitleScriptMap({
{ "load", TitleScript::Load }, { OpenRCT2::Title::LoadParkCommand::ScriptingName, TitleScript::Load },
{ "location", TitleScript::Location }, { OpenRCT2::Title::SetLocationCommand::ScriptingName, TitleScript::Location },
{ "rotate", TitleScript::Rotate }, { OpenRCT2::Title::RotateViewCommand::ScriptingName, TitleScript::Rotate },
{ "zoom", TitleScript::Zoom }, { OpenRCT2::Title::SetZoomCommand::ScriptingName, TitleScript::Zoom },
{ "follow", TitleScript::Follow }, { OpenRCT2::Title::FollowEntityCommand::ScriptingName, TitleScript::Follow },
{ "speed", TitleScript::Speed }, { OpenRCT2::Title::SetSpeedCommand::ScriptingName, TitleScript::Speed },
{ "wait", TitleScript::Wait }, { OpenRCT2::Title::WaitCommand::ScriptingName, TitleScript::Wait },
{ "loadsc", TitleScript::LoadSc }, { OpenRCT2::Title::LoadScenarioCommand::ScriptingName, TitleScript::LoadSc },
{ "restart", TitleScript::Restart }, { OpenRCT2::Title::RestartCommand::ScriptingName, TitleScript::Restart },
{ "end", TitleScript::End }, { OpenRCT2::Title::EndCommand::ScriptingName, TitleScript::End },
}); });
template<> DukValue ToDuk(duk_context* ctx, const TitleScript& value) template<> DukValue ToDuk(duk_context* ctx, const TitleScript& value)
@ -45,43 +64,52 @@ namespace OpenRCT2::Scripting
return ToDuk(ctx, TitleScriptMap[value]); return ToDuk(ctx, TitleScriptMap[value]);
} }
template<> DukValue ToDuk(duk_context* ctx, const TitleCommand& value) template<> DukValue ToDuk(duk_context* ctx, const OpenRCT2::Title::TitleCommand& value)
{ {
using namespace OpenRCT2::Title;
DukObject obj(ctx); DukObject obj(ctx);
obj.Set("type", ToDuk(ctx, value.Type)); std::visit(
switch (value.Type) [&obj](auto&& command) {
using T = std::decay_t<decltype(command)>;
obj.Set("type", T::ScriptingName);
if constexpr (std::is_same_v<T, LoadParkCommand>)
{ {
case TitleScript::Load: obj.Set("index", command.SaveIndex);
obj.Set("index", value.SaveIndex); }
break; else if constexpr (std::is_same_v<T, SetLocationCommand>)
case TitleScript::Location: {
obj.Set("x", value.Location.X); obj.Set("x", command.Location.X);
obj.Set("y", value.Location.Y); obj.Set("y", command.Location.Y);
break; }
case TitleScript::Rotate: else if constexpr (std::is_same_v<T, RotateViewCommand>)
obj.Set("rotations", value.Rotations); {
break; obj.Set("rotations", command.Rotations);
case TitleScript::Zoom: }
obj.Set("zoom", value.Zoom); else if constexpr (std::is_same_v<T, SetZoomCommand>)
break; {
case TitleScript::Follow: obj.Set("zoom", command.Zoom);
if (value.Follow.SpriteIndex.IsNull()) }
else if constexpr (std::is_same_v<T, FollowEntityCommand>)
{
if (command.Follow.SpriteIndex.IsNull())
obj.Set("id", nullptr); obj.Set("id", nullptr);
else else
obj.Set("id", value.Follow.SpriteIndex.ToUnderlying()); obj.Set("id", command.Follow.SpriteIndex.ToUnderlying());
break;
case TitleScript::Speed:
obj.Set("speed", value.Speed);
break;
case TitleScript::Wait:
obj.Set("duration", value.Milliseconds);
break;
case TitleScript::LoadSc:
obj.Set("scenario", String::ToStringView(value.Scenario, sizeof(value.Scenario)));
break;
default:
break;
} }
else if constexpr (std::is_same_v<T, SetSpeedCommand>)
{
obj.Set("speed", command.Speed);
}
else if constexpr (std::is_same_v<T, WaitCommand>)
{
obj.Set("duration", command.Milliseconds);
}
else if constexpr (std::is_same_v<T, LoadScenarioCommand>)
{
obj.Set("scenario", String::ToStringView(command.Scenario, sizeof(command.Scenario)));
}
},
value);
return obj.Take(); return obj.Take();
} }
@ -92,47 +120,60 @@ namespace OpenRCT2::Scripting
throw DukException() << "Invalid title command id"; throw DukException() << "Invalid title command id";
} }
template<> TitleCommand FromDuk(const DukValue& value) template<> OpenRCT2::Title::TitleCommand FromDuk(const DukValue& value)
{ {
using namespace OpenRCT2::Title;
auto type = FromDuk<TitleScript>(value["type"]); auto type = FromDuk<TitleScript>(value["type"]);
TitleCommand command{}; TitleCommand command{};
command.Type = type;
switch (type) switch (type)
{ {
case TitleScript::Load: case TitleScript::Load:
command.SaveIndex = value["index"].as_int(); command = LoadParkCommand{ static_cast<uint8_t>(value["index"].as_int()) };
break; break;
case TitleScript::Location: case TitleScript::Location:
command.Location.X = value["x"].as_int(); command = SetLocationCommand{
command.Location.Y = value["y"].as_int(); static_cast<uint8_t>(value["x"].as_int()),
static_cast<uint8_t>(value["y"].as_int()),
};
break; break;
case TitleScript::Rotate: case TitleScript::Rotate:
command.Rotations = value["rotations"].as_int(); command = RotateViewCommand{ static_cast<uint8_t>(value["rotations"].as_int()) };
break; break;
case TitleScript::Zoom: case TitleScript::Zoom:
command.Zoom = value["zoom"].as_int(); command = SetZoomCommand{ static_cast<uint8_t>(value["zoom"].as_int()) };
break; break;
case TitleScript::Follow: case TitleScript::Follow:
{ {
auto dukId = value["id"]; auto dukId = value["id"];
if (dukId.type() == DukValue::Type::NUMBER) if (dukId.type() == DukValue::Type::NUMBER)
{ {
command.Follow.SpriteIndex = EntityId::FromUnderlying(dukId.as_int()); command = FollowEntityCommand{ EntityId::FromUnderlying(dukId.as_int()) };
} }
else else
{ {
command.Follow.SpriteIndex = EntityId::GetNull(); command = FollowEntityCommand{ EntityId::GetNull() };
} }
break; break;
} }
case TitleScript::Speed: case TitleScript::Speed:
command.Speed = value["speed"].as_int(); command = SetSpeedCommand{ static_cast<uint8_t>(value["speed"].as_int()) };
break; break;
case TitleScript::Wait: case TitleScript::Wait:
command.Milliseconds = value["duration"].as_int(); command = WaitCommand{ static_cast<uint16_t>(value["duration"].as_int()) };
break; break;
case TitleScript::LoadSc: case TitleScript::LoadSc:
String::Set(command.Scenario, sizeof(command.Scenario), value["scenario"].as_c_string()); {
auto loadScenarioCommand = LoadScenarioCommand{};
String::Set(
loadScenarioCommand.Scenario, sizeof(loadScenarioCommand.Scenario), value["scenario"].as_c_string());
command = loadScenarioCommand;
break;
}
case TitleScript::Restart:
command = RestartCommand{};
break;
case TitleScript::End:
command = EndCommand{};
break; break;
default: default:
break; break;
@ -164,7 +205,7 @@ namespace OpenRCT2::Scripting
if (value == _fileName) if (value == _fileName)
return; return;
auto seq = LoadTitleSequence(_titleSequencePath); auto seq = OpenRCT2::Title::LoadTitleSequence(_titleSequencePath);
if (seq != nullptr) if (seq != nullptr)
{ {
// Check if name already in use // Check if name already in use
@ -183,27 +224,27 @@ namespace OpenRCT2::Scripting
void delete_() void delete_()
{ {
auto seq = LoadTitleSequence(_titleSequencePath); auto seq = OpenRCT2::Title::LoadTitleSequence(_titleSequencePath);
if (seq != nullptr) if (seq != nullptr)
{ {
auto index = GetIndex(*seq, _fileName); auto index = GetIndex(*seq, _fileName);
if (index) if (index)
{ {
TitleSequenceRemovePark(*seq, *index); OpenRCT2::Title::TitleSequenceRemovePark(*seq, *index);
TitleSequenceSave(*seq); OpenRCT2::Title::TitleSequenceSave(*seq);
} }
} }
} }
void load() void load()
{ {
auto seq = LoadTitleSequence(_titleSequencePath); auto seq = OpenRCT2::Title::LoadTitleSequence(_titleSequencePath);
if (seq != nullptr) if (seq != nullptr)
{ {
auto index = GetIndex(*seq, _fileName); auto index = GetIndex(*seq, _fileName);
if (index) if (index)
{ {
auto handle = TitleSequenceGetParkHandle(*seq, *index); auto handle = OpenRCT2::Title::TitleSequenceGetParkHandle(*seq, *index);
auto isScenario = ParkImporter::ExtensionIsScenario(handle->HintPath); auto isScenario = ParkImporter::ExtensionIsScenario(handle->HintPath);
try try
{ {
@ -246,7 +287,7 @@ namespace OpenRCT2::Scripting
} }
private: private:
static std::optional<size_t> GetIndex(const TitleSequence& seq, const std::string_view needle) static std::optional<size_t> GetIndex(const OpenRCT2::Title::TitleSequence& seq, const std::string_view needle)
{ {
for (size_t i = 0; i < seq.Saves.size(); i++) for (size_t i = 0; i < seq.Saves.size(); i++)
{ {
@ -327,7 +368,7 @@ namespace OpenRCT2::Scripting
std::vector<std::shared_ptr<ScTitleSequencePark>> parks_get() const std::vector<std::shared_ptr<ScTitleSequencePark>> parks_get() const
{ {
std::vector<std::shared_ptr<ScTitleSequencePark>> result; std::vector<std::shared_ptr<ScTitleSequencePark>> result;
auto titleSeq = LoadTitleSequence(_path); auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path);
if (titleSeq != nullptr) if (titleSeq != nullptr)
{ {
for (size_t i = 0; i < titleSeq->Saves.size(); i++) for (size_t i = 0; i < titleSeq->Saves.size(); i++)
@ -344,7 +385,7 @@ namespace OpenRCT2::Scripting
auto ctx = scriptEngine.GetContext(); auto ctx = scriptEngine.GetContext();
std::vector<DukValue> result; std::vector<DukValue> result;
auto titleSeq = LoadTitleSequence(_path); auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path);
if (titleSeq != nullptr) if (titleSeq != nullptr)
{ {
for (const auto& command : titleSeq->Commands) for (const auto& command : titleSeq->Commands)
@ -357,23 +398,23 @@ namespace OpenRCT2::Scripting
void commands_set(const std::vector<DukValue>& value) void commands_set(const std::vector<DukValue>& value)
{ {
std::vector<TitleCommand> commands; std::vector<OpenRCT2::Title::TitleCommand> commands;
for (const auto& v : value) for (const auto& v : value)
{ {
auto command = FromDuk<TitleCommand>(v); auto command = FromDuk<OpenRCT2::Title::TitleCommand>(v);
commands.push_back(std::move(command)); commands.push_back(std::move(command));
} }
auto titleSeq = LoadTitleSequence(_path); auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path);
titleSeq->Commands = commands; titleSeq->Commands = commands;
TitleSequenceSave(*titleSeq); OpenRCT2::Title::TitleSequenceSave(*titleSeq);
} }
void addPark(const std::string& path, const std::string& fileName) void addPark(const std::string& path, const std::string& fileName)
{ {
auto titleSeq = LoadTitleSequence(_path); auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path);
TitleSequenceAddPark(*titleSeq, path.c_str(), fileName.c_str()); OpenRCT2::Title::TitleSequenceAddPark(*titleSeq, path.c_str(), fileName.c_str());
TitleSequenceSave(*titleSeq); OpenRCT2::Title::TitleSequenceSave(*titleSeq);
} }
std::shared_ptr<ScTitleSequence> clone(const std::string& name) const std::shared_ptr<ScTitleSequence> clone(const std::string& name) const

View File

@ -39,9 +39,10 @@
#include <openrct2/windows/Intent.h> #include <openrct2/windows/Intent.h>
#include <openrct2/world/Map.h> #include <openrct2/world/Map.h>
#include <openrct2/world/Scenery.h> #include <openrct2/world/Scenery.h>
#include <stdexcept>
using namespace OpenRCT2; namespace OpenRCT2::Title
{
class TitleSequencePlayer final : public ITitleSequencePlayer class TitleSequencePlayer final : public ITitleSequencePlayer
{ {
private: private:
@ -51,9 +52,9 @@ private:
int32_t _position = 0; int32_t _position = 0;
int32_t _waitCounter = 0; int32_t _waitCounter = 0;
int32_t _lastScreenWidth = 0; int32_t _previousWindowWidth = 0;
int32_t _lastScreenHeight = 0; int32_t _previousWindowHeight = 0;
CoordsXY _viewCentreLocation = {}; ScreenCoordsXY _previousViewPosition = {};
public: public:
explicit TitleSequencePlayer(GameState& gameState) explicit TitleSequencePlayer(GameState& gameState)
@ -78,6 +79,8 @@ public:
bool Begin(size_t titleSequenceId) override bool Begin(size_t titleSequenceId) override
{ {
StoreCurrentViewLocation();
size_t numSequences = TitleSequenceManager::GetCount(); size_t numSequences = TitleSequenceManager::GetCount();
if (titleSequenceId >= numSequences) if (titleSequenceId >= numSequences)
{ {
@ -100,50 +103,81 @@ public:
bool Update() override bool Update() override
{ {
int32_t entryPosition = _position; RestoreViewLocationIfResized();
FixViewLocation();
if (_sequence == nullptr) if (_sequence == nullptr)
{ {
SetViewLocation(TileCoordsXY(75, 75).ToCoordsXY());
return false; return false;
} }
// Check that position is valid // Run commands in order, until we reach one that is not instantly done
if (_position >= static_cast<int32_t>(_sequence->Commands.size())) int32_t entryPosition = _position;
{
_position = 0;
return false;
}
// Don't execute next command until we are done with the current wait command
if (_waitCounter != 0)
{
_waitCounter--;
if (_waitCounter == 0)
{
const auto& command = _sequence->Commands[_position];
if (command.Type == TitleScript::Wait)
{
IncrementPosition();
}
}
}
else
{
while (true) while (true)
{ {
const auto& command = _sequence->Commands[_position]; auto& currentCommand = _sequence->Commands[_position];
if (ExecuteCommand(command)) try
{ {
if (command.Type == TitleScript::Wait) int framesToWait = std::visit([&](auto& command) { return command(_waitCounter); }, currentCommand);
if (framesToWait > _waitCounter)
{ {
_waitCounter++;
break; break;
} }
if (command.Type != TitleScript::Restart)
// TODO: Make the loading interface simpler so these blocks can be moved to their respective command classes
if (std::holds_alternative<LoadParkCommand>(currentCommand))
{ {
IncrementPosition(); bool loadSuccess = false;
const auto saveIndex = std::get<LoadParkCommand>(currentCommand).SaveIndex;
auto parkHandle = TitleSequenceGetParkHandle(*_sequence, saveIndex);
if (parkHandle != nullptr)
{
game_notify_map_change();
loadSuccess = LoadParkFromStream(parkHandle->Stream.get(), parkHandle->HintPath);
} }
if (!loadSuccess)
{
if (_sequence->Saves.size() > saveIndex)
{
const auto& path = _sequence->Saves[saveIndex];
throw std::domain_error("Failed to load: \"" + path + "\" for the title sequence.");
}
throw std::out_of_range("Failed to load park; index out of range.");
}
game_notify_map_changed();
}
else if (std::holds_alternative<LoadScenarioCommand>(currentCommand))
{
auto& scenarioName = std::get<LoadScenarioCommand>(currentCommand).Scenario;
bool loadSuccess = false;
auto scenario = GetScenarioRepository()->GetByInternalName(scenarioName);
if (scenario != nullptr)
{
game_notify_map_change();
loadSuccess = LoadParkFromFile(scenario->path);
}
if (!loadSuccess)
{
auto message = std::string("Failed to load: \"") + scenarioName + " for the title sequence.";
throw std::domain_error(message);
}
game_notify_map_changed();
}
}
catch (std::exception& e)
{
const char* commandName = std::visit(
[](auto&& command) { return std::decay_t<decltype(command)>::Name; }, currentCommand);
Console::Error::WriteLine("%s (command %i) failed with error: %s", commandName, _position, e.what());
Console::Error::WriteLine(" Skipping to the next command.");
}
IncrementPosition();
if (_position == entryPosition) if (_position == entryPosition)
{ {
Console::Error::WriteLine("Infinite loop detected in title sequence."); Console::Error::WriteLine("Infinite loop detected in title sequence.");
@ -151,16 +185,10 @@ public:
return false; return false;
} }
} }
else
{ // Store current window size and screen position in case the window resizes and the main focus changes
if (!SkipToNextLoadCommand() || _position == entryPosition) StoreCurrentViewLocation();
{
Console::Error::WriteLine("Unable to load any parks from %s.", _sequence->Name.c_str());
return false;
}
}
}
}
return true; return true;
} }
@ -181,10 +209,11 @@ public:
Reset(); Reset();
} }
if (_sequence->Commands[targetPosition].Type == TitleScript::Restart) if (std::holds_alternative<RestartCommand>(_sequence->Commands[targetPosition]))
{ {
targetPosition = 0; targetPosition = 0;
} }
// Set position to the last LOAD command before target position // Set position to the last LOAD command before target position
for (int32_t i = targetPosition; i >= 0; i--) for (int32_t i = targetPosition; i >= 0; i--)
{ {
@ -225,6 +254,7 @@ private:
{ {
_position = 0; _position = 0;
} }
_waitCounter = 0;
} }
bool SkipToNextLoadCommand() bool SkipToNextLoadCommand()
@ -239,134 +269,6 @@ private:
return _position != entryPosition; return _position != entryPosition;
} }
bool ExecuteCommand(const TitleCommand& command)
{
switch (command.Type)
{
case TitleScript::End:
_waitCounter = 1;
break;
case TitleScript::Wait:
// The waitCounter is measured in 25-ms game ticks. Previously it was seconds * 40 ticks/second, now it is ms /
// 25 ms/tick
_waitCounter = std::max<int32_t>(
1, command.Milliseconds / static_cast<uint32_t>(GAME_UPDATE_TIME_MS * 1000.0f));
break;
case TitleScript::Location:
{
auto loc = TileCoordsXY(command.Location.X, command.Location.Y).ToCoordsXY().ToTileCentre();
SetViewLocation(loc);
break;
}
case TitleScript::Undefined:
break;
case TitleScript::Loop:
break;
case TitleScript::EndLoop:
break;
case TitleScript::Rotate:
RotateView(command.Rotations);
break;
case TitleScript::Zoom:
SetViewZoom(ZoomLevel{ static_cast<int8_t>(command.Zoom) });
break;
case TitleScript::Speed:
gGameSpeed = std::clamp<uint8_t>(command.Speed, 1, 4);
break;
case TitleScript::Follow:
FollowSprite(command.Follow.SpriteIndex);
break;
case TitleScript::Restart:
Reset();
break;
case TitleScript::Load:
{
bool loadSuccess = false;
uint8_t saveIndex = command.SaveIndex;
auto parkHandle = TitleSequenceGetParkHandle(*_sequence, saveIndex);
if (parkHandle != nullptr)
{
game_notify_map_change();
loadSuccess = LoadParkFromStream(parkHandle->Stream.get(), parkHandle->HintPath);
}
if (loadSuccess)
{
game_notify_map_changed();
}
else
{
if (_sequence->Saves.size() > saveIndex)
{
const auto& path = _sequence->Saves[saveIndex];
Console::Error::WriteLine("Failed to load: \"%s\" for the title sequence.", path.c_str());
}
return false;
}
break;
}
case TitleScript::LoadSc:
{
bool loadSuccess = false;
auto scenario = GetScenarioRepository()->GetByInternalName(command.Scenario);
if (scenario != nullptr)
{
game_notify_map_change();
loadSuccess = LoadParkFromFile(scenario->path);
}
if (loadSuccess)
{
game_notify_map_changed();
}
else
{
Console::Error::WriteLine("Failed to load: \"%s\" for the title sequence.", command.Scenario);
return false;
}
break;
}
}
return true;
}
void SetViewZoom(ZoomLevel zoom)
{
rct_window* w = window_get_main();
if (w != nullptr && w->viewport != nullptr)
{
window_zoom_set(w, zoom, false);
}
}
void RotateView(uint32_t count)
{
rct_window* w = window_get_main();
if (w != nullptr)
{
for (uint32_t i = 0; i < count; i++)
{
window_rotate_camera(w, 1);
}
}
}
void FollowSprite(EntityId spriteIndex)
{
rct_window* w = window_get_main();
if (w != nullptr)
{
window_follow_sprite(w, spriteIndex);
}
}
void UnfollowSprite()
{
rct_window* w = window_get_main();
if (w != nullptr)
{
window_unfollow_sprite(w);
}
}
bool LoadParkFromFile(const utf8* path) bool LoadParkFromFile(const utf8* path)
{ {
log_verbose("TitleSequencePlayer::LoadParkFromFile(%s)", path); log_verbose("TitleSequencePlayer::LoadParkFromFile(%s)", path);
@ -478,44 +380,29 @@ private:
gGameSpeed = 1; gGameSpeed = 1;
} }
/** void StoreCurrentViewLocation()
* Sets the map location to the given (big) coordinates. Z is automatic.
* @param loc X and Y position in big coordinates.
*/
void SetViewLocation(const CoordsXY& loc)
{ {
// Update viewport
rct_window* w = window_get_main(); rct_window* w = window_get_main();
if (w != nullptr) if (w != nullptr && w->viewport_smart_follow_sprite.IsNull())
{ {
int32_t z = tile_element_height(loc); _previousWindowWidth = w->width;
_previousWindowHeight = w->height;
// Prevent scroll adjustment due to window placement when in-game _previousViewPosition = w->savedViewPos;
auto oldScreenFlags = gScreenFlags;
gScreenFlags = SCREEN_FLAGS_TITLE_DEMO;
w->SetLocation({ loc, z });
gScreenFlags = oldScreenFlags;
viewport_update_position(w);
// Save known tile position in case of window resize
_lastScreenWidth = w->width;
_lastScreenHeight = w->height;
_viewCentreLocation = loc;
} }
} }
/** /**
* Fixes the view location for when the game window has changed size. * Fixes the view location for when the game window has changed size.
*/ */
void FixViewLocation() void RestoreViewLocationIfResized()
{ {
rct_window* w = window_get_main(); rct_window* w = window_get_main();
if (w != nullptr && w->viewport_smart_follow_sprite.IsNull()) if (w != nullptr && w->viewport_smart_follow_sprite.IsNull())
{ {
if (w->width != _lastScreenWidth || w->height != _lastScreenHeight) if (w->width != _previousWindowWidth || w->height != _previousWindowHeight)
{ {
SetViewLocation(_viewCentreLocation); w->savedViewPos.x += (_previousWindowWidth - w->width) / 2;
w->savedViewPos.y += (_previousWindowHeight - w->height) / 2;
} }
} }
} }
@ -525,3 +412,4 @@ std::unique_ptr<ITitleSequencePlayer> CreateTitleSequencePlayer(GameState& gameS
{ {
return std::make_unique<TitleSequencePlayer>(gameState); return std::make_unique<TitleSequencePlayer>(gameState);
} }
} // namespace OpenRCT2::Title

View File

@ -18,6 +18,9 @@ struct IScenarioRepository;
namespace OpenRCT2 namespace OpenRCT2
{ {
class GameState; class GameState;
}
[[nodiscard]] std::unique_ptr<ITitleSequencePlayer> CreateTitleSequencePlayer(OpenRCT2::GameState& gameState); namespace Title
{
[[nodiscard]] std::unique_ptr<ITitleSequencePlayer> CreateTitleSequencePlayer(GameState& gameState);
} // namespace Title
} // namespace OpenRCT2

View File

@ -27,7 +27,6 @@ struct rct_drawpixelinfo;
struct rct_window; struct rct_window;
union rct_window_event; union rct_window_event;
struct track_design_file_ref; struct track_design_file_ref;
struct TitleSequence;
struct TextInputSession; struct TextInputSession;
struct scenario_index_entry; struct scenario_index_entry;

View File

@ -497,6 +497,16 @@
<ClInclude Include="scripting\bindings\world\ScTile.hpp" /> <ClInclude Include="scripting\bindings\world\ScTile.hpp" />
<ClInclude Include="sprites.h" /> <ClInclude Include="sprites.h" />
<ClInclude Include="System.hpp" /> <ClInclude Include="System.hpp" />
<ClInclude Include="title\Command\End.h" />
<ClInclude Include="title\Command\FollowEntity.h" />
<ClInclude Include="title\Command\LoadPark.h" />
<ClInclude Include="title\Command\LoadScenario.h" />
<ClInclude Include="title\Command\Restart.h" />
<ClInclude Include="title\Command\RotateView.h" />
<ClInclude Include="title\Command\SetLocation.h" />
<ClInclude Include="title\Command\SetSpeed.h" />
<ClInclude Include="title\Command\SetZoom.h" />
<ClInclude Include="title\Command\Wait.h" />
<ClInclude Include="title\TitleScreen.h" /> <ClInclude Include="title\TitleScreen.h" />
<ClInclude Include="title\TitleSequence.h" /> <ClInclude Include="title\TitleSequence.h" />
<ClInclude Include="title\TitleSequenceManager.h" /> <ClInclude Include="title\TitleSequenceManager.h" />
@ -941,6 +951,16 @@
<ClCompile Include="scripting\HookEngine.cpp" /> <ClCompile Include="scripting\HookEngine.cpp" />
<ClCompile Include="scripting\Plugin.cpp" /> <ClCompile Include="scripting\Plugin.cpp" />
<ClCompile Include="scripting\ScriptEngine.cpp" /> <ClCompile Include="scripting\ScriptEngine.cpp" />
<ClCompile Include="title\Command\End.cpp" />
<ClCompile Include="title\Command\FollowEntity.cpp" />
<ClCompile Include="title\Command\LoadPark.cpp" />
<ClCompile Include="title\Command\LoadScenario.cpp" />
<ClCompile Include="title\Command\Restart.cpp" />
<ClCompile Include="title\Command\RotateView.cpp" />
<ClCompile Include="title\Command\SetLocation.cpp" />
<ClCompile Include="title\Command\SetSpeed.cpp" />
<ClCompile Include="title\Command\SetZoom.cpp" />
<ClCompile Include="title\Command\Wait.cpp" />
<ClCompile Include="title\TitleScreen.cpp" /> <ClCompile Include="title\TitleScreen.cpp" />
<ClCompile Include="title\TitleSequence.cpp" /> <ClCompile Include="title\TitleSequence.cpp" />
<ClCompile Include="title\TitleSequenceManager.cpp" /> <ClCompile Include="title\TitleSequenceManager.cpp" />

View File

@ -46,7 +46,7 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting namespace OpenRCT2::Scripting
{ {
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 56; static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 57;
// Versions marking breaking changes. // Versions marking breaking changes.
static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33; static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33;

View File

@ -0,0 +1,19 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "End.h"
namespace OpenRCT2::Title
{
int16_t EndCommand::operator()(int16_t timer)
{
// The end command is used as a tag, no logic required here.
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,23 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct EndCommand
{
static constexpr const char* Name = "End Command";
static constexpr const char* ScriptingName = "end";
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,26 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "FollowEntity.h"
#include "../../interface/Window.h"
namespace OpenRCT2::Title
{
int16_t FollowEntityCommand::operator()(int16_t timer)
{
auto* w = window_get_main();
if (w != nullptr)
{
window_follow_sprite(w, Follow.SpriteIndex);
}
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,33 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include "../../Identifiers.h"
#include "../../core/String.hpp"
#include "../../localisation/Localisation.h"
#include <cstdint>
namespace OpenRCT2::Title
{
struct FollowEntityCommand
{
static constexpr const char* Name = "Follow Entity Command";
static constexpr const char* ScriptingName = "follow";
struct
{
EntityId SpriteIndex{ EntityId::GetNull() };
utf8 SpriteName[USER_STRING_MAX_LENGTH]{};
} Follow;
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,19 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "LoadPark.h"
namespace OpenRCT2::Title
{
int16_t LoadParkCommand::operator()(int16_t timer)
{
// Park loading is currently handled by the title sequence player
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,25 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct LoadParkCommand
{
static constexpr const char* Name = "Load Park Command";
static constexpr const char* ScriptingName = "load";
uint8_t SaveIndex{};
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,19 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "LoadScenario.h"
namespace OpenRCT2::Title
{
int16_t LoadScenarioCommand::operator()(int16_t timer)
{
// Scenario loading is currently handled by the title sequence player
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,29 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include "../../core/String.hpp"
#include <cstdint>
#define TITLE_COMMAND_SCENARIO_LENGTH 64
namespace OpenRCT2::Title
{
struct LoadScenarioCommand
{
static constexpr const char* Name = "Load Scenario Command";
static constexpr const char* ScriptingName = "loadsc";
utf8 Scenario[TITLE_COMMAND_SCENARIO_LENGTH]{};
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,19 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "Restart.h"
namespace OpenRCT2::Title
{
int16_t RestartCommand::operator()(int16_t timer)
{
// The restart command is used as a tag, no logic required here.
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,23 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct RestartCommand
{
static constexpr const char* Name = "Restart Command";
static constexpr const char* ScriptingName = "restart";
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,29 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "RotateView.h"
#include "../../interface/Window.h"
namespace OpenRCT2::Title
{
int16_t RotateViewCommand::operator()(int16_t timer)
{
rct_window* w = window_get_main();
if (w != nullptr)
{
for (uint_fast8_t i = 0; i < Rotations; i++)
{
window_rotate_camera(w, 1);
}
}
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,25 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct RotateViewCommand
{
static constexpr const char* Name = "Rotate View Command";
static constexpr const char* ScriptingName = "rotate";
uint8_t Rotations{};
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,38 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "SetLocation.h"
#include "../../OpenRCT2.h"
#include "../../interface/Window.h"
#include "../../interface/Window_internal.h"
#include "../../world/Map.h"
namespace OpenRCT2::Title
{
int16_t SetLocationCommand::operator()(int16_t timer)
{
rct_window* w = window_get_main();
if (w != nullptr)
{
auto loc = TileCoordsXY(Location.X, Location.Y).ToCoordsXY().ToTileCentre();
int32_t z = tile_element_height(loc);
// Prevent scroll adjustment due to window placement when in-game
auto oldScreenFlags = gScreenFlags;
gScreenFlags = SCREEN_FLAGS_TITLE_DEMO;
w->SetLocation({ loc, z });
gScreenFlags = oldScreenFlags;
viewport_update_position(w);
}
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,30 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct SetLocationCommand
{
static constexpr const char* Name = "Set Location Command";
static constexpr const char* ScriptingName = "location";
// TODO: Use TileCoordsXY instead
struct
{
uint8_t X;
uint8_t Y;
} Location;
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,24 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "SetSpeed.h"
#include "../../Game.h"
#include <algorithm>
namespace OpenRCT2::Title
{
int16_t SetSpeedCommand::operator()(int16_t timer)
{
gGameSpeed = std::clamp<uint8_t>(Speed, 1, 4);
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,25 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct SetSpeedCommand
{
static constexpr const char* Name = "Set Speed Command";
static constexpr const char* ScriptingName = "speed";
uint8_t Speed{};
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,27 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "SetZoom.h"
#include "../../interface/Window.h"
#include "../../interface/ZoomLevel.h"
namespace OpenRCT2::Title
{
int16_t SetZoomCommand::operator()(int16_t timer)
{
rct_window* w = window_get_main();
if (w != nullptr)
{
window_zoom_set(w, ZoomLevel{ static_cast<int8_t>(Zoom) }, false);
}
return 0;
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,26 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct SetZoomCommand
{
static constexpr const char* Name = "Set Zoom Command";
static constexpr const char* ScriptingName = "zoom";
// TODO: Use ZoomLevel instead
uint8_t Zoom{};
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,23 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "Wait.h"
#include "../../Context.h"
#include <algorithm>
namespace OpenRCT2::Title
{
int16_t WaitCommand::operator()(int16_t timer)
{
// Return number of game ticks this wait command lasts
return std::max<int16_t>(1, GAME_UPDATE_FPS * Milliseconds / 1000);
}
} // namespace OpenRCT2::Title

View File

@ -0,0 +1,25 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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.
*****************************************************************************/
#pragma once
#include <cstdint>
namespace OpenRCT2::Title
{
struct WaitCommand
{
static constexpr const char* Name = "Wait Command";
static constexpr const char* ScriptingName = "wait";
uint16_t Milliseconds{};
int16_t operator()(int16_t timer);
};
} // namespace OpenRCT2::Title

View File

@ -29,8 +29,14 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <memory> #include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector> #include <vector>
namespace OpenRCT2::Title
{
static std::vector<std::string> GetSaves(const std::string& path); static std::vector<std::string> GetSaves(const std::string& path);
static std::vector<std::string> GetSaves(IZipArchive* zip); static std::vector<std::string> GetSaves(IZipArchive* zip);
static std::vector<TitleCommand> LegacyScriptRead(const std::vector<uint8_t>& script, std::vector<std::string> saves); static std::vector<TitleCommand> LegacyScriptRead(const std::vector<uint8_t>& script, std::vector<std::string> saves);
@ -87,7 +93,7 @@ std::unique_ptr<TitleSequence> LoadTitleSequence(const std::string& path)
auto commands = LegacyScriptRead(script, saves); auto commands = LegacyScriptRead(script, saves);
auto seq = CreateTitleSequence(); auto seq = OpenRCT2::Title::CreateTitleSequence();
seq->Name = Path::GetFileNameWithoutExtension(path); seq->Name = Path::GetFileNameWithoutExtension(path);
seq->Path = path; seq->Path = path;
seq->Saves = saves; seq->Saves = saves;
@ -118,7 +124,8 @@ std::unique_ptr<TitleSequenceParkHandle> TitleSequenceGetParkHandle(const TitleS
} }
else else
{ {
Console::Error::WriteLine("Failed to open zipped path '%s' from zip '%s'", filename.c_str(), seq.Path.c_str()); Console::Error::WriteLine(
"Failed to open zipped path '%s' from zip '%s'", filename.c_str(), seq.Path.c_str());
} }
} }
else else
@ -268,9 +275,11 @@ bool TitleSequenceRemovePark(TitleSequence& seq, size_t index)
seq.Saves.erase(seq.Saves.begin() + index); seq.Saves.erase(seq.Saves.begin() + index);
// Update load commands // Update load commands
for (auto& command : seq.Commands) for (auto& seqCommand : seq.Commands)
{ {
if (command.Type == TitleScript::Load) std::visit(
[index](auto&& command) {
if constexpr (std::is_same_v<std::decay_t<decltype(command)>, LoadParkCommand>)
{ {
if (command.SaveIndex == index) if (command.SaveIndex == index)
{ {
@ -283,6 +292,8 @@ bool TitleSequenceRemovePark(TitleSequence& seq, size_t index)
command.SaveIndex--; command.SaveIndex--;
} }
} }
},
seqCommand);
} }
return true; return true;
@ -328,73 +339,76 @@ static std::vector<TitleCommand> LegacyScriptRead(const std::vector<uint8_t>& sc
LegacyScriptGetLine(&fs, parts); LegacyScriptGetLine(&fs, parts);
const char* token = parts[0].data(); const char* token = parts[0].data();
TitleCommand command = {}; std::optional<TitleCommand> command = std::nullopt;
command.Type = TitleScript::Undefined;
if (token[0] != 0) if (token[0] != 0)
{ {
if (_stricmp(token, "LOAD") == 0) if (_stricmp(token, "LOAD") == 0)
{ {
command.Type = TitleScript::Load; auto saveIndex = SAVE_INDEX_INVALID;
command.SaveIndex = SAVE_INDEX_INVALID; const std::string relativePath = parts[1].data();
for (size_t i = 0; i < saves.size(); i++) for (size_t i = 0; i < saves.size(); i++)
{ {
if (String::Equals(parts[1].data(), saves[i], true)) if (String::Equals(relativePath, saves[i], true))
{ {
command.SaveIndex = static_cast<uint8_t>(i); saveIndex = static_cast<uint8_t>(i);
break; break;
} }
} }
command = LoadParkCommand{ saveIndex };
} }
else if (_stricmp(token, "LOCATION") == 0) else if (_stricmp(token, "LOCATION") == 0)
{ {
command.Type = TitleScript::Location; uint8_t locationX = atoi(parts[1].data()) & 0xFF;
command.Location.X = atoi(parts[1].data()) & 0xFF; uint8_t locationY = atoi(parts[2].data()) & 0xFF;
command.Location.Y = atoi(parts[2].data()) & 0xFF; command = SetLocationCommand{ locationX, locationY };
} }
else if (_stricmp(token, "ROTATE") == 0) else if (_stricmp(token, "ROTATE") == 0)
{ {
command.Type = TitleScript::Rotate; uint8_t rotations = atoi(parts[1].data()) & 0xFF;
command.Rotations = atoi(parts[1].data()) & 0xFF; command = RotateViewCommand{ rotations };
} }
else if (_stricmp(token, "ZOOM") == 0) else if (_stricmp(token, "ZOOM") == 0)
{ {
command.Type = TitleScript::Zoom; uint8_t zoom = atoi(parts[1].data()) & 0xFF;
command.Zoom = atoi(parts[1].data()) & 0xFF; command = SetZoomCommand{ zoom };
} }
else if (_stricmp(token, "SPEED") == 0) else if (_stricmp(token, "SPEED") == 0)
{ {
command.Type = TitleScript::Speed; uint8_t speed = std::max(1, std::min(4, atoi(parts[1].data()) & 0xFF));
command.Speed = std::max(1, std::min(4, atoi(parts[1].data()) & 0xFF)); command = SetSpeedCommand{ speed };
} }
else if (_stricmp(token, "FOLLOW") == 0) else if (_stricmp(token, "FOLLOW") == 0)
{ {
command.Type = TitleScript::Follow; auto entityID = EntityId::FromUnderlying(atoi(parts[1].data()) & 0xFFFF);
command.Follow.SpriteIndex = EntityId::FromUnderlying(atoi(parts[1].data()) & 0xFFFF); auto followCommand = FollowEntityCommand{ entityID };
safe_strcpy(command.Follow.SpriteName, parts[2].data(), USER_STRING_MAX_LENGTH); safe_strcpy(followCommand.Follow.SpriteName, parts[2].data(), USER_STRING_MAX_LENGTH);
command = followCommand;
} }
else if (_stricmp(token, "WAIT") == 0) else if (_stricmp(token, "WAIT") == 0)
{ {
command.Type = TitleScript::Wait; uint16_t milliseconds = atoi(parts[1].data()) & 0xFFFF;
command.Milliseconds = atoi(parts[1].data()) & 0xFFFF; command = WaitCommand{ milliseconds };
} }
else if (_stricmp(token, "RESTART") == 0) else if (_stricmp(token, "RESTART") == 0)
{ {
command.Type = TitleScript::Restart; command = RestartCommand{};
} }
else if (_stricmp(token, "END") == 0) else if (_stricmp(token, "END") == 0)
{ {
command.Type = TitleScript::End; command = EndCommand{};
} }
else if (_stricmp(token, "LOADSC") == 0) else if (_stricmp(token, "LOADSC") == 0)
{ {
command.Type = TitleScript::LoadSc; auto loadScenarioCommand = LoadScenarioCommand{};
safe_strcpy(command.Scenario, parts[1].data(), sizeof(command.Scenario)); safe_strcpy(loadScenarioCommand.Scenario, parts[1].data(), sizeof(loadScenarioCommand.Scenario));
command = loadScenarioCommand;
} }
} }
if (command.Type != TitleScript::Undefined)
if (command.has_value())
{ {
commands.push_back(std::move(command)); commands.push_back(std::move(*command));
} }
} while (fs.GetPosition() < fs.GetLength()); } while (fs.GetPosition() < fs.GetLength());
return commands; return commands;
@ -493,11 +507,13 @@ static std::string LegacyScriptWrite(const TitleSequence& seq)
sb.Append("# SCRIPT FOR "); sb.Append("# SCRIPT FOR ");
sb.Append(seq.Name.c_str()); sb.Append(seq.Name.c_str());
sb.Append("\n"); sb.Append("\n");
for (const auto& command : seq.Commands) for (const auto& seqCommand : seq.Commands)
{ {
switch (command.Type) std::visit(
[&buffer, &seq, &sb](auto&& command) {
using T = std::decay_t<decltype(command)>;
if constexpr (std::is_same_v<T, LoadParkCommand>)
{ {
case TitleScript::Load:
if (command.SaveIndex < seq.Saves.size()) if (command.SaveIndex < seq.Saves.size())
{ {
sb.Append("LOAD "); sb.Append("LOAD ");
@ -507,8 +523,9 @@ static std::string LegacyScriptWrite(const TitleSequence& seq)
{ {
sb.Append("LOAD <No save file>"); sb.Append("LOAD <No save file>");
} }
break; }
case TitleScript::LoadSc: else if constexpr (std::is_same_v<T, LoadScenarioCommand>)
{
if (command.Scenario[0] == '\0') if (command.Scenario[0] == '\0')
{ {
sb.Append("LOADSC <No scenario name>"); sb.Append("LOADSC <No scenario name>");
@ -518,44 +535,48 @@ static std::string LegacyScriptWrite(const TitleSequence& seq)
sb.Append("LOADSC "); sb.Append("LOADSC ");
sb.Append(command.Scenario); sb.Append(command.Scenario);
} }
break; }
case TitleScript::Undefined: else if constexpr (std::is_same_v<T, SetLocationCommand>)
break; {
case TitleScript::Loop:
break;
case TitleScript::EndLoop:
break;
case TitleScript::Location:
String::Format(buffer, sizeof(buffer), "LOCATION %u %u", command.Location.X, command.Location.Y); String::Format(buffer, sizeof(buffer), "LOCATION %u %u", command.Location.X, command.Location.Y);
sb.Append(buffer); sb.Append(buffer);
break; }
case TitleScript::Rotate: else if constexpr (std::is_same_v<T, RotateViewCommand>)
{
String::Format(buffer, sizeof(buffer), "ROTATE %u", command.Rotations); String::Format(buffer, sizeof(buffer), "ROTATE %u", command.Rotations);
sb.Append(buffer); sb.Append(buffer);
break; }
case TitleScript::Zoom: else if constexpr (std::is_same_v<T, SetZoomCommand>)
{
String::Format(buffer, sizeof(buffer), "ZOOM %u", command.Zoom); String::Format(buffer, sizeof(buffer), "ZOOM %u", command.Zoom);
sb.Append(buffer); sb.Append(buffer);
break; }
case TitleScript::Follow: else if constexpr (std::is_same_v<T, FollowEntityCommand>)
{
String::Format(buffer, sizeof(buffer), "FOLLOW %u ", command.Follow.SpriteIndex); String::Format(buffer, sizeof(buffer), "FOLLOW %u ", command.Follow.SpriteIndex);
sb.Append(buffer); sb.Append(buffer);
sb.Append(command.Follow.SpriteName); sb.Append(command.Follow.SpriteName);
break; }
case TitleScript::Speed: else if constexpr (std::is_same_v<T, SetSpeedCommand>)
{
String::Format(buffer, sizeof(buffer), "SPEED %u", command.Speed); String::Format(buffer, sizeof(buffer), "SPEED %u", command.Speed);
sb.Append(buffer); sb.Append(buffer);
break; }
case TitleScript::Wait: else if constexpr (std::is_same_v<T, WaitCommand>)
{
String::Format(buffer, sizeof(buffer), "WAIT %u", command.Milliseconds); String::Format(buffer, sizeof(buffer), "WAIT %u", command.Milliseconds);
sb.Append(buffer); sb.Append(buffer);
break; }
case TitleScript::Restart: else if constexpr (std::is_same_v<T, RestartCommand>)
{
sb.Append("RESTART"); sb.Append("RESTART");
break; }
case TitleScript::End: else if constexpr (std::is_same_v<T, EndCommand>)
{
sb.Append("END"); sb.Append("END");
} }
},
seqCommand);
sb.Append("\n"); sb.Append("\n");
} }
@ -564,12 +585,6 @@ static std::string LegacyScriptWrite(const TitleSequence& seq)
bool TitleSequenceIsLoadCommand(const TitleCommand& command) bool TitleSequenceIsLoadCommand(const TitleCommand& command)
{ {
switch (command.Type) return std::holds_alternative<LoadParkCommand>(command) || std::holds_alternative<LoadScenarioCommand>(command);
{
case TitleScript::Load:
case TitleScript::LoadSc:
return true;
default:
return false;
}
} }
} // namespace OpenRCT2::Title

View File

@ -10,36 +10,27 @@
#pragma once #pragma once
#include "../common.h" #include "../common.h"
#include "../localisation/Localisation.h"
#include "../openrct2/core/IStream.hpp" #include "../openrct2/core/IStream.hpp"
#include "Command/End.h"
#include "Command/FollowEntity.h"
#include "Command/LoadPark.h"
#include "Command/LoadScenario.h"
#include "Command/Restart.h"
#include "Command/RotateView.h"
#include "Command/SetLocation.h"
#include "Command/SetSpeed.h"
#include "Command/SetZoom.h"
#include "Command/Wait.h"
#include <memory> #include <memory>
#include <string>
#include <variant>
#define TITLE_COMMAND_SCENARIO_LENGTH 64 namespace OpenRCT2::Title
enum class TitleScript : uint8_t;
struct TitleCommand
{ {
TitleScript Type; using TitleCommand = std::variant<
union WaitCommand, SetLocationCommand, RotateViewCommand, SetZoomCommand, FollowEntityCommand, RestartCommand,
{ LoadParkCommand, EndCommand, SetSpeedCommand, LoadScenarioCommand>;
uint8_t SaveIndex; // LOAD (this index is internal only)
struct // LOCATION
{
uint8_t X;
uint8_t Y;
} Location;
uint8_t Rotations; // ROTATE (counter-clockwise)
uint8_t Zoom; // ZOOM
struct // FOLLOW
{
EntityId SpriteIndex;
utf8 SpriteName[USER_STRING_MAX_LENGTH];
} Follow;
uint8_t Speed; // SPEED
uint16_t Milliseconds; // WAIT
utf8 Scenario[TITLE_COMMAND_SCENARIO_LENGTH]; // LOADSC
};
};
struct TitleSequence struct TitleSequence
{ {
@ -58,23 +49,6 @@ struct TitleSequenceParkHandle
std::unique_ptr<OpenRCT2::IStream> Stream; std::unique_ptr<OpenRCT2::IStream> Stream;
}; };
enum class TitleScript : uint8_t
{
Undefined = 0xFF,
Wait = 0,
Location,
Rotate,
Zoom,
Follow,
Restart,
Load,
End,
Speed,
Loop,
EndLoop,
LoadSc,
};
constexpr const utf8* TITLE_SEQUENCE_EXTENSION = ".parkseq"; constexpr const utf8* TITLE_SEQUENCE_EXTENSION = ".parkseq";
constexpr uint8_t SAVE_INDEX_INVALID = UINT8_MAX; constexpr uint8_t SAVE_INDEX_INVALID = UINT8_MAX;
@ -88,3 +62,4 @@ bool TitleSequenceRenamePark(TitleSequence& seq, size_t index, const utf8* name)
bool TitleSequenceRemovePark(TitleSequence& seq, size_t index); bool TitleSequenceRemovePark(TitleSequence& seq, size_t index);
bool TitleSequenceIsLoadCommand(const TitleCommand& command); bool TitleSequenceIsLoadCommand(const TitleCommand& command);
} // namespace OpenRCT2::Title

View File

@ -103,7 +103,7 @@ namespace TitleSequenceManager
auto newPath = Path::Combine(Path::GetDirectory(oldPath), newName); auto newPath = Path::Combine(Path::GetDirectory(oldPath), newName);
if (item->IsZip) if (item->IsZip)
{ {
newPath += TITLE_SEQUENCE_EXTENSION; newPath += OpenRCT2::Title::TITLE_SEQUENCE_EXTENSION;
File::Move(oldPath, newPath); File::Move(oldPath, newPath);
} }
else else
@ -138,7 +138,7 @@ namespace TitleSequenceManager
size_t CreateItem(const utf8* name) size_t CreateItem(const utf8* name)
{ {
auto seq = CreateTitleSequence(); auto seq = OpenRCT2::Title::CreateTitleSequence();
seq->Name = name; seq->Name = name;
seq->Path = GetNewTitleSequencePath(seq->Name, true); seq->Path = GetNewTitleSequencePath(seq->Name, true);
seq->IsZip = true; seq->IsZip = true;
@ -159,7 +159,7 @@ namespace TitleSequenceManager
auto path = Path::Combine(GetUserSequencesPath(), name); auto path = Path::Combine(GetUserSequencesPath(), name);
if (isZip) if (isZip)
{ {
path += TITLE_SEQUENCE_EXTENSION; path += OpenRCT2::Title::TITLE_SEQUENCE_EXTENSION;
} }
return path; return path;
} }