Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -1974,7 +1974,7 @@ // If using a non-default prepare time, re-sync the animation when the timer runs. this.resyncAnimation = prepare != this.attackTimers.prepare; - this.FaceTowardsTarget(this.order.data.target); + this.SetTargetToFace(this.order.data.target); let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); if (cmpBuildingAI) @@ -1988,6 +1988,7 @@ cmpBuildingAI.SetUnitAITarget(0); this.StopTimer(); this.ResetAnimation(); + this.StopFacingTarget(); }, "Timer": function(msg) { @@ -2008,8 +2009,6 @@ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.lastAttacked = cmpTimer.GetTime() - msg.lateness; - this.FaceTowardsTarget(target); - // BuildingAI has it's own attack-routine let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); if (!cmpBuildingAI) @@ -4888,6 +4887,27 @@ cmpPosition.TurnTo(cmpPosition.GetPosition2D().angleTo(targetPosition)); }; +/** + * Let an entity keep facing their target. + * @param {number} entity - The entity-ID of the target. + */ +UnitAI.prototype.SetTargetToFace = function(target) +{ + let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + if (cmpUnitMotion) + cmpUnitMotion.SetTargetToFace(target); +}; + +/** + * Stop facing a target. + */ +UnitAI.prototype.StopFacingTarget = function() +{ + let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + if (cmpUnitMotion) + cmpUnitMotion.ResetTargetToFace(); +}; + UnitAI.prototype.CheckTargetDistanceFromHeldPosition = function(target, iid, type) { let range = this.GetRange(iid, type); Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -176,6 +176,7 @@ "MoveToTargetRange": (target, min, max) => true, "StopMoving": () => {}, "SetFacePointAfterMove": () => {}, + "SetTargetToFace": () => {}, "GetPassabilityClassName": () => "default" }); @@ -334,8 +335,10 @@ "GetWalkSpeed": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, + "ResetTargetToFace": () => {}, "StopMoving": () => {}, "SetFacePointAfterMove": () => {}, + "SetTargetToFace": () => {}, "GetPassabilityClassName": () => "default" }); Index: source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- source/simulation2/components/CCmpUnitMotion.cpp +++ source/simulation2/components/CCmpUnitMotion.cpp @@ -186,6 +186,9 @@ WaypointPath m_LongPath; WaypointPath m_ShortPath; + // A target to keep turning towards. + entity_id_t m_TargetToKeepFacing = INVALID_ENTITY; + static std::string GetSchema() { return @@ -302,11 +305,15 @@ } case MT_Update_MotionUnit: { - if (!m_FormationController) - { - fixed dt = static_cast (msg).turnLength; - Move(dt); - } + if (m_FormationController) + break; + + fixed dt = static_cast (msg).turnLength; + Move(dt); + + if (m_TargetToKeepFacing != INVALID_ENTITY) + TurnToTarget(m_TargetToKeepFacing); + break; } case MT_RenderSubmit: @@ -466,6 +473,16 @@ return m_Clearance; } + virtual void ResetTargetToFace() + { + SetTargetToFace(INVALID_ENTITY); + } + + virtual void SetTargetToFace(entity_id_t target) + { + m_TargetToKeepFacing = target; + } + private: bool ShouldAvoidMovingUnits() const { @@ -660,6 +677,8 @@ void RenderPath(const WaypointPath& path, std::vector& lines, CColor color); void RenderSubmit(SceneCollector& collector); + + void TurnToTarget(entity_id_t target); }; REGISTER_COMPONENT_TYPE(UnitMotion) @@ -1218,6 +1237,16 @@ return true; } +void CCmpUnitMotion::TurnToTarget(entity_id_t target) +{ + CmpPtr cmpPosition(GetSimContext(), target); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return; + + CFixedVector2D pos = cmpPosition->GetPosition2D(); + FaceTowardsPoint(pos.X, pos.Y); +} + void CCmpUnitMotion::FaceTowardsPoint(entity_pos_t x, entity_pos_t z) { CmpPtr cmpPosition(GetEntityHandle()); Index: source/simulation2/components/ICmpUnitMotion.h =================================================================== --- source/simulation2/components/ICmpUnitMotion.h +++ source/simulation2/components/ICmpUnitMotion.h @@ -132,6 +132,16 @@ */ virtual void SetDebugOverlay(bool enabled) = 0; + /** + * Keep facing a target. + */ + virtual void SetTargetToFace(entity_id_t target) = 0; + + /** + * Stop facing a target. + */ + virtual void ResetTargetToFace() = 0; + DECLARE_INTERFACE_TYPE(UnitMotion) }; Index: source/simulation2/components/ICmpUnitMotion.cpp =================================================================== --- source/simulation2/components/ICmpUnitMotion.cpp +++ source/simulation2/components/ICmpUnitMotion.cpp @@ -38,6 +38,8 @@ DEFINE_INTERFACE_METHOD_CONST_0("GetUnitClearance", entity_pos_t, ICmpUnitMotion, GetUnitClearance) DEFINE_INTERFACE_METHOD_1("SetFacePointAfterMove", void, ICmpUnitMotion, SetFacePointAfterMove, bool) DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitMotion, SetDebugOverlay, bool) +DEFINE_INTERFACE_METHOD_1("SetTargetToFace", void, ICmpUnitMotion, SetTargetToFace, entity_id_t) +DEFINE_INTERFACE_METHOD_0("ResetTargetToFace", void, ICmpUnitMotion, ResetTargetToFace) END_INTERFACE_WRAPPER(UnitMotion) class CCmpUnitMotionScripted : public ICmpUnitMotion @@ -130,6 +132,16 @@ m_Script.CallVoid("SetDebugOverlay", enabled); } + virtual void SetTargetToFace(entity_id_t target) + { + m_Script.CallVoid("SetTargetToFace", target); + } + + virtual void ResetTargetToFace() + { + m_Script.CallVoid("ResetTargetToFace"); + } + }; REGISTER_COMPONENT_SCRIPT_WRAPPER(UnitMotionScripted)