diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg
index a8c9bafb84..dea94d44ac 100644
--- a/binaries/data/config/default.cfg
+++ b/binaries/data/config/default.cfg
@@ -113,6 +113,7 @@ hotkey.archive.abort = "Alt+F4" ; Prematurely terminate the archive builder
; > CAMERA SETTINGS
hotkey.camera.reset = "H" ; Reset camera rotation to default.
+hotkey.camera.follow = "F" ; Follow the first unit in the selection
hotkey.camera.reset.origin = "Ctrl+H" ; Reset camera to origin.
hotkey.camera.zoom.in = Plus, Equals, NumPlus ; Zoom camera in.
hotkey.camera.zoom.out = Minus, NumMinus ; Zoom camera out.
diff --git a/binaries/data/mods/public/gui/session_new/input.js b/binaries/data/mods/public/gui/session_new/input.js
index 0ff4db1190..188c0f4816 100644
--- a/binaries/data/mods/public/gui/session_new/input.js
+++ b/binaries/data/mods/public/gui/session_new/input.js
@@ -759,3 +759,22 @@ function performCommand(entity, commandName)
}
}
}
+
+// Set the camera to follow the given unit
+function setCameraFollow(entity)
+{
+ // Follow the given entity if it's a unit
+ if (entity)
+ {
+ var entState = GetEntityState(entity);
+ if (entState && isUnit(entState))
+ {
+ Engine.CameraFollow(entity);
+ return;
+ }
+ }
+
+ // Otherwise stop following
+ Engine.CameraFollow(0);
+}
+
diff --git a/binaries/data/mods/public/gui/session_new/session.xml b/binaries/data/mods/public/gui/session_new/session.xml
index 7628892b2d..d649e102be 100644
--- a/binaries/data/mods/public/gui/session_new/session.xml
+++ b/binaries/data/mods/public/gui/session_new/session.xml
@@ -62,6 +62,11 @@
+
+
+
diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp
index 02a2a87144..80926a7aa7 100644
--- a/source/graphics/GameView.cpp
+++ b/source/graphics/GameView.cpp
@@ -52,6 +52,7 @@
#include "scripting/ScriptableObject.h"
#include "simulation/LOSManager.h"
#include "simulation2/Simulation2.h"
+#include "simulation2/components/ICmpPosition.h"
extern int g_xres, g_yres;
@@ -157,6 +158,7 @@ public:
LockCullCamera(false),
ConstrainCamera(true),
Culling(true),
+ FollowEntity(INVALID_ENTITY),
// Dummy values (these will be filled in by the config file)
ViewScrollSpeed(0),
@@ -235,6 +237,11 @@ public:
CCinemaManager TrackManager;
+ /**
+ * Entity for the camera to follow, or INVALID_ENTITY if none.
+ */
+ entity_id_t FollowEntity;
+
////////////////////////////////////////
// Settings
float ViewScrollSpeed;
@@ -574,7 +581,7 @@ void CGameView::Update(float DeltaTime)
// TODO: this is probably not an ideal place for this, it should probably go
// in a CCmpWaterManager or some such thing (once such a thing exists)
- if (!g_Game->m_Paused)
+ if (!m->Game->m_Paused)
g_Renderer.GetWaterManager()->m_WaterTexTimer += DeltaTime;
if (m->TrackManager.IsActive() && m->TrackManager.IsPlaying())
@@ -639,6 +646,9 @@ void CGameView::Update(float DeltaTime)
if (moveRightward || moveForward)
{
+ // Break out of following mode when the user starts scrolling
+ m->FollowEntity = INVALID_ENTITY;
+
float s = sin(m->RotateY.GetSmoothedValue());
float c = cos(m->RotateY.GetSmoothedValue());
m->PosX.AddSmoothly(c * moveRightward);
@@ -647,6 +657,37 @@ void CGameView::Update(float DeltaTime)
m->PosZ.AddSmoothly(c * moveForward);
}
+ if (m->FollowEntity)
+ {
+ CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity);
+ if (!cmpPosition.null() && cmpPosition->IsInWorld())
+ {
+ // Get the most recent interpolated position
+ float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset();
+ CVector3D pos = cmpPosition->GetInterpolatedTransform(frameOffset, false).GetTranslation();
+
+ // move the camera after unit
+ // use smoothed values of rotation around X and Y, since we need to hold user's rotation done
+ // in this function above
+ CCamera targetCam = m->ViewCamera;
+ targetCam.m_Orientation.SetIdentity();
+ targetCam.m_Orientation.RotateX(m->RotateX.GetSmoothedValue());
+ targetCam.m_Orientation.RotateY(m->RotateY.GetSmoothedValue());
+ targetCam.m_Orientation.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
+
+ CVector3D pivot = targetCam.GetFocus();
+ CVector3D delta = pos - pivot;
+ m->PosX.AddSmoothly(delta.X);
+ m->PosY.AddSmoothly(delta.Y);
+ m->PosZ.AddSmoothly(delta.Z);
+ }
+ else
+ {
+ // The unit disappeared (died or garrisoned etc), so stop following it
+ m->FollowEntity = INVALID_ENTITY;
+ }
+ }
+
if (hotkeys[HOTKEY_CAMERA_ZOOM_IN])
m->Zoom.AddSmoothly(m->ViewZoomSpeed * DeltaTime);
if (hotkeys[HOTKEY_CAMERA_ZOOM_OUT])
@@ -785,6 +826,9 @@ void CGameView::MoveCameraTarget(const CVector3D& target)
m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());
ClampDistance(m, false);
+
+ // Break out of following mode so the camera really moves to the target
+ m->FollowEntity = INVALID_ENTITY;
}
void CGameView::ResetCameraTarget(const CVector3D& target)
@@ -803,6 +847,9 @@ void CGameView::ResetCameraTarget(const CVector3D& target)
SetupCameraMatrix(m, &m->ViewCamera.m_Orientation);
m->ViewCamera.UpdateFrustum();
+
+ // Break out of following mode so the camera really moves to the target
+ m->FollowEntity = INVALID_ENTITY;
}
void CGameView::ResetCameraAngleZoom()
@@ -824,6 +871,11 @@ void CGameView::ResetCameraAngleZoom()
m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault));
}
+void CGameView::CameraFollow(entity_id_t entity)
+{
+ m->FollowEntity = entity;
+}
+
InReaction game_view_handler(const SDL_Event_* ev)
{
// put any events that must be processed even if inactive here
diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h
index 6adb542dc6..c97508f0ba 100644
--- a/source/graphics/GameView.h
+++ b/source/graphics/GameView.h
@@ -23,6 +23,7 @@ extern float g_MaxZoomHeight; //note: Max terrain height is this minus YMinOffs
extern float g_YMinOffset;
#include "renderer/Scene.h"
+#include "simulation2/system/Entity.h"
#include "lib/input.h" // InReaction - can't forward-declare enum
@@ -85,6 +86,7 @@ public:
void MoveCameraTarget(const CVector3D& target);
void ResetCameraTarget(const CVector3D& target);
void ResetCameraAngleZoom();
+ void CameraFollow(entity_id_t entity);
CCamera *GetCamera();
CCinemaManager* GetCinema();
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index 670cdd988d..3c43d73aa4 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -73,11 +73,6 @@ void PopGuiPage(void* UNUSED(cbdata))
g_GUI->PopPage();
}
-bool IsNewSimulation(void* UNUSED(cbdata))
-{
- return true; // XXX: delete this function
-}
-
CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data)
{
CGUIManager* guiManager = static_cast (cbdata);
@@ -317,6 +312,16 @@ void SetRevealMap(void* UNUSED(cbdata), bool enabled)
cmpRangeManager->SetLosRevealAll(enabled);
}
+/**
+ * Start / stop camera following mode
+ * @param entityid unit id to follow. If zero, stop following mode
+ */
+void CameraFollow(void* UNUSED(cbdata), entity_id_t entityid)
+{
+ if (g_Game && g_Game->GetView())
+ g_Game->GetView()->CameraFollow(entityid);
+}
+
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
@@ -328,7 +333,6 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction("PopGuiPage");
// Simulation<->GUI interface functions:
- scriptInterface.RegisterFunction("IsNewSimulation");
scriptInterface.RegisterFunction("GuiInterfaceCall");
scriptInterface.RegisterFunction("PostNetworkCommand");
@@ -357,4 +361,5 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("LoadMapData");
scriptInterface.RegisterFunction("SetRevealMap");
+ scriptInterface.RegisterFunction("CameraFollow");
}
diff --git a/source/ps/Hotkey.cpp b/source/ps/Hotkey.cpp
index 0501c64489..7eb15d9eab 100644
--- a/source/ps/Hotkey.cpp
+++ b/source/ps/Hotkey.cpp
@@ -74,6 +74,7 @@ static SHotkeyInfo hotkeyInfo[] =
{ HOTKEY_WIREFRAME, "wireframe", SDLK_w, 0 },
{ HOTKEY_TOGGLEFULLSCREEN, "togglefullscreen", 0, 0 },
{ HOTKEY_CAMERA_RESET, "camera.reset", 0, 0 },
+ { HOTKEY_CAMERA_FOLLOW, "camera.follow", 0, 0 },
{ HOTKEY_CAMERA_ZOOM_IN, "camera.zoom.in", SDLK_PLUS, SDLK_KP_PLUS },
{ HOTKEY_CAMERA_ZOOM_OUT, "camera.zoom.out", SDLK_MINUS, SDLK_KP_MINUS },
{ HOTKEY_CAMERA_ZOOM_WHEEL_IN, "camera.zoom.wheel.in", MOUSE_WHEELUP, 0 },
diff --git a/source/ps/Hotkey.h b/source/ps/Hotkey.h
index 89ea4483f2..8e68ce9e0b 100644
--- a/source/ps/Hotkey.h
+++ b/source/ps/Hotkey.h
@@ -55,6 +55,7 @@ enum
HOTKEY_WIREFRAME,
HOTKEY_TOGGLEFULLSCREEN,
HOTKEY_CAMERA_RESET,
+ HOTKEY_CAMERA_FOLLOW,
HOTKEY_CAMERA_ZOOM_IN,
HOTKEY_CAMERA_ZOOM_OUT,
HOTKEY_CAMERA_ZOOM_WHEEL_IN,
diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp
index 3f4604c150..e1a040082b 100644
--- a/source/simulation2/Simulation2.cpp
+++ b/source/simulation2/Simulation2.cpp
@@ -83,6 +83,7 @@ public:
m_ComponentManager.ResetState();
m_DeltaTime = 0.0;
+ m_LastFrameOffset = 0.0f;
m_TurnNumber = 0;
CParamNode noParam;
@@ -133,6 +134,7 @@ public:
CSimContext m_SimContext;
CComponentManager m_ComponentManager;
double m_DeltaTime;
+ float m_LastFrameOffset;
std::wstring m_StartupScript;
CScriptValRooted m_MapSettings;
@@ -245,6 +247,8 @@ bool CSimulation2Impl::Update(int turnLength, const std::vectorm_ComponentManager.BroadcastMessage(msg);
}
+float CSimulation2::GetLastFrameOffset() const
+{
+ return m->m_LastFrameOffset;
+}
+
bool CSimulation2::LoadScripts(const VfsPath& path)
{
return m->LoadScripts(path);
diff --git a/source/simulation2/Simulation2.h b/source/simulation2/Simulation2.h
index e83edca059..739316d8eb 100644
--- a/source/simulation2/Simulation2.h
+++ b/source/simulation2/Simulation2.h
@@ -114,6 +114,12 @@ public:
void Interpolate(float frameLength, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
+ /**
+ * Returns the last frame offset passed to Interpolate(), i.e. the offset corresponding
+ * to the currently-rendered scene.
+ */
+ float GetLastFrameOffset() const;
+
/**
* Construct a new entity and add it to the world.
* @param templateName see ICmpTemplateManager for syntax