Index: binaries/data/mods/public/simulation/components/Formation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Formation.js
+++ binaries/data/mods/public/simulation/components/Formation.js
@@ -866,29 +866,13 @@
*/
Formation.prototype.GetEstimatedOrientation = function(pos)
{
- let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
- let r = { "sin": 0, "cos": 1 };
- let unitAIState = cmpUnitAI.GetCurrentState();
- if (unitAIState == "FORMATIONCONTROLLER.WALKING" || unitAIState == "FORMATIONCONTROLLER.COMBAT.APPROACHING")
- {
- let targetPos = cmpUnitAI.GetTargetPositions();
- if (!targetPos.length)
- return r;
- let d = targetPos[0].sub(pos).normalize();
- if (!d.x && !d.y)
- return r;
- r.cos = d.y;
- r.sin = d.x;
- }
- else
- {
- let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
- if (!cmpPosition)
- return r;
- let rot = cmpPosition.GetRotation().y;
- r.sin = Math.sin(rot);
- r.cos = Math.cos(rot);
- }
+ let r = {};
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition)
+ return r;
+ let rot = cmpPosition.GetRotation().y;
+ r.sin = Math.sin(rot);
+ r.cos = Math.cos(rot);
return r;
};
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
@@ -952,6 +952,9 @@
this.FinishOrder();
return true;
}
+
+ this.ToggleTurnTime();
+
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.SetRearrange(true);
cmpFormation.MoveMembersIntoFormation(true, true);
@@ -975,6 +978,9 @@
this.FinishOrder();
return true;
}
+
+ this.ToggleTurnTime();
+
this.StartTimer(0, 1000);
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.SetRearrange(true);
@@ -1021,6 +1027,8 @@
}
this.StartTimer(0, 1000);
+ this.ToggleTurnTime();
+
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.SetRearrange(true);
cmpFormation.MoveMembersIntoFormation(true, true, "combat");
@@ -1593,6 +1601,7 @@
this.FinishOrder();
return true;
}
+ this.ToggleTurnTime();
return false;
},
@@ -1616,6 +1625,7 @@
this.FinishOrder();
return true;
}
+ this.ToggleTurnTime();
// Show weapons rather than carried resources.
this.SetAnimationVariant("combat");
@@ -1653,6 +1663,8 @@
return true;
}
+ this.ToggleTurnTime();
+
if (!this.patrolStartPosOrder)
{
this.patrolStartPosOrder = cmpPosition.GetPosition();
@@ -6096,6 +6108,12 @@
cmpUnitMotion.SetSpeedMultiplier(speed);
};
+UnitAI.prototype.ToggleTurnTime = function()
+{
+ let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ if (cmpUnitMotion)
+ cmpUnitMotion.ToggleTurnTime();
+};
/**
* Try to match the targets current movement speed.
*
Index: binaries/data/mods/public/simulation/components/UnitMotionFlying.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitMotionFlying.js
+++ binaries/data/mods/public/simulation/components/UnitMotionFlying.js
@@ -327,6 +327,11 @@
// Ignore this - angle is controlled by the target-seeking code instead.
};
+UnitMotionFlying.prototype.ToggleTurnTime = function()
+{
+ // Ignore this - unitMotionFlying has his own turning algorithm.
+};
+
UnitMotionFlying.prototype.StopMoving = function()
{
// Invert.
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 @@
"StopMoving": () => {},
"SetFacePointAfterMove": () => {},
"GetFacePointAfterMove": () => true,
+ "ToggleTurnTime": () => {},
"GetPassabilityClassName": () => "default"
});
@@ -245,7 +246,8 @@
"MoveToPointRange": () => true,
"SetFacePointAfterMove": () => {},
"GetFacePointAfterMove": () => true,
- "GetPassabilityClassName": () => "default"
+ "GetPassabilityClassName": () => "default",
+ "ToggleTurnTime": () => {}
});
controllerAI.OnCreate();
Index: binaries/data/mods/public/simulation/templates/template_formation.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_formation.xml
+++ binaries/data/mods/public/simulation/templates/template_formation.xml
@@ -53,8 +53,8 @@
0
upright
false
- 0.0
- 3.0
+ 0
+ 3
0.75
Index: source/simulation2/components/CCmpPosition.cpp
===================================================================
--- source/simulation2/components/CCmpPosition.cpp
+++ source/simulation2/components/CCmpPosition.cpp
@@ -129,7 +129,7 @@
""
""
""
- ""
+ ""
""
"";
}
@@ -536,6 +536,11 @@
return CFixedVector2D(m_PrevX, m_PrevZ);
}
+ virtual float GetTurnRate() const
+ {
+ return m_RotYSpeed;
+ }
+
virtual void TurnTo(entity_angle_t y)
{
if (m_TurretParent != INVALID_ENTITY)
Index: source/simulation2/components/CCmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/CCmpUnitMotion.cpp
+++ source/simulation2/components/CCmpUnitMotion.cpp
@@ -140,6 +140,7 @@
fixed m_WalkSpeed, m_RunMultiplier;
bool m_FacePointAfterMove;
+ bool m_TurnTime;
// Number of path computations that failed (in a row).
// When this gets above MAX_FAILED_PATH_COMPUTATIONS, inform other components
@@ -227,6 +228,7 @@
m_FormationController = paramNode.GetChild("FormationController").ToBool();
m_FacePointAfterMove = true;
+ m_TurnTime = false;
m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
m_SpeedMultiplier = fixed::FromInt(1);
@@ -278,6 +280,7 @@
serialize.NumberFixed_Unbounded("current speed", m_CurSpeed);
serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
+ serialize.Bool("turnTime", m_TurnTime);
SerializeVector()(serialize, "long path", m_LongPath.m_Waypoints);
SerializeVector()(serialize, "short path", m_ShortPath.m_Waypoints);
@@ -431,6 +434,11 @@
return m_FacePointAfterMove;
}
+ virtual void ToggleTurnTime()
+ {
+ m_TurnTime = true;
+ }
+
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;
@@ -616,7 +624,7 @@
* This does not send actually change the position.
* @returns true if the move was obstructed.
*/
- bool PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const;
+ bool PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, entity_angle_t& angle);
/**
* Update other components on our speed.
@@ -876,24 +884,29 @@
// Keep track of the current unit's position during the update
CFixedVector2D pos = initialPos;
+ entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
+ entity_angle_t angle = initialAngle;
// If we're chasing a potentially-moving unit and are currently close
// enough to its current position, and we can head in a straight line
// to it, then throw away our current path and go straight to it
bool wentStraight = TryGoingStraightToTarget(initialPos);
- bool wasObstructed = PerformMove(dt, m_ShortPath, m_LongPath, pos);
+ bool wasObstructed = PerformMove(dt, m_ShortPath, m_LongPath, pos, angle);
- // Update our speed over this turn so that the visual actor shows the correct animation.
if (pos == initialPos)
+ {
+ // Update our speed over this turn so that the visual actor shows the correct animation.
UpdateMovementState(fixed::Zero());
+ if (angle != initialAngle)
+ cmpPosition->TurnTo(angle);
+ }
else
{
// Update the Position component after our movement (if we actually moved anywhere)
+ // When moving always set the angle in the direction of the movement.
CFixedVector2D offset = pos - initialPos;
-
- // Face towards the target
- entity_angle_t angle = atan2_approx(offset.X, offset.Y);
- cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle);
+ angle = atan2_approx(offset.X, offset.Y);
+ cmpPosition->MoveAndTurnTo(pos.X, pos.Y, angle);
// Calculate the mean speed over this past turn.
UpdateMovementState(offset.Length() / dt);
@@ -942,12 +955,18 @@
return false;
}
-bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const
+bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, entity_angle_t& angle)
{
// If there are no waypoint, behave as though we were obstructed and let HandleObstructedMove handle it.
if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty())
return true;
+ // Wrap the angle to (-Pi, Pi].
+ while (angle > entity_angle_t::Pi())
+ angle -= entity_angle_t::Pi() * 2;
+ while (angle < -entity_angle_t::Pi())
+ angle += entity_angle_t::Pi() * 2;
+
// TODO: there's some asymmetry here when units look at other
// units' positions - the result will depend on the order of execution.
// Maybe we should split the updates into multiple phases to minimise
@@ -969,8 +988,6 @@
fixed maxSpeed = basicSpeed.Multiply(terrainSpeed);
- // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint
-
fixed timeLeft = dt;
fixed zero = fixed::Zero();
@@ -987,6 +1004,39 @@
target = CFixedVector2D(shortPath.m_Waypoints.back().x, shortPath.m_Waypoints.back().z);
CFixedVector2D offset = target - pos;
+ if (m_TurnTime)
+ {
+ CmpPtr cmpPosition(GetEntityHandle());
+ fixed turnRate = fixed::FromFloat(cmpPosition->GetTurnRate());
+ fixed maxRotation = turnRate.Multiply(timeLeft);
+ fixed angleDiff = angle - atan2_approx(offset.X, offset.Y);
+ if (angleDiff != zero)
+ {
+ fixed absoluteAngleDiff = angleDiff.Absolute();
+ if (absoluteAngleDiff > entity_angle_t::Pi())
+ absoluteAngleDiff = entity_angle_t::Pi() * 2 - absoluteAngleDiff;
+
+ // Figure out whether rotating will increase or decrease the angle, and how far we need to rotate in that direction.
+ int direction = (entity_angle_t::Zero() < angleDiff && angleDiff <= entity_angle_t::Pi()) || angleDiff < -entity_angle_t::Pi() ? -1 : 1;
+
+ // Can't rotate far enough, just rotate in the correct direction.
+ if (absoluteAngleDiff > maxRotation)
+ {
+ angle += maxRotation * direction;
+ if (angle * direction > entity_angle_t::Pi())
+ angle -= entity_angle_t::Pi() * 2 * direction;
+ break;
+ }
+ // Rotate to the next waypoint.
+ else
+ {
+ m_TurnTime = false;
+ angle = atan2_approx(offset.X, offset.Y);
+ timeLeft = (maxRotation - absoluteAngleDiff) / turnRate;
+ continue;
+ }
+ }
+ }
// Work out how far we can travel in timeLeft
fixed maxdist = maxSpeed.Multiply(timeLeft);
Index: source/simulation2/components/ICmpPosition.h
===================================================================
--- source/simulation2/components/ICmpPosition.h
+++ source/simulation2/components/ICmpPosition.h
@@ -185,6 +185,11 @@
virtual CFixedVector2D GetPreviousPosition2D() const = 0;
/**
+ * Returns the turn rate in radians per second
+ */
+ virtual float GetTurnRate() const = 0;
+
+ /**
* Rotate smoothly to the given angle around the upwards axis.
* @param y clockwise radians from the +Z axis.
*/
Index: source/simulation2/components/ICmpPosition.cpp
===================================================================
--- source/simulation2/components/ICmpPosition.cpp
+++ source/simulation2/components/ICmpPosition.cpp
@@ -42,6 +42,7 @@
DEFINE_INTERFACE_METHOD_CONST_0("GetPosition2D", CFixedVector2D, ICmpPosition, GetPosition2D)
DEFINE_INTERFACE_METHOD_CONST_0("GetPreviousPosition", CFixedVector3D, ICmpPosition, GetPreviousPosition)
DEFINE_INTERFACE_METHOD_CONST_0("GetPreviousPosition2D", CFixedVector2D, ICmpPosition, GetPreviousPosition2D)
+DEFINE_INTERFACE_METHOD_CONST_0("GetTurnRate", float, ICmpPosition, GetTurnRate)
DEFINE_INTERFACE_METHOD_1("TurnTo", void, ICmpPosition, TurnTo, entity_angle_t)
DEFINE_INTERFACE_METHOD_1("SetYRotation", void, ICmpPosition, SetYRotation, entity_angle_t)
DEFINE_INTERFACE_METHOD_2("SetXZRotation", void, ICmpPosition, SetXZRotation, entity_angle_t, entity_angle_t)
Index: source/simulation2/components/ICmpUnitMotion.h
===================================================================
--- source/simulation2/components/ICmpUnitMotion.h
+++ source/simulation2/components/ICmpUnitMotion.h
@@ -124,6 +124,11 @@
virtual bool GetFacePointAfterMove() const = 0;
/**
+ * Set whether the unit will waste time rotating on the next turn when moving.
+ */
+ virtual void ToggleTurnTime() = 0;
+
+ /**
* Get the unit's passability class.
*/
virtual pass_class_t GetPassabilityClass() const = 0;
Index: source/simulation2/components/ICmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/ICmpUnitMotion.cpp
+++ source/simulation2/components/ICmpUnitMotion.cpp
@@ -39,6 +39,7 @@
DEFINE_INTERFACE_METHOD_CONST_0("GetUnitClearance", entity_pos_t, ICmpUnitMotion, GetUnitClearance)
DEFINE_INTERFACE_METHOD_1("SetFacePointAfterMove", void, ICmpUnitMotion, SetFacePointAfterMove, bool)
DEFINE_INTERFACE_METHOD_CONST_0("GetFacePointAfterMove", bool, ICmpUnitMotion, GetFacePointAfterMove)
+DEFINE_INTERFACE_METHOD_0("ToggleTurnTime", void, ICmpUnitMotion, ToggleTurnTime)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitMotion, SetDebugOverlay, bool)
END_INTERFACE_WRAPPER(UnitMotion)
@@ -122,6 +123,11 @@
return m_Script.Call("GetFacePointAfterMove");
}
+ virtual void ToggleTurnTime()
+ {
+ m_Script.CallVoid("ToggleTurnTime");
+ }
+
virtual pass_class_t GetPassabilityClass() const
{
return m_Script.Call("GetPassabilityClass");
Index: source/simulation2/components/tests/test_RangeManager.h
===================================================================
--- source/simulation2/components/tests/test_RangeManager.h
+++ source/simulation2/components/tests/test_RangeManager.h
@@ -60,6 +60,7 @@
virtual CFixedVector2D GetPosition2D() const { return CFixedVector2D(); }
virtual CFixedVector3D GetPreviousPosition() const { return CFixedVector3D(); }
virtual CFixedVector2D GetPreviousPosition2D() const { return CFixedVector2D(); }
+ virtual float GetTurnRate() const { return 0; }
virtual void TurnTo(entity_angle_t UNUSED(y)) { }
virtual void SetYRotation(entity_angle_t UNUSED(y)) { }
virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { }