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 @@ performCommand(g_Selection.toList()[0], "delete"); + + + + setCameraFollow(g_Selection.toList()[0]); + 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