Revamp CinemaManager component

Clean up the the implementation, improve the naming, and
add some more documentation as well as more in-depth tests.
This commit is contained in:
Vantha 2026-02-24 10:31:19 +01:00 committed by Vantha
parent 1d3cdec48d
commit d882ab74a1
11 changed files with 285 additions and 183 deletions

View file

@ -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", {});

View file

@ -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();

View file

@ -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<ICmpCinemaManager> cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity());
return cmpCinemaManager && cmpCinemaManager->IsEnabled();
}
bool CCinemaManager::IsPlaying() const
{
return IsEnabled() && g_Game && !g_Game->m_Paused;
CmpPtr<ICmpCinemaManager> cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity());
return cmpCinemaManager && cmpCinemaManager->IsPlayingQueue();
}
bool CCinemaManager::GetPathsDrawing() const

View file

@ -39,7 +39,6 @@ public:
void Render(Renderer::Backend::IDeviceCommandContext& deviceCommandContext) const;
bool IsPlaying() const;
bool IsEnabled() const;
/**
* Updates CCinemManager and current path

View file

@ -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();

View file

@ -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<const CStrW, CCinemaPath>& 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<const CMessageUpdate&>(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<CStrW, CCinemaPath>& GetPaths() const override
{
return m_Paths;
@ -229,41 +198,85 @@ public:
m_Paths = newPaths;
}
const std::list<CCinemaPath>& 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<ICmpRangeManager> 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<ICmpRangeManager> 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<CStrW, CCinemaPath> m_Paths;
std::list<CCinemaPath> 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)

View file

@ -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)

View file

@ -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<CStrW, CCinemaPath>& GetPaths() const = 0;
/**
* Override the entire list of existing paths.
* @param newPaths new list of paths
*/
virtual void SetPaths(const std::map<CStrW, CCinemaPath>& 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<CStrW, CCinemaPath>& GetPaths() const = 0;
virtual void SetPaths(const std::map<CStrW, CCinemaPath>& newPaths) = 0;
virtual const std::list<CCinemaPath>& 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)
};

View file

@ -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<ICmpCinemaManager>(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<CStrW, CCinemaPath>{{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<ICmpCinemaManager>(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:

View file

@ -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();

View file

@ -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 )
{