diff --git a/Generals/Code/GameEngine/Include/GameClient/LookAtXlat.h b/Generals/Code/GameEngine/Include/GameClient/LookAtXlat.h index 8aa865f59a9..91522d87b1a 100644 --- a/Generals/Code/GameEngine/Include/GameClient/LookAtXlat.h +++ b/Generals/Code/GameEngine/Include/GameClient/LookAtXlat.h @@ -71,6 +71,7 @@ class LookAtTranslator : public GameMessageTranslator ICoord2D m_anchor; ICoord2D m_originalAnchor; ICoord2D m_currentPos; + Real m_anchorAngle; Bool m_isScrolling; // set to true if we are in the act of RMB scrolling Bool m_isRotating; // set to true if we are in the act of MMB rotating Bool m_isPitching; // set to true if we are in the act of ALT pitch rotation diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index f2f60953063..c77e045a11e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -291,6 +291,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage m_isRotating = true; m_anchor = msg->getArgument( 0 )->pixel; + m_anchorAngle = TheTacticalView->getAngle(); m_originalAnchor = msg->getArgument( 0 )->pixel; m_currentPos = msg->getArgument( 0 )->pixel; m_timestamp = TheGameClient->getFrame(); @@ -360,10 +361,18 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage if (m_isRotating) { const Real FACTOR = 0.01f; + const Real angle = FACTOR * (m_currentPos.x - m_originalAnchor.x); + Real targetAngle = m_anchorAngle + angle; - Real angle = FACTOR * (m_currentPos.x - m_anchor.x); + // TheSuperHackers @tweak Stubbjax 13/11/2025 Snap angle to nearest 45 degrees + // while using force attack mode for convenience. + if (TheInGameUI->isInForceAttackMode()) + { + const Real snapRadians = DEG_TO_RADF(45); + targetAngle = WWMath::Round(targetAngle / snapRadians) * snapRadians; + } - TheTacticalView->setAngle( TheTacticalView->getAngle() + angle ); + TheTacticalView->setAngle(targetAngle); m_anchor = msg->getArgument( 0 )->pixel; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/LookAtXlat.h b/GeneralsMD/Code/GameEngine/Include/GameClient/LookAtXlat.h index d69dfe48580..e91b98f66ec 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/LookAtXlat.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/LookAtXlat.h @@ -71,6 +71,7 @@ class LookAtTranslator : public GameMessageTranslator ICoord2D m_anchor; ICoord2D m_originalAnchor; ICoord2D m_currentPos; + Real m_anchorAngle; Bool m_isScrolling; // set to true if we are in the act of RMB scrolling Bool m_isRotating; // set to true if we are in the act of MMB rotating Bool m_isPitching; // set to true if we are in the act of ALT pitch rotation diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index c5b76e705f5..b9990857def 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -290,6 +290,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage m_isRotating = true; m_anchor = msg->getArgument( 0 )->pixel; + m_anchorAngle = TheTacticalView->getAngle(); m_originalAnchor = msg->getArgument( 0 )->pixel; m_currentPos = msg->getArgument( 0 )->pixel; m_timestamp = TheGameClient->getFrame(); @@ -359,10 +360,18 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage if (m_isRotating) { const Real FACTOR = 0.01f; + const Real angle = FACTOR * (m_currentPos.x - m_originalAnchor.x); + Real targetAngle = m_anchorAngle + angle; - Real angle = FACTOR * (m_currentPos.x - m_anchor.x); + // TheSuperHackers @tweak Stubbjax 13/11/2025 Snap angle to nearest 45 degrees + // while using force attack mode for convenience. + if (TheInGameUI->isInForceAttackMode()) + { + const Real snapRadians = DEG_TO_RADF(45); + targetAngle = WWMath::Round(targetAngle / snapRadians) * snapRadians; + } - TheTacticalView->setAngle( TheTacticalView->getAngle() + angle ); + TheTacticalView->setAngle(targetAngle); m_anchor = msg->getArgument( 0 )->pixel; }