diff --git a/binaries/data/mods/public/maps/scenarios/cinema_demo.js b/binaries/data/mods/public/maps/scenarios/cinema_demo.js index a38088d75f..293e9b0f00 100644 --- a/binaries/data/mods/public/maps/scenarios/cinema_demo.js +++ b/binaries/data/mods/public/maps/scenarios/cinema_demo.js @@ -1,22 +1,21 @@ Trigger.prototype.CounterMessage = function(data) { - var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGUIInterface.PushNotification({ + Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface)?.PushNotification({ "players": [1, 2], "message": markForTranslation("Cutscene starts after 5 seconds"), "translateMessage": true }); }; -Trigger.prototype.StartCutscene = function(data) +Trigger.prototype.StartCutscene = function() { - var cmpCinemaManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CinemaManager); + const cmpCinemaManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CinemaManager); if (!cmpCinemaManager) return; - cmpCinemaManager.AddCinemaPathToQueue("test"); - cmpCinemaManager.Play(); + cmpCinemaManager.PushPathToQueue("test"); + cmpCinemaManager.StartPlayingQueue(); }; -var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); +const cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); cmpTrigger.DoAfterDelay(1000, "CounterMessage", {}); -cmpTrigger.DoAfterDelay(6000, "StartCutscene", {}); +cmpTrigger.DoAfterDelay(5000, "StartCutscene", {}); diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 1f892f7ea7..d3cdcde055 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -155,7 +155,7 @@ GuiInterface.prototype.GetSimulationState = function() const cmpCinemaManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CinemaManager); if (cmpCinemaManager) - ret.cinemaPlaying = cmpCinemaManager.IsPlaying(); + ret.cinemaPlaying = cmpCinemaManager.IsPlayingQueue(); const cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); ret.victoryConditions = cmpEndGameManager.GetVictoryConditions(); diff --git a/source/graphics/CinemaManager.cpp b/source/graphics/CinemaManager.cpp index 9f8ac7a5a2..c18713c8fd 100644 --- a/source/graphics/CinemaManager.cpp +++ b/source/graphics/CinemaManager.cpp @@ -51,12 +51,12 @@ void CCinemaManager::Update(const float deltaRealTime) const return; if (IsPlaying()) - cmpCinemaManager->PlayQueue(deltaRealTime, g_Game->GetView()->GetCamera()); + cmpCinemaManager->UpdateActivePath(deltaRealTime, g_Game->GetView()->GetCamera()); } void CCinemaManager::Render(Renderer::Backend::IDeviceCommandContext& deviceCommandContext) const { - if (!IsEnabled() && m_DrawPaths) + if (!IsPlaying() && m_DrawPaths) DrawPaths(deviceCommandContext); } @@ -121,15 +121,10 @@ void CCinemaManager::DrawNodes(Renderer::Backend::IDeviceCommandContext& deviceC } } -bool CCinemaManager::IsEnabled() const -{ - CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); - return cmpCinemaManager && cmpCinemaManager->IsEnabled(); -} - bool CCinemaManager::IsPlaying() const { - return IsEnabled() && g_Game && !g_Game->m_Paused; + CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); + return cmpCinemaManager && cmpCinemaManager->IsPlayingQueue(); } bool CCinemaManager::GetPathsDrawing() const diff --git a/source/graphics/CinemaManager.h b/source/graphics/CinemaManager.h index b7d4d3a458..6f67493d00 100644 --- a/source/graphics/CinemaManager.h +++ b/source/graphics/CinemaManager.h @@ -39,7 +39,6 @@ public: void Render(Renderer::Backend::IDeviceCommandContext& deviceCommandContext) const; bool IsPlaying() const; - bool IsEnabled() const; /** * Updates CCinemManager and current path diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index d67739214e..03cccac34c 100644 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -291,6 +291,9 @@ void CGameView::Update(const float deltaRealTime) { m->MiniMapTexture.Update(deltaRealTime); + m->CinemaManager.Update(deltaRealTime); + if (m->CinemaManager.IsPlaying()) + return; // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) @@ -299,10 +302,6 @@ void CGameView::Update(const float deltaRealTime) if (!g_app_has_focus) return; - m->CinemaManager.Update(deltaRealTime); - if (m->CinemaManager.IsEnabled()) - return; - m->CameraController->Update(deltaRealTime); } @@ -354,7 +353,7 @@ entity_id_t CGameView::GetFollowedEntity() InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here - if (!g_app_has_focus || !g_Game || !g_Game->IsGameStarted() || g_Game->GetView()->GetCinema()->IsEnabled()) + if (!g_app_has_focus || !g_Game || !g_Game->IsGameStarted() || g_Game->GetView()->GetCinema()->IsPlaying()) return IN_PASS; CGameView *pView=g_Game->GetView(); diff --git a/source/simulation2/components/CCmpCinemaManager.cpp b/source/simulation2/components/CCmpCinemaManager.cpp index bc416e1e20..fc82ab172f 100644 --- a/source/simulation2/components/CCmpCinemaManager.cpp +++ b/source/simulation2/components/CCmpCinemaManager.cpp @@ -26,11 +26,10 @@ #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Game.h" +#include "renderer/Renderer.h" +#include "renderer/SceneRenderer.h" #include "simulation2/MessageTypes.h" -#include "simulation2/components/ICmpOverlayRenderer.h" #include "simulation2/components/ICmpRangeManager.h" -#include "simulation2/components/ICmpSelectable.h" -#include "simulation2/components/ICmpTerritoryManager.h" #include "simulation2/helpers/CinemaPath.h" #include "simulation2/system/Component.h" #include "simulation2/system/Entity.h" @@ -50,6 +49,7 @@ public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeToMessageType(MT_Update); + componentManager.SubscribeToMessageType(MT_Deserialized); } DEFAULT_COMPONENT_ALLOCATOR(CinemaManager) @@ -61,11 +61,11 @@ public: void Init(const CParamNode&) override { - m_Enabled = false; - m_MapRevealed = false; - m_ElapsedTime = fixed::Zero(); - m_TotalTime = fixed::Zero(); - m_CurrentPathElapsedTime = fixed::Zero(); + m_IsPlayingPathQueue = false; + m_QueuePlayingElapsedTime = fixed::Zero(); + m_PathQueueDuration = fixed::Zero(); + m_ActivePathElapsedTime = fixed::Zero(); + m_WasMapRevealed = false; } void Deinit() override @@ -74,10 +74,10 @@ public: void Serialize(ISerializer& serializer) override { - serializer.Bool("Enabled", m_Enabled); - serializer.NumberFixed_Unbounded("ElapsedTime", m_ElapsedTime); - serializer.NumberFixed_Unbounded("CurrentPathElapsedTime", m_CurrentPathElapsedTime); - serializer.Bool("MapRevealed", m_MapRevealed); + serializer.Bool("IsPlayingPathQueue", m_IsPlayingPathQueue); + serializer.NumberFixed_Unbounded("QueueElapsedTime", m_QueuePlayingElapsedTime); + serializer.NumberFixed_Unbounded("CurrentPathElapsedTime", m_ActivePathElapsedTime); + serializer.Bool("WasMapRevealed", m_WasMapRevealed); serializer.NumberU32_Unbounded("NumberOfPaths", m_Paths.size()); for (const std::pair& it : m_Paths) @@ -85,15 +85,15 @@ public: serializer.NumberU32_Unbounded("NumberOfQueuedPaths", m_PathQueue.size()); for (const CCinemaPath& path : m_PathQueue) - serializer.String("PathName", path.GetName(), 1, 128); + serializer.String("QueuedPathName", path.GetName(), 1, 128); } void Deserialize(const CParamNode&, IDeserializer& deserializer) override { - deserializer.Bool("Enabled", m_Enabled); - deserializer.NumberFixed_Unbounded("ElapsedTime", m_ElapsedTime); - deserializer.NumberFixed_Unbounded("CurrentPathElapsedTime", m_CurrentPathElapsedTime); - deserializer.Bool("MapRevealed", m_MapRevealed); + deserializer.Bool("IsPlayingPathQueue", m_IsPlayingPathQueue); + deserializer.NumberFixed_Unbounded("QueueElapsedTime", m_QueuePlayingElapsedTime); + deserializer.NumberFixed_Unbounded("CurrentPathElapsedTime", m_ActivePathElapsedTime); + deserializer.Bool("WasMapRevealed", m_WasMapRevealed); uint32_t numberOfPaths = 0; deserializer.NumberU32_Unbounded("NumberOfPaths", numberOfPaths); @@ -108,55 +108,53 @@ public: for (uint32_t i = 0; i < numberOfQueuedPaths; ++i) { CStrW pathName; - deserializer.String("PathName", pathName, 1, 128); + deserializer.String("QueuedPathName", pathName, 1, 128); ENSURE(HasPath(pathName)); - AddCinemaPathToQueue(pathName); + PushPathToQueue(pathName); } if (!m_PathQueue.empty()) { - m_PathQueue.front().m_TimeElapsed = m_CurrentPathElapsedTime.ToFloat(); + m_PathQueue.front().m_TimeElapsed = m_ActivePathElapsedTime.ToFloat(); m_PathQueue.front().Validate(); } - - SetEnabled(m_Enabled); } void HandleMessage(const CMessage& msg, bool /*global*/) override { switch (msg.GetType()) { + case MT_Deserialized: + if (!m_IsPlayingPathQueue) + break; + + m_IsPlayingPathQueue = false; + StartPlayingQueue(); + break; case MT_Update: { const CMessageUpdate &msgData = static_cast(msg); - if (!m_Enabled) + if (!m_IsPlayingPathQueue) break; // The paths play at a fixed speed, no matter the sim rate. // The turn length we have received here, however, is scaled by that rate. const fixed realTurnLength{msgData.turnLength / fixed::FromFloat(g_Game ? g_Game->GetSimRate() : 1.0f)}; - m_ElapsedTime += realTurnLength; - m_CurrentPathElapsedTime += realTurnLength; - if (m_CurrentPathElapsedTime >= m_PathQueue.front().GetDuration()) + m_QueuePlayingElapsedTime += realTurnLength; + m_ActivePathElapsedTime += realTurnLength; + if (m_ActivePathElapsedTime >= m_PathQueue.front().GetDuration()) { CMessageCinemaPathEnded msgCinemaPathEnded(m_PathQueue.front().GetName()); m_PathQueue.pop_front(); GetSimContext().GetComponentManager().PostMessage(SYSTEM_ENTITY, msgCinemaPathEnded); - m_CurrentPathElapsedTime = fixed::Zero(); + m_ActivePathElapsedTime = fixed::Zero(); if (!m_PathQueue.empty()) m_PathQueue.front().Reset(); } - if (m_ElapsedTime >= m_TotalTime) - { - m_CurrentPathElapsedTime = fixed::Zero(); - m_ElapsedTime = fixed::Zero(); - m_TotalTime = fixed::Zero(); - SetEnabled(false); - GetSimContext().GetComponentManager().PostMessage(SYSTEM_ENTITY, CMessageCinemaQueueEnded()); - } - + if (m_QueuePlayingElapsedTime >= m_PathQueueDuration) + StopPlayingQueue(); break; } default: @@ -168,57 +166,28 @@ public: { if (m_Paths.find(path.GetName()) != m_Paths.end()) { - LOGWARNING("Path with name '%s' already exists", path.GetName().ToUTF8()); + LOGWARNING("Cinema path with name '%s' already exists", path.GetName().ToUTF8()); return; } m_Paths[path.GetName()] = path; } - void AddCinemaPathToQueue(const CStrW& name) override - { - if (!HasPath(name)) - { - LOGWARNING("Path with name '%s' doesn't exist", name.ToUTF8()); - return; - } - m_PathQueue.push_back(m_Paths[name]); - - if (m_PathQueue.size() == 1) - m_PathQueue.front().Reset(); - m_TotalTime += m_Paths[name].GetDuration(); - } - - void Play() override - { - SetEnabled(true); - } - - void Stop() override - { - SetEnabled(false); - } - - bool HasPath(const CStrW& name) const override - { - return m_Paths.find(name) != m_Paths.end(); - } - - void ClearQueue() override - { - m_PathQueue.clear(); - } - void DeletePath(const CStrW& name) override { if (!HasPath(name)) { - LOGWARNING("Path with name '%s' doesn't exist", name.ToUTF8()); + LOGWARNING("Cinema path with name '%s' doesn't exist", name.ToUTF8()); return; } m_PathQueue.remove_if([name](const CCinemaPath& path) { return path.GetName() == name; }); m_Paths.erase(name); } + bool HasPath(const CStrW& name) const override + { + return m_Paths.find(name) != m_Paths.end(); + } + const std::map& GetPaths() const override { return m_Paths; @@ -229,41 +198,85 @@ public: m_Paths = newPaths; } - const std::list& GetQueue() const override + void PushPathToQueue(const CStrW& name) override { - return m_PathQueue; + if (!HasPath(name)) + { + LOGWARNING("Cinema path with name '%s' doesn't exist", name.ToUTF8()); + return; + } + m_PathQueue.push_back(m_Paths[name]); + + if (m_PathQueue.size() == 1) + m_PathQueue.front().Reset(); + m_PathQueueDuration += m_Paths[name].GetDuration(); } - bool IsEnabled() const override + void ClearQueue() override { - return m_Enabled; + m_PathQueue.clear(); } - void SetEnabled(bool enabled) override + void StartPlayingQueue() override { - if (m_PathQueue.empty() && enabled) - enabled = false; - - if (m_Enabled == enabled) + if (m_IsPlayingPathQueue || m_PathQueue.empty()) return; CmpPtr cmpRangeManager(GetSimContext().GetSystemEntity()); if (cmpRangeManager) { - if (enabled) - m_MapRevealed = cmpRangeManager->GetLosRevealWholeMapForAll(); - // TODO: improve m_MapRevealed state and without fade in - cmpRangeManager->SetLosRevealWholeMapForAll(enabled); + m_WasMapRevealed = cmpRangeManager->GetLosRevealWholeMapForAll(); + // Note: this results in all fogged entities seen during the cinema path being revealed/updated in FOW + // after the queue has ended. + cmpRangeManager->SetLosRevealWholeMapForAll(true); } - m_Enabled = enabled; + m_IsPlayingPathQueue = true; } - void PlayQueue(const float deltaRealTime, CCamera* camera) override + void StopPlayingQueue() override { - if (m_PathQueue.empty()) + if (!m_IsPlayingPathQueue) return; - m_PathQueue.front().Play(deltaRealTime, camera); + + CmpPtr cmpRangeManager(GetSimContext().GetSystemEntity()); + if (cmpRangeManager) + cmpRangeManager->SetLosRevealWholeMapForAll(m_WasMapRevealed); + + GetSimContext().GetComponentManager().PostMessage(SYSTEM_ENTITY, CMessageCinemaQueueEnded()); + + m_ActivePathElapsedTime = fixed::Zero(); + m_QueuePlayingElapsedTime = fixed::Zero(); + m_PathQueueDuration = fixed::Zero(); + for (const CCinemaPath& path : m_PathQueue) + m_PathQueueDuration += path.GetDuration(); + m_IsPlayingPathQueue = false; + } + + bool IsPlayingQueue() const override + { + return m_IsPlayingPathQueue; + } + + void UpdateActivePath(const float deltaRealTime, CCamera* camera) override + { + if (m_IsPlayingPathQueue) + { + if (m_PathQueue.empty()) + StopPlayingQueue(); + else + m_PathQueue.front().Play(deltaRealTime, camera); + } + } + + const CStrW GetActivePath() const override + { + return m_IsPlayingPathQueue ? m_PathQueue.front().GetName() : CStrW(); + } + + const fixed GetActivePathElapsedTime() const override + { + return m_ActivePathElapsedTime; } private: @@ -364,16 +377,22 @@ private: return CCinemaPath(data, pathSpline, targetSpline); } - bool m_Enabled; + bool m_IsPlayingPathQueue; std::map m_Paths; std::list m_PathQueue; + fixed m_PathQueueDuration; - // States before playing - bool m_MapRevealed; + // Total time elapsed since starting to play the queue. + fixed m_QueuePlayingElapsedTime; - fixed m_ElapsedTime; - fixed m_TotalTime; - fixed m_CurrentPathElapsedTime; + // Time elapsed since the currently active path first started playing. + fixed m_ActivePathElapsedTime; + + // Time elapsed since the + fixed m_QueueEndedElapsedTime; + + // Whether the map was revealed before playing the queue. + bool m_WasMapRevealed; }; REGISTER_COMPONENT_TYPE(CinemaManager) diff --git a/source/simulation2/components/ICmpCinemaManager.cpp b/source/simulation2/components/ICmpCinemaManager.cpp index f856730ad3..94f06a53a9 100644 --- a/source/simulation2/components/ICmpCinemaManager.cpp +++ b/source/simulation2/components/ICmpCinemaManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -24,9 +24,13 @@ BEGIN_INTERFACE_WRAPPER(CinemaManager) DEFINE_INTERFACE_METHOD("AddPath", ICmpCinemaManager, AddPath) -DEFINE_INTERFACE_METHOD("AddCinemaPathToQueue", ICmpCinemaManager, AddCinemaPathToQueue) +DEFINE_INTERFACE_METHOD("HasPath", ICmpCinemaManager, HasPath) DEFINE_INTERFACE_METHOD("DeletePath", ICmpCinemaManager, DeletePath) -DEFINE_INTERFACE_METHOD("IsPlaying", ICmpCinemaManager, IsEnabled) -DEFINE_INTERFACE_METHOD("Play", ICmpCinemaManager, Play) -DEFINE_INTERFACE_METHOD("Stop", ICmpCinemaManager, Stop) +DEFINE_INTERFACE_METHOD("PushPathToQueue", ICmpCinemaManager, PushPathToQueue) +DEFINE_INTERFACE_METHOD("ClearQueue", ICmpCinemaManager, ClearQueue) +DEFINE_INTERFACE_METHOD("StartPlayingQueue", ICmpCinemaManager, StartPlayingQueue) +DEFINE_INTERFACE_METHOD("IsPlayingQueue", ICmpCinemaManager, IsPlayingQueue) +DEFINE_INTERFACE_METHOD("GetActivePath", ICmpCinemaManager, GetActivePath) +DEFINE_INTERFACE_METHOD("GetActivePathElapsedTime", ICmpCinemaManager, GetActivePathElapsedTime) +DEFINE_INTERFACE_METHOD("StopPlayingQueue", ICmpCinemaManager, StopPlayingQueue) END_INTERFACE_WRAPPER(CinemaManager) diff --git a/source/simulation2/components/ICmpCinemaManager.h b/source/simulation2/components/ICmpCinemaManager.h index 48e1b3d035..39937c5e36 100644 --- a/source/simulation2/components/ICmpCinemaManager.h +++ b/source/simulation2/components/ICmpCinemaManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -31,54 +31,83 @@ class CCinemaPath; class CStrW; /** - * Component for CCinemaManager class - * TODO: write description + * Manages a dynamic list of cinema paths (predefined camera movements/cutscenes) as well as a queue of paths that + * is played one by one on command to all players. Simulation messages are sent each time a path ends and also when + * the queue finishes as a whole. */ - class ICmpCinemaManager : public IComponent { public: /** - * Adds the path to the path list - * @param CCinemaPath path data + * Register a new path. + * @param path path data */ virtual void AddPath(const CCinemaPath& path) = 0; /** - * Adds the path to the playlist - * @param name path name - */ - virtual void AddCinemaPathToQueue(const CStrW& name) = 0; - - virtual void Play() = 0; - virtual void Stop() = 0; - virtual void PlayQueue(const float deltaRealTime, CCamera* camera) = 0; - - /** - * Checks the path name in the path list - * @param name path name - * @return true if path with that name exists, else false - */ - virtual bool HasPath(const CStrW& name) const = 0; - + * Remove a path and its data entirely. + * @param name path name + */ virtual void DeletePath(const CStrW& name) = 0; /** - * Clears the playlist + * Check whether a path exists (is registered under the given name).. + * @param name path name + */ + virtual bool HasPath(const CStrW& name) const = 0; + + /** + * Get all registered paths, keyed by their names. + */ + virtual const std::map& GetPaths() const = 0; + + /** + * Override the entire list of existing paths. + * @param newPaths new list of paths + */ + virtual void SetPaths(const std::map& newPaths) = 0; + + /** + * Push a path to the back of the queue. + * @param name path name + */ + virtual void PushPathToQueue(const CStrW& name) = 0; + + /** + * Clear all paths from the queue. */ virtual void ClearQueue() = 0; - virtual const std::map& GetPaths() const = 0; - virtual void SetPaths(const std::map& newPaths) = 0; - virtual const std::list& GetQueue() const = 0; - - virtual bool IsEnabled() const = 0; + /** + * Start playing the paths in the queue one by one. + */ + virtual void StartPlayingQueue() = 0; /** - * Sets enable state of the cinema manager (shows/hide gui, show/hide rings, etc) - * @param enable new state - */ - virtual void SetEnabled(bool enabled) = 0; + * Stop playing the active path and the queue altogether. + */ + virtual void StopPlayingQueue() = 0; + + /** + * Whether the first path of the queue is being played at the moment. + */ + virtual bool IsPlayingQueue() const = 0; + + /** + * Send an update to the path currently playing for it to determine the new camera position. + * Called every frame. + */ + virtual void UpdateActivePath(const float deltaRealTime, CCamera* camera) = 0; + + /** + * Get the name of the path currently playing, if any. + */ + virtual const CStrW GetActivePath() const = 0; + + /** + * Get the time elapsed since the currently active path started playing. + */ + virtual const fixed GetActivePathElapsedTime() const = 0; DECLARE_INTERFACE_TYPE(CinemaManager) }; diff --git a/source/simulation2/components/tests/test_CinemaManager.h b/source/simulation2/components/tests/test_CinemaManager.h index b2f7316166..8406fc599c 100644 --- a/source/simulation2/components/tests/test_CinemaManager.h +++ b/source/simulation2/components/tests/test_CinemaManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -37,33 +37,91 @@ class TestCmpCinemaManager : public CxxTest::TestSuite { public: - void test_basic() + void test_managing_paths() { CXeromycesEngine xeromycesEngine; ComponentTestHelper test(*g_ScriptContext); ICmpCinemaManager* cmp = test.Add(CID_CinemaManager, "", SYSTEM_ENTITY); - TS_ASSERT_EQUALS(cmp->HasPath(L"test"), false); + TS_ASSERT(!cmp->HasPath(L"test")); cmp->AddPath(generatePath(L"test")); - TS_ASSERT_EQUALS(cmp->HasPath(L"test"), true); - cmp->DeletePath(L"test"); - TS_ASSERT_EQUALS(cmp->HasPath(L"test"), false); + TS_ASSERT(cmp->HasPath(L"test")); + TS_ASSERT(!cmp->HasPath(L"test_2")); + cmp->SetPaths(std::map{{L"test_2", generatePath(L"test_2")}}); + TS_ASSERT(!cmp->HasPath(L"test")); + TS_ASSERT(cmp->HasPath(L"test_2")); + cmp->DeletePath(L"test_2"); + TS_ASSERT(!cmp->HasPath(L"test_2")); + } - cmp->AddPath(generatePath(L"long_path", fixed::FromInt(3600))); - TS_ASSERT_EQUALS(cmp->HasPath(L"long_path"), true); + void test_playing_queue() + { + CXeromycesEngine xeromycesEngine; + ComponentTestHelper test(*g_ScriptContext); - TS_ASSERT_EQUALS(cmp->IsEnabled(), false); - cmp->AddCinemaPathToQueue(L"long_path"); - cmp->Play(); - size_t number_of_turns = 0; - while (cmp->IsEnabled()) + ICmpCinemaManager* cmp = test.Add(CID_CinemaManager, "", SYSTEM_ENTITY); + + CMessageUpdate updateMsg(fixed::FromInt(200)); + + cmp->AddPath(generatePath(L"path_1", fixed::FromInt(10000))); + cmp->AddPath(generatePath(L"path_2", fixed::FromInt(5000))); + + // Try getting the active path if there is none. + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L""); + + // Try to start playing the queue if it's empty. + cmp->StartPlayingQueue(); + TS_ASSERT(!cmp->IsPlayingQueue()); + + // Try stopping playing the queue if it's not playing in the first place. + cmp->StartPlayingQueue(); + TS_ASSERT(!cmp->IsPlayingQueue()); + + cmp->PushPathToQueue(L"path_1"); + cmp->PushPathToQueue(L"path_2"); + // Try getting the active path if there is none. + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L""); + + cmp->StartPlayingQueue(); + TS_ASSERT(cmp->IsPlayingQueue()); + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L"path_1"); + TS_ASSERT_EQUALS(cmp->GetActivePathElapsedTime(), fixed::FromInt(0)); + + for (int i = 0; i < 35; i++) + cmp->HandleMessage(updateMsg, true); + TS_ASSERT(cmp->IsPlayingQueue()); + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L"path_1"); + TS_ASSERT_EQUALS(cmp->GetActivePathElapsedTime(), fixed::FromInt(7000)); + + // Finish path_1 and start with path_2 + for (int i = 0; i < 20; i++) + cmp->HandleMessage(updateMsg, true); + + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L"path_2"); + TS_ASSERT_EQUALS(cmp->GetActivePathElapsedTime(), fixed::FromInt(1000)); + + // Try restarting while a path is being played. + // This should result in the active path starting again from the beginning. + cmp->StopPlayingQueue(); + cmp->StartPlayingQueue(); + TS_ASSERT(cmp->IsPlayingQueue()); + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L"path_2"); + + size_t remainingTurns = 0; + while (cmp->IsPlayingQueue()) { - CMessageUpdate msg(fixed::FromInt(36)); - cmp->HandleMessage(msg, true); - ++number_of_turns; + cmp->HandleMessage(updateMsg, true); + remainingTurns++; } - TS_ASSERT_EQUALS(number_of_turns, 100); + TS_ASSERT_EQUALS(remainingTurns, 25); + TS_ASSERT(!cmp->IsPlayingQueue()); + TS_ASSERT_WSTR_EQUALS(cmp->GetActivePath(), L""); + TS_ASSERT_EQUALS(cmp->GetActivePathElapsedTime(), fixed::FromInt(0)); + + // Make sure the queue is empty. + cmp->StartPlayingQueue(); + TS_ASSERT(!cmp->IsPlayingQueue()); } private: diff --git a/source/tools/atlas/GameInterface/Handlers/CameraCtrlHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/CameraCtrlHandlers.cpp index 4da50115da..4028483a00 100644 --- a/source/tools/atlas/GameInterface/Handlers/CameraCtrlHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/CameraCtrlHandlers.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -41,7 +41,7 @@ namespace AtlasMessage { MESSAGEHANDLER(CameraReset) { - if (!g_Game || g_Game->GetView()->GetCinema()->IsEnabled()) + if (!g_Game || g_Game->GetView()->GetCinema()->IsPlaying()) return; CVector3D focus = g_Game->GetView()->GetCamera()->GetFocus(); @@ -64,7 +64,7 @@ MESSAGEHANDLER(CameraReset) MESSAGEHANDLER(ScrollConstant) { - if (!g_Game || g_Game->GetView()->GetCinema()->IsEnabled()) + if (!g_Game || g_Game->GetView()->GetCinema()->IsPlaying()) return; if (msg->dir < 0 || msg->dir > 5) @@ -82,7 +82,7 @@ MESSAGEHANDLER(ScrollConstant) MESSAGEHANDLER(Scroll) { - if (!g_Game || g_Game->GetView()->GetCinema()->IsEnabled()) // TODO: do this better (probably a separate AtlasView class for cinematics) + if (!g_Game || g_Game->GetView()->GetCinema()->IsPlaying()) // TODO: do this better (probably a separate AtlasView class for cinematics) return; static CVector3D targetPos; @@ -131,7 +131,7 @@ MESSAGEHANDLER(Scroll) MESSAGEHANDLER(SmoothZoom) { - if (!g_Game || g_Game->GetView()->GetCinema()->IsEnabled()) + if (!g_Game || g_Game->GetView()->GetCinema()->IsPlaying()) return; g_AtlasGameLoop->input.zoomDelta += msg->amount; @@ -139,7 +139,7 @@ MESSAGEHANDLER(SmoothZoom) MESSAGEHANDLER(RotateAround) { - if (!g_Game || g_Game->GetView()->GetCinema()->IsEnabled()) + if (!g_Game || g_Game->GetView()->GetCinema()->IsPlaying()) return; static CVector3D focusPos; @@ -245,7 +245,7 @@ QUERYHANDLER(GetView) MESSAGEHANDLER(SetView) { - if (!g_Game || g_Game->GetView()->GetCinema()->IsEnabled()) + if (!g_Game || g_Game->GetView()->GetCinema()->IsPlaying()) return; CGameView* view = g_Game->GetView(); diff --git a/source/tools/atlas/GameInterface/Handlers/CinemaHandler.cpp b/source/tools/atlas/GameInterface/Handlers/CinemaHandler.cpp index 50a7d9ac25..b5a03689f5 100644 --- a/source/tools/atlas/GameInterface/Handlers/CinemaHandler.cpp +++ b/source/tools/atlas/GameInterface/Handlers/CinemaHandler.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -189,7 +189,7 @@ MESSAGEHANDLER(CinemaEvent) if (msg->mode == eCinemaEventMode::SMOOTH) { - cmpCinemaManager->AddCinemaPathToQueue(*msg->path); + cmpCinemaManager->PushPathToQueue(*msg->path); } else if ( msg->mode == eCinemaEventMode::RESET ) {