From 6e32cfa370814190e81ce5eb352e0980d91f0d62 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 28 Feb 2026 12:06:39 +0100 Subject: [PATCH 1/4] fix(view): Implement state for user controlled camera to properly distinguish between scripted and user camera --- Core/GameEngine/Include/GameClient/View.h | 29 ++- Core/GameEngine/Source/GameClient/View.cpp | 180 +++++++++++++++++- .../Include/W3DDevice/GameClient/W3DView.h | 3 +- .../Source/W3DDevice/GameClient/W3DView.cpp | 48 +++-- Core/Libraries/Include/Lib/BaseType.h | 1 + .../GUI/GUICallbacks/ControlBarCallback.cpp | 2 +- .../Source/GameClient/GameClient.cpp | 3 +- .../GameEngine/Source/GameClient/InGameUI.cpp | 14 +- .../GameClient/MessageStream/CommandXlat.cpp | 28 +-- .../GameClient/MessageStream/LookAtXlat.cpp | 22 ++- .../MessageStream/SelectionXlat.cpp | 6 +- .../GameLogic/System/GameLogicDispatch.cpp | 17 +- 12 files changed, 285 insertions(+), 68 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/View.h b/Core/GameEngine/Include/GameClient/View.h index 53502580bb3..bf40115073c 100644 --- a/Core/GameEngine/Include/GameClient/View.h +++ b/Core/GameEngine/Include/GameClient/View.h @@ -105,7 +105,7 @@ class View : public Snapshot virtual UnsignedInt getID() { return m_id; } virtual void setZoomLimited( Bool limit ) { m_zoomLimited = limit; } ///< limit the zoom height - virtual Bool isZoomLimited() { return m_zoomLimited; } ///< get status of zoom limit + virtual Bool isZoomLimited() const { return m_zoomLimited; } ///< get status of zoom limit /// pick drawable given the screen pixel coords. If force attack, picks bridges as well. virtual Drawable *pickDrawable( const ICoord2D *screen, Bool forceAttack, PickType pickType ) = 0; @@ -128,12 +128,11 @@ class View : public Snapshot virtual void setOrigin( Int x, Int y) { m_originX=x; m_originY=y;} ///< Sets location of top-left view corner on display virtual void getOrigin( Int *x, Int *y) { *x=m_originX; *y=m_originY;} ///< Return location of top-left view corner on display - virtual void lockViewUntilFrame(UnsignedInt frame); ///< Locks the current view until the given frame is reached. virtual void forceRedraw() = 0; virtual void lookAt( const Coord3D *o ); ///< Center the view on the given coordinate virtual void initHeightForMap() {}; ///< Init the camera height for the map at the current position. - virtual void scrollBy( Coord2D *delta ); ///< Shift the view by the given delta + virtual void scrollBy( const Coord2D *delta ); ///< Shift the view by the given delta virtual void moveCameraTo(const Coord3D *o, Int frames, Int shutter, Bool orient, Real easeIn=0.0f, Real easeOut=0.0f) { lookAt( o ); } virtual void moveCameraAlongWaypointPath(Waypoint *way, Int frames, Int shutter, Bool orient, Real easeIn=0.0f, Real easeOut=0.0f) { } @@ -194,6 +193,25 @@ class View : public Snapshot virtual void setZoomToDefault() { m_zoom = 1.0f; } ///< Set zoom to default value virtual void setOkToAdjustHeight( Bool val ) { m_okToAdjustHeight = val; } ///< Set this to adjust camera height + // TheSuperHackers @info Functions to call for user camera controls, not by the scripted camera. + Bool userSetPosition(const Coord3D *pos); + Bool userSetAngle(Real radians); + Bool userSetAngleToDefault(); + Bool userSetPitch(Real radians); + Bool userSetPitchToDefault(); + Bool userZoom(Real height); + Bool userSetZoom(Real z); + Bool userSetZoomToDefault(); + Bool userSetFieldOfView(Real angle); + Bool userLookAt(const Coord3D *o); + Bool userScrollBy(const Coord2D *delta); + Bool userSetLocation(const ViewLocation *location); + Bool userSetCameraLock(ObjectID id); + Bool userSetCameraLockDrawable(Drawable *drawable); + + void lockUserControlUntilFrame(UnsignedInt frame); ///< Locks the user control over camera until the given frame is reached. + Bool isUserControlLocked() const; + // for debugging virtual Real getTerrainHeightAtPivot() { return m_terrainHeightAtPivot; } virtual Real getCurrentHeightAboveGround() { return m_currentHeightAboveGround; } @@ -246,6 +264,8 @@ class View : public Snapshot virtual View *prependViewToList( View *list ); ///< Prepend this view to the given list, return the new list virtual View *getNextView() { return m_next; } ///< Return next view in the set + virtual void setUserControlled(Bool value) { m_isUserControlled = value; } + protected: View *m_next; ///< List links used by the Display class @@ -253,7 +273,8 @@ class View : public Snapshot UnsignedInt m_id; ///< The ID of this view static UnsignedInt m_idNext; ///< Used for allocating view ID's for all views - UnsignedInt m_viewLockedUntilFrame; + UnsignedInt m_userControlLockedUntilFrame; ///< Locks the user control over camera until the given frame is reached + Bool m_isUserControlled; ///< True if the user moved the camera last, false if the scripted camera moved the camera last Coord3D m_pos; ///< Pivot of the camera, in world coordinates // TheSuperHackers @todo Make this Coord2D or use the Z component Int m_width, m_height; ///< Dimensions of the view diff --git a/Core/GameEngine/Source/GameClient/View.cpp b/Core/GameEngine/Source/GameClient/View.cpp index 377cef65f31..f6109db4054 100644 --- a/Core/GameEngine/Source/GameClient/View.cpp +++ b/Core/GameEngine/Source/GameClient/View.cpp @@ -30,8 +30,9 @@ #include "Common/GameEngine.h" #include "Common/Xfer.h" -#include "GameClient/View.h" #include "GameClient/Drawable.h" +#include "GameClient/GameClient.h" +#include "GameClient/View.h" UnsignedInt View::m_idNext = 1; @@ -41,7 +42,8 @@ View *TheTacticalView = nullptr; View::View() { - m_viewLockedUntilFrame = 0u; + m_userControlLockedUntilFrame = 0u; + m_isUserControlled = true; m_currentHeightAboveGround = 0.0f; m_defaultAngle = 0.0f; m_defaultPitch = 0.0f; @@ -109,7 +111,8 @@ void View::reset() // Only fixing the reported bug. Who knows what side effects resetting the rest could have. m_zoomLimited = TRUE; - m_viewLockedUntilFrame = 0u; + m_userControlLockedUntilFrame = 0u; + m_isUserControlled = true; } /** @@ -126,11 +129,6 @@ void View::zoom( Real height ) setHeightAboveGround(getHeightAboveGround() + height); } -void View::lockViewUntilFrame(UnsignedInt frame) -{ - m_viewLockedUntilFrame = frame; -} - /** * Center the view on the given coordinate. */ @@ -147,7 +145,7 @@ void View::lookAt( const Coord3D *o ) /** * Shift the view by the given delta. */ -void View::scrollBy( Coord2D *delta ) +void View::scrollBy( const Coord2D *delta ) { // update view's world position m_pos.x += delta->x; @@ -228,6 +226,170 @@ void View::setLocation( const ViewLocation *location ) } +Bool View::userSetPosition(const Coord3D *pos) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setPosition(pos); + return true; +} + +Bool View::userSetAngle(Real radians) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setAngle(radians); + return true; +} + +Bool View::userSetAngleToDefault() +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setAngleToDefault(); + return true; +} + +Bool View::userSetPitch(Real radians) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setPitch(radians); + return true; +} + +Bool View::userSetPitchToDefault() +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setPitchToDefault(); + return true; +} + +Bool View::userZoom(Real height) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + zoom(height); + return true; +} + +Bool View::userSetZoom(Real z) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setZoom(z); + return true; +} + +Bool View::userSetZoomToDefault() +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setZoomToDefault(); + return true; +} + +Bool View::userSetFieldOfView(Real angle) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setFieldOfView(angle); + return true; +} + +Bool View::userLookAt(const Coord3D *o) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + lookAt(o); + return true; +} + +Bool View::userScrollBy(const Coord2D *delta) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + scrollBy(delta); + return true; +} + +Bool View::userSetLocation(const ViewLocation *location) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setLocation(location); + return true; +} + +Bool View::userSetCameraLock(ObjectID id) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setCameraLock(id); + return true; +} + +Bool View::userSetCameraLockDrawable(Drawable *drawable) +{ + if (isUserControlLocked()) + return false; + + stopDoingScriptedCamera(); + setUserControlled(true); + setCameraLockDrawable(drawable); + return true; +} + +void View::lockUserControlUntilFrame(UnsignedInt frame) +{ + m_userControlLockedUntilFrame = frame; +} + +Bool View::isUserControlLocked() const +{ + return m_userControlLockedUntilFrame > TheGameClient->getFrame(); +} + //------------------------------------------------------------------------------------------------- /** project the 4 corners of this view into the world and return each point as a parameter, the world points are at the requested Z */ diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index c5e2e221be7..f2b20893fb9 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -162,7 +162,7 @@ class W3DView : public View, public SubsystemInterface virtual void setHeight( Int height ); virtual void setOrigin( Int x, Int y); ///< Sets location of top-left view corner on display - virtual void scrollBy( Coord2D *delta ); ///< Shift the view by the given delta + virtual void scrollBy( const Coord2D *delta ); ///< Shift the view by the given delta virtual void forceRedraw(); @@ -289,6 +289,7 @@ class W3DView : public View, public SubsystemInterface void buildCameraTransform(Matrix3D *transform); ///< calculate (but do not set) the transform matrix of m_3DCamera, based on m_pos & m_angle void calcCameraAreaConstraints(); ///< Recalculates the camera area constraints Bool isWithinCameraHeightConstraints() const; + virtual void setUserControlled(Bool value); Bool hasScriptedState(ScriptedState state) const; void addScriptedState(ScriptedState state); void removeScriptedState(ScriptedState state); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index c700bba866b..5185fed43ec 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -527,9 +527,6 @@ void W3DView::setCameraTransform() if (TheGlobalData->m_headless) return; - if (m_viewLockedUntilFrame > TheGameClient->getFrame()) - return; - m_cameraHasMovedSinceRequest = true; Matrix3D cameraTransform; @@ -650,10 +647,15 @@ void W3DView::reset() // Just in case... setTimeMultiplier(1); // Set time rate back to 1. + stopDoingScriptedCamera(); + setUserControlled(true); + + // Just move the camera to zero. It'll get repositioned at the beginning of the next game anyways. Coord3D arbitraryPos = { 0, 0, 0 }; - // Just move the camera to 0, 0, 0. It'll get repositioned at the beginning of the next game - // anyways. - resetCamera(&arbitraryPos, 1, 0.0f, 0.0f); + setPosition(&arbitraryPos); + setAngleToDefault(); + setPitchToDefault(); + setZoomToDefault(); setViewFilter(FT_VIEW_DEFAULT); @@ -1281,6 +1283,12 @@ void W3DView::update() didScriptedMovement = true; // don't mess up the scripted movement } } + + if (!m_isUserControlled) + { + didScriptedMovement = true; + } + // // Process camera shake // @@ -1320,8 +1328,9 @@ void W3DView::update() m_heightAboveGround = m_currentHeightAboveGround; } - const Bool isScrolling = TheInGameUI && TheInGameUI->isScrolling(); - const Bool isScrollingTooFast = m_scrollAmount.length() >= m_scrollAmountCutoff; + const Real scrollLenSqr = m_scrollAmount.lengthSqr(); + const Bool isScrolling = scrollLenSqr > FLT_EPSILON; + const Bool isScrollingTooFast = scrollLenSqr >= m_scrollAmountCutoff; const Bool isWithinHeightConstraints = isWithinCameraHeightConstraints(); // if scrolling, only adjust if we're too close or too far @@ -1766,9 +1775,8 @@ void W3DView::setSnapMode( CameraLockType lockType, Real lockDist ) // TheSuperHackers @bugfix Now rotates the view plane on the Z axis only to properly discard the // camera pitch. The aspect ratio also no longer modifies the vertical scroll speed. //------------------------------------------------------------------------------------------------- -void W3DView::scrollBy( Coord2D *delta ) +void W3DView::scrollBy( const Coord2D *delta ) { - // if we haven't moved, ignore if( delta && (delta->x != 0 || delta->y != 0) ) { constexpr const Real SCROLL_RESOLUTION = 250.0f; @@ -1808,7 +1816,11 @@ void W3DView::scrollBy( Coord2D *delta ) removeScriptedState(Scripted_Rotate); m_recalcCamera = true; } - + else + { + m_scrollAmount.x = 0; + m_scrollAmount.y = 0; + } } //------------------------------------------------------------------------------------------------- @@ -1902,7 +1914,8 @@ void W3DView::setHeightAboveGround(Real z) // the camera height. void W3DView::setZoom(Real z) { - View::setZoom(z); + m_heightAboveGround = m_maxHeightAboveGround * z; + m_zoom = z; stopDoingScriptedCamera(); m_CameraArrivedAtWaypointOnPathFlag = false; @@ -2248,7 +2261,6 @@ void W3DView::lookAt( const Coord3D *o ) { Coord3D pos = *o; - // no, don't call the super-lookAt, since it will munge our coords // as for a 2d view. just call setPosition. //View::lookAt(&pos); @@ -3034,6 +3046,15 @@ void W3DView::pitchCameraOneFrame() } } +//------------------------------------------------------------------------------------------------- +void W3DView::setUserControlled(Bool value) +{ + if (m_isUserControlled != value) + { + m_isUserControlled = value; + } +} + // ------------------------------------------------------------------------------------------------ Bool W3DView::isDoingScriptedCamera() { @@ -3056,6 +3077,7 @@ Bool W3DView::hasScriptedState(ScriptedState state) const void W3DView::addScriptedState(ScriptedState state) { m_scriptedState |= state; + setUserControlled(false); } // ------------------------------------------------------------------------------------------------ diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index e26a981d14b..f3d24185882 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -211,6 +211,7 @@ struct Coord2D Real x, y; Real length() const { return (Real)sqrt( x*x + y*y ); } + Real lengthSqr() const { return x*x + y*y; } void normalize() { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp index ee517043d29..b9058daab1e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp @@ -263,7 +263,7 @@ WindowMsgHandledType LeftHUDInput( GameWindow *window, UnsignedInt msg, || (! TheGlobalData->m_useAlternateMouse && msg == GWM_RIGHT_DOWN) || (TheGlobalData->m_useAlternateMouse && msg == GWM_LEFT_DOWN) ) { - TheTacticalView->lookAt( &world ); + TheTacticalView->userLookAt( &world ); break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 87c1b27090f..92350e5c348 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -611,8 +611,7 @@ void GameClient::update() Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); if ( draw ) { - const Coord3D *pos = draw->getPosition(); - TheTacticalView->lookAt( pos ); + TheTacticalView->userLookAt( draw->getPosition() ); } else TheInGameUI->setCameraTrackingDrawable( FALSE ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 3b852e3f676..c8750b44e5c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -2097,20 +2097,20 @@ void InGameUI::update() if( m_cameraRotatingLeft && !m_cameraRotatingRight ) { - TheTacticalView->setAngle( TheTacticalView->getAngle() - rotateAngle ); + TheTacticalView->userSetAngle( TheTacticalView->getAngle() - rotateAngle ); } else if( m_cameraRotatingRight && !m_cameraRotatingLeft ) { - TheTacticalView->setAngle( TheTacticalView->getAngle() + rotateAngle ); + TheTacticalView->userSetAngle( TheTacticalView->getAngle() + rotateAngle ); } if( m_cameraZoomingIn && !m_cameraZoomingOut ) { - TheTacticalView->zoom( -zoomHeight ); + TheTacticalView->userZoom( -zoomHeight ); } else if( m_cameraZoomingOut && !m_cameraZoomingIn ) { - TheTacticalView->zoom( +zoomHeight ); + TheTacticalView->userZoom( +zoomHeight ); } } @@ -3115,8 +3115,8 @@ void InGameUI::setScrolling( Bool isScrolling ) setMouseCursor( Mouse::SCROLL ); // break any camera locks - TheTacticalView->setCameraLock( INVALID_ID ); - TheTacticalView->setCameraLockDrawable( nullptr ); + TheTacticalView->userSetCameraLock( INVALID_ID ); + TheTacticalView->userSetCameraLockDrawable( nullptr ); } else { @@ -5904,7 +5904,7 @@ void InGameUI::selectNextIdleWorker() }*/ // center on the unit - TheTacticalView->lookAt(selectThisObject->getPosition()); + TheTacticalView->userLookAt(selectThisObject->getPosition()); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index bf06bd0a858..089b1566bc0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -952,7 +952,7 @@ static void viewCommandCenter() localPlayer->iterateObjects(findCommandCenterOrMostExpensiveBuilding, &ccl); if (ccl.atLeastOne) { - TheTacticalView->lookAt(&ccl.loc); + TheTacticalView->userLookAt(&ccl.loc); } else { // @todo. Find their starting position and look at that instead? } @@ -2553,7 +2553,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( temp ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2614,7 +2614,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2659,7 +2659,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( temp ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2730,7 +2730,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2782,7 +2782,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheAudio->addAudioEvent( &soundEvent ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2841,7 +2841,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2886,7 +2886,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( temp ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2958,7 +2958,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -3008,7 +3008,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( heroDraw ); // center on the unit - TheTacticalView->lookAt(heroDraw->getPosition()); + TheTacticalView->userLookAt(heroDraw->getPosition()); disp = DESTROY_MESSAGE; break; @@ -3026,7 +3026,9 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage Coord3D lastEvent; if( TheRadar->getLastEventLoc( &lastEvent ) ) - TheTacticalView->lookAt( &lastEvent ); + { + TheTacticalView->userLookAt( &lastEvent ); + } disp = DESTROY_MESSAGE; break; @@ -4668,8 +4670,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage { d = nullptr; } - TheTacticalView->setCameraLock(id); - TheTacticalView->setCameraLockDrawable(d); + TheTacticalView->userSetCameraLock(id); + TheTacticalView->userSetCameraLockDrawable(d); disp = DESTROY_MESSAGE; break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index 552755702b1..5741ca735d2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -314,9 +314,9 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage // if middle button is "clicked", reset to "home" orientation if (!didMove && elapsedMsec < CLICK_DURATION_MSEC) { - TheTacticalView->setAngleToDefault(); - TheTacticalView->setPitchToDefault(); - TheTacticalView->setZoomToDefault(); + TheTacticalView->userSetAngleToDefault(); + TheTacticalView->userSetPitchToDefault(); + TheTacticalView->userSetZoomToDefault(); } break; @@ -373,7 +373,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage targetAngle = WWMath::Round(targetAngle / snapRadians) * snapRadians; } - TheTacticalView->setAngle(targetAngle); + TheTacticalView->userSetAngle(targetAngle); m_anchor = msg->getArgument( 0 )->pixel; } @@ -382,7 +382,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { constexpr const Real Scale = 0.01f; const Real angle = Scale * (m_currentPos.y - m_anchor.y); - TheTacticalView->setPitch( TheTacticalView->getPitch() + angle ); + TheTacticalView->userSetPitch( TheTacticalView->getPitch() + angle ); m_anchor = msg->getArgument( 0 )->pixel; } @@ -392,7 +392,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { constexpr const Real Scale = 0.01f; const Real angle = Scale * (m_currentPos.y - m_anchor.y); - TheTacticalView->setFieldOfView( TheTacticalView->getFieldOfView() + angle ); + TheTacticalView->userSetFieldOfView( TheTacticalView->getFieldOfView() + angle ); m_anchor = msg->getArgument( 0 )->pixel; } #endif @@ -406,7 +406,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage const Int spin = msg->getArgument( 1 )->integer; const Real zoom = -spin * View::ZoomHeightPerSecond; - TheTacticalView->zoom(zoom); + TheTacticalView->userZoom(zoom); break; } @@ -430,6 +430,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { // If we've been forced to stop scrolling (script action?) TheInGameUI->setScrollAmount(offset); + TheTacticalView->scrollBy(&offset); stopScrolling(); } else if (m_isScrolling) @@ -514,12 +515,13 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage } TheInGameUI->setScrollAmount(offset); - TheTacticalView->scrollBy( &offset ); + TheTacticalView->userScrollBy( &offset ); } else { //not scrolling so reset amount TheInGameUI->setScrollAmount(offset); + TheTacticalView->scrollBy(&offset); } //if (TheGlobalData->m_saveCameraInReplay /*&& TheRecorder->getMode() != RECORDERMODETYPE_PLAYBACK *//**/&& (TheGameLogic->isInSinglePlayerGame() || TheGameLogic->isInSkirmishGame())/**/) @@ -649,7 +651,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage Int slot = t - GameMessage::MSG_META_VIEW_VIEW1 + 1; if ( slot > 0 && slot <= MAX_VIEW_LOCS ) { - TheTacticalView->setLocation( &m_viewLocation[slot-1] ); + TheTacticalView->userSetLocation( &m_viewLocation[slot-1] ); } disp = DESTROY_MESSAGE; break; @@ -701,7 +703,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage if (doLock) { - TheTacticalView->setCameraLock( d->getObject()->getID() ); + TheTacticalView->userSetCameraLock( d->getObject()->getID() ); m_lastPlaneID = d->getID(); done = true; break; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index 892b42228fe..b9519dbeb20 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -1103,7 +1103,7 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa { // if there's someone in the group, center the camera on them. Drawable* drawable = objlist[numObjs - 1]->getDrawable(); - TheTacticalView->lookAt( drawable->getPosition() ); + TheTacticalView->userLookAt( drawable->getPosition() ); performSelection = !TheInGameUI->areAllObjectsSelected( objlist ); } } @@ -1179,7 +1179,7 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa if (numObjs > 0) { // if there's someone in the group, center the camera on them. - TheTacticalView->lookAt( objlist[numObjs-1]->getDrawable()->getPosition() ); + TheTacticalView->userLookAt( objlist[numObjs-1]->getDrawable()->getPosition() ); } } } @@ -1255,7 +1255,7 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa if (numObjs > 0) { // if there's someone in the group, center the camera on them. - TheTacticalView->lookAt( objlist[ numObjs-1 ]->getDrawable()->getPosition() ); + TheTacticalView->userLookAt( objlist[ numObjs-1 ]->getDrawable()->getPosition() ); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 6fe151a8d1d..346fcd51767 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1894,13 +1894,20 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Real pitch = msg->getArgument( 2 )->real; const Real zoom = msg->getArgument( 3 )->real; - TheTacticalView->setPosition(&pos); - TheTacticalView->setAngle(angle); - TheTacticalView->setPitch(pitch); - TheTacticalView->setZoom(zoom); + // TheSuperHackers @info Definitely call in user mode to ensure the camera operates with auto-zoom + // over terrain elevations, because the Replay Camera does not store the absolute camera location, + // but key parameters relative to the terrain height at the camera pivot. + TheTacticalView->userSetPosition(&pos); + TheTacticalView->userSetAngle(angle); + TheTacticalView->userSetPitch(pitch); + TheTacticalView->userSetZoom(zoom); + + // TheSuperHackers @fix Make sure there is no scrolling ever. + const Coord2D scroll = {0, 0}; + TheTacticalView->userScrollBy(&scroll); // TheSuperHackers @fix xezon 18/09/2025 Lock the new location to avoid user input from changing the camera in this frame. - TheTacticalView->lockViewUntilFrame( getFrame() + 1 ); + TheTacticalView->lockUserControlUntilFrame( getFrame() + 1 ); if (!TheLookAtTranslator->hasMouseMovedRecently()) { From 204746787c37eda157126828d743ffd6b2dcbabb Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:10:55 +0100 Subject: [PATCH 2/4] Fix scroll amount cutoff --- Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h | 2 +- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index f2b20893fb9..af128140b77 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -277,7 +277,7 @@ class W3DView : public View, public SubsystemInterface Coord3D m_cameraOffset; ///< offset for camera from view center Coord3D m_previousLookAtPosition; ///< offset for camera from view center Coord2D m_scrollAmount; ///< scroll speed - Real m_scrollAmountCutoff; ///< scroll speed at which we do not adjust height + Real m_scrollAmountCutoffSqr; ///< scroll speed at which we do not adjust height Real m_groundLevel; ///< height of ground. diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 5185fed43ec..c7a5056f600 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -624,7 +624,7 @@ void W3DView::init() m_cameraAreaConstraintsValid = false; - m_scrollAmountCutoff = TheGlobalData->m_scrollAmountCutoff; + m_scrollAmountCutoffSqr = sqr(TheGlobalData->m_scrollAmountCutoff); m_recalcCamera = true; } @@ -1330,7 +1330,7 @@ void W3DView::update() const Real scrollLenSqr = m_scrollAmount.lengthSqr(); const Bool isScrolling = scrollLenSqr > FLT_EPSILON; - const Bool isScrollingTooFast = scrollLenSqr >= m_scrollAmountCutoff; + const Bool isScrollingTooFast = scrollLenSqr >= m_scrollAmountCutoffSqr; const Bool isWithinHeightConstraints = isWithinCameraHeightConstraints(); // if scrolling, only adjust if we're too close or too far From 34f0439106117617b453b33e2c41f3f5fdb39eab Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:45:47 +0100 Subject: [PATCH 3/4] Reduce amount of code for user camera functions --- Core/GameEngine/Include/GameClient/View.h | 56 +++++--- Core/GameEngine/Source/GameClient/View.cpp | 159 --------------------- 2 files changed, 40 insertions(+), 175 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/View.h b/Core/GameEngine/Include/GameClient/View.h index bf40115073c..1ff378d5c01 100644 --- a/Core/GameEngine/Include/GameClient/View.h +++ b/Core/GameEngine/Include/GameClient/View.h @@ -194,22 +194,22 @@ class View : public Snapshot virtual void setOkToAdjustHeight( Bool val ) { m_okToAdjustHeight = val; } ///< Set this to adjust camera height // TheSuperHackers @info Functions to call for user camera controls, not by the scripted camera. - Bool userSetPosition(const Coord3D *pos); - Bool userSetAngle(Real radians); - Bool userSetAngleToDefault(); - Bool userSetPitch(Real radians); - Bool userSetPitchToDefault(); - Bool userZoom(Real height); - Bool userSetZoom(Real z); - Bool userSetZoomToDefault(); - Bool userSetFieldOfView(Real angle); - Bool userLookAt(const Coord3D *o); - Bool userScrollBy(const Coord2D *delta); - Bool userSetLocation(const ViewLocation *location); - Bool userSetCameraLock(ObjectID id); - Bool userSetCameraLockDrawable(Drawable *drawable); - - void lockUserControlUntilFrame(UnsignedInt frame); ///< Locks the user control over camera until the given frame is reached. + Bool userSetPosition(const Coord3D *pos) { return doUserAction(&View::setPosition, pos); } + Bool userSetAngle(Real radians) { return doUserAction(&View::setAngle, radians); } + Bool userSetAngleToDefault() { return doUserAction(&View::setAngleToDefault); } + Bool userSetPitch(Real radians) { return doUserAction(&View::setPitch, radians); } + Bool userSetPitchToDefault() { return doUserAction(&View::setPitchToDefault); } + Bool userZoom(Real height) { return doUserAction(&View::zoom, height); } + Bool userSetZoom(Real z) { return doUserAction(&View::setZoom, z); } + Bool userSetZoomToDefault() { return doUserAction(&View::setZoomToDefault); } + Bool userSetFieldOfView(Real angle) { return doUserAction(&View::setFieldOfView, angle); } + Bool userLookAt(const Coord3D *o) { return doUserAction(&View::lookAt, o); } + Bool userScrollBy(const Coord2D *delta) { return doUserAction(&View::scrollBy, delta); } + Bool userSetLocation(const ViewLocation *location) { return doUserAction(&View::setLocation, location); } + Bool userSetCameraLock(ObjectID id) { return doUserAction(&View::setCameraLock, id); } + Bool userSetCameraLockDrawable(Drawable *drawable) { return doUserAction(&View::setCameraLockDrawable, drawable); } + + void lockUserControlUntilFrame(UnsignedInt frame) { m_userControlLockedUntilFrame = frame; } ///< Locks the user control over camera until the given frame is reached. Bool isUserControlLocked() const; // for debugging @@ -266,6 +266,30 @@ class View : public Snapshot virtual void setUserControlled(Bool value) { m_isUserControlled = value; } +private: + + template + Bool doUserAction(Function function) + { + if (isUserControlLocked()) + return false; + stopDoingScriptedCamera(); + setUserControlled(true); + (this->*function)(); + return true; + } + + template + Bool doUserAction(Function function, Arg1 arg1) + { + if (isUserControlLocked()) + return false; + stopDoingScriptedCamera(); + setUserControlled(true); + (this->*function)(arg1); + return true; + } + protected: View *m_next; ///< List links used by the Display class diff --git a/Core/GameEngine/Source/GameClient/View.cpp b/Core/GameEngine/Source/GameClient/View.cpp index f6109db4054..5b36c14ea15 100644 --- a/Core/GameEngine/Source/GameClient/View.cpp +++ b/Core/GameEngine/Source/GameClient/View.cpp @@ -226,165 +226,6 @@ void View::setLocation( const ViewLocation *location ) } -Bool View::userSetPosition(const Coord3D *pos) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setPosition(pos); - return true; -} - -Bool View::userSetAngle(Real radians) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setAngle(radians); - return true; -} - -Bool View::userSetAngleToDefault() -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setAngleToDefault(); - return true; -} - -Bool View::userSetPitch(Real radians) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setPitch(radians); - return true; -} - -Bool View::userSetPitchToDefault() -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setPitchToDefault(); - return true; -} - -Bool View::userZoom(Real height) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - zoom(height); - return true; -} - -Bool View::userSetZoom(Real z) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setZoom(z); - return true; -} - -Bool View::userSetZoomToDefault() -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setZoomToDefault(); - return true; -} - -Bool View::userSetFieldOfView(Real angle) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setFieldOfView(angle); - return true; -} - -Bool View::userLookAt(const Coord3D *o) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - lookAt(o); - return true; -} - -Bool View::userScrollBy(const Coord2D *delta) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - scrollBy(delta); - return true; -} - -Bool View::userSetLocation(const ViewLocation *location) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setLocation(location); - return true; -} - -Bool View::userSetCameraLock(ObjectID id) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setCameraLock(id); - return true; -} - -Bool View::userSetCameraLockDrawable(Drawable *drawable) -{ - if (isUserControlLocked()) - return false; - - stopDoingScriptedCamera(); - setUserControlled(true); - setCameraLockDrawable(drawable); - return true; -} - -void View::lockUserControlUntilFrame(UnsignedInt frame) -{ - m_userControlLockedUntilFrame = frame; -} - Bool View::isUserControlLocked() const { return m_userControlLockedUntilFrame > TheGameClient->getFrame(); From 50ae6022d5d43d77a7d71477cea095209236cfc7 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:03:48 +0100 Subject: [PATCH 4/4] Replicate in Generals --- .../GUI/GUICallbacks/ControlBarCallback.cpp | 2 +- .../GameEngine/Source/GameClient/InGameUI.cpp | 14 +++++----- .../GameClient/MessageStream/CommandXlat.cpp | 28 ++++++++++--------- .../GameClient/MessageStream/LookAtXlat.cpp | 22 ++++++++------- .../MessageStream/SelectionXlat.cpp | 6 ++-- .../GameLogic/System/GameLogicDispatch.cpp | 17 +++++++---- 6 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp index e1824e93a6e..c8ab842a340 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp @@ -264,7 +264,7 @@ WindowMsgHandledType LeftHUDInput( GameWindow *window, UnsignedInt msg, || (! TheGlobalData->m_useAlternateMouse && msg == GWM_RIGHT_DOWN) || (TheGlobalData->m_useAlternateMouse && msg == GWM_LEFT_DOWN) ) { - TheTacticalView->lookAt( &world ); + TheTacticalView->userLookAt( &world ); break; } diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index a7c70a70f7b..4ba15c09991 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -2041,20 +2041,20 @@ void InGameUI::update() if( m_cameraRotatingLeft && !m_cameraRotatingRight ) { - TheTacticalView->setAngle( TheTacticalView->getAngle() - rotateAngle ); + TheTacticalView->userSetAngle( TheTacticalView->getAngle() - rotateAngle ); } else if( m_cameraRotatingRight && !m_cameraRotatingLeft ) { - TheTacticalView->setAngle( TheTacticalView->getAngle() + rotateAngle ); + TheTacticalView->userSetAngle( TheTacticalView->getAngle() + rotateAngle ); } if( m_cameraZoomingIn && !m_cameraZoomingOut ) { - TheTacticalView->zoom( -zoomHeight ); + TheTacticalView->userZoom( -zoomHeight ); } else if( m_cameraZoomingOut && !m_cameraZoomingIn ) { - TheTacticalView->zoom( +zoomHeight ); + TheTacticalView->userZoom( +zoomHeight ); } } @@ -3035,8 +3035,8 @@ void InGameUI::setScrolling( Bool isScrolling ) setMouseCursor( Mouse::SCROLL ); // break any camera locks - TheTacticalView->setCameraLock( INVALID_ID ); - TheTacticalView->setCameraLockDrawable( nullptr ); + TheTacticalView->userSetCameraLock( INVALID_ID ); + TheTacticalView->userSetCameraLockDrawable( nullptr ); } else { @@ -5731,7 +5731,7 @@ void InGameUI::selectNextIdleWorker() }*/ // center on the unit - TheTacticalView->lookAt(selectThisObject->getPosition()); + TheTacticalView->userLookAt(selectThisObject->getPosition()); } } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 13103412d04..a8f6abfa763 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -894,7 +894,7 @@ static void viewCommandCenter() localPlayer->iterateObjects(findCommandCenterOrMostExpensiveBuilding, &ccl); if (ccl.atLeastOne) { - TheTacticalView->lookAt(&ccl.loc); + TheTacticalView->userLookAt(&ccl.loc); } else { // @todo. Find their starting position and look at that instead? } @@ -2407,7 +2407,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( temp ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2468,7 +2468,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2513,7 +2513,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( temp ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2584,7 +2584,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2636,7 +2636,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheAudio->addAudioEvent( &soundEvent ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2695,7 +2695,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2740,7 +2740,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( temp ); // center on the unit - TheTacticalView->lookAt(temp->getPosition()); + TheTacticalView->userLookAt(temp->getPosition()); break; } } @@ -2812,7 +2812,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( newDrawable ); // center on the unit - TheTacticalView->lookAt(newDrawable->getPosition()); + TheTacticalView->userLookAt(newDrawable->getPosition()); } } } @@ -2862,7 +2862,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage TheInGameUI->selectDrawable( heroDraw ); // center on the unit - TheTacticalView->lookAt(heroDraw->getPosition()); + TheTacticalView->userLookAt(heroDraw->getPosition()); disp = DESTROY_MESSAGE; break; @@ -2880,7 +2880,9 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage Coord3D lastEvent; if( TheRadar->getLastEventLoc( &lastEvent ) ) - TheTacticalView->lookAt( &lastEvent ); + { + TheTacticalView->userLookAt( &lastEvent ); + } disp = DESTROY_MESSAGE; break; @@ -4282,8 +4284,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage { d = nullptr; } - TheTacticalView->setCameraLock(id); - TheTacticalView->setCameraLockDrawable(d); + TheTacticalView->userSetCameraLock(id); + TheTacticalView->userSetCameraLockDrawable(d); disp = DESTROY_MESSAGE; break; } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index 86c2617e0b6..95754064dc4 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -315,9 +315,9 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage // if middle button is "clicked", reset to "home" orientation if (!didMove && elapsedMsec < CLICK_DURATION_MSEC) { - TheTacticalView->setAngleToDefault(); - TheTacticalView->setPitchToDefault(); - TheTacticalView->setZoomToDefault(); + TheTacticalView->userSetAngleToDefault(); + TheTacticalView->userSetPitchToDefault(); + TheTacticalView->userSetZoomToDefault(); } break; @@ -374,7 +374,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage targetAngle = WWMath::Round(targetAngle / snapRadians) * snapRadians; } - TheTacticalView->setAngle(targetAngle); + TheTacticalView->userSetAngle(targetAngle); m_anchor = msg->getArgument( 0 )->pixel; } @@ -383,7 +383,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { constexpr const Real Scale = 0.01f; const Real angle = Scale * (m_currentPos.y - m_anchor.y); - TheTacticalView->setPitch( TheTacticalView->getPitch() + angle ); + TheTacticalView->userSetPitch( TheTacticalView->getPitch() + angle ); m_anchor = msg->getArgument( 0 )->pixel; } @@ -393,7 +393,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { constexpr const Real Scale = 0.01f; const Real angle = Scale * (m_currentPos.y - m_anchor.y); - TheTacticalView->setFieldOfView( TheTacticalView->getFieldOfView() + angle ); + TheTacticalView->userSetFieldOfView( TheTacticalView->getFieldOfView() + angle ); m_anchor = msg->getArgument( 0 )->pixel; } #endif @@ -407,7 +407,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage const Int spin = msg->getArgument( 1 )->integer; const Real zoom = -spin * View::ZoomHeightPerSecond; - TheTacticalView->zoom(zoom); + TheTacticalView->userZoom(zoom); break; } @@ -431,6 +431,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage { // If we've been forced to stop scrolling (script action?) TheInGameUI->setScrollAmount(offset); + TheTacticalView->scrollBy(&offset); stopScrolling(); } else if (m_isScrolling) @@ -515,12 +516,13 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage } TheInGameUI->setScrollAmount(offset); - TheTacticalView->scrollBy( &offset ); + TheTacticalView->userScrollBy( &offset ); } else { //not scrolling so reset amount TheInGameUI->setScrollAmount(offset); + TheTacticalView->scrollBy(&offset); } //if (TheGlobalData->m_saveCameraInReplay /*&& TheRecorder->getMode() != RECORDERMODETYPE_PLAYBACK *//**/&& (TheGameLogic->isInSinglePlayerGame() || TheGameLogic->isInSkirmishGame())/**/) @@ -638,7 +640,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage Int slot = t - GameMessage::MSG_META_VIEW_VIEW1 + 1; if ( slot > 0 && slot <= MAX_VIEW_LOCS ) { - TheTacticalView->setLocation( &m_viewLocation[slot-1] ); + TheTacticalView->userSetLocation( &m_viewLocation[slot-1] ); } disp = DESTROY_MESSAGE; break; @@ -690,7 +692,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage if (doLock) { - TheTacticalView->setCameraLock( d->getObject()->getID() ); + TheTacticalView->userSetCameraLock( d->getObject()->getID() ); m_lastPlaneID = d->getID(); done = true; break; diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index b2eed26c3f9..fb54e6c071b 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -1028,7 +1028,7 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa { // if there's someone in the group, center the camera on them. Drawable* drawable = objlist[numObjs - 1]->getDrawable(); - TheTacticalView->lookAt( drawable->getPosition() ); + TheTacticalView->userLookAt( drawable->getPosition() ); performSelection = !TheInGameUI->areAllObjectsSelected( objlist ); } } @@ -1104,7 +1104,7 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa if (numObjs > 0) { // if there's someone in the group, center the camera on them. - TheTacticalView->lookAt( objlist[numObjs-1]->getDrawable()->getPosition() ); + TheTacticalView->userLookAt( objlist[numObjs-1]->getDrawable()->getPosition() ); } } } @@ -1180,7 +1180,7 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa if (numObjs > 0) { // if there's someone in the group, center the camera on them. - TheTacticalView->lookAt( objlist[ numObjs-1 ]->getDrawable()->getPosition() ); + TheTacticalView->userLookAt( objlist[ numObjs-1 ]->getDrawable()->getPosition() ); } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 723e9d4c0eb..894c78a56ac 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1866,13 +1866,20 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Real pitch = msg->getArgument( 2 )->real; const Real zoom = msg->getArgument( 3 )->real; - TheTacticalView->setPosition(&pos); - TheTacticalView->setAngle(angle); - TheTacticalView->setPitch(pitch); - TheTacticalView->setZoom(zoom); + // TheSuperHackers @info Definitely call in user mode to ensure the camera operates with auto-zoom + // over terrain elevations, because the Replay Camera does not store the absolute camera location, + // but key parameters relative to the terrain height at the camera pivot. + TheTacticalView->userSetPosition(&pos); + TheTacticalView->userSetAngle(angle); + TheTacticalView->userSetPitch(pitch); + TheTacticalView->userSetZoom(zoom); + + // TheSuperHackers @fix Make sure there is no scrolling ever. + const Coord2D scroll = {0, 0}; + TheTacticalView->userScrollBy(&scroll); // TheSuperHackers @fix xezon 18/09/2025 Lock the new location to avoid user input from changing the camera in this frame. - TheTacticalView->lockViewUntilFrame( getFrame() + 1 ); + TheTacticalView->lockUserControlUntilFrame( getFrame() + 1 ); if (!TheLookAtTranslator->hasMouseMovedRecently()) {