Let units take time actual time for turning while moving. This limits the possibility of "dancing" (making short moves to avoid being hit by arrows) beyond only patrolling.

Reviewed By: wraitii
Gameplay Tests By: FeldFeld
Comments By: Freagarach, Angen
Differential Revision: D2837
refs: #5106

This was SVN commit r24415.
This commit is contained in:
bb
2020-12-17 22:54:14 +00:00
parent 051807ffae
commit 42c70cd508
30 changed files with 138 additions and 73 deletions
@@ -859,36 +859,18 @@ Formation.prototype.GetRealOffsetPositions = function(offsets, pos)
};
/**
* Calculate the estimated rotation of the formation
* based on the first unitAI target position when ordered to walk,
* based on the current rotation in other cases.
* Calculate the estimated rotation of the formation based on the current rotation.
* Return the sine and cosine of the angle.
*/
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;
};
@@ -23,8 +23,8 @@
<Altitude>-1</Altitude>
<Anchor>upright</Anchor>
<Floating>true</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>5.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>5</TurnRate>
</Position>
<Selectable>
<Overlay>
@@ -4,8 +4,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>6</TurnRate>
</Position>
<Visibility>
<RetainInFog>true</RetainInFog>
@@ -8,8 +8,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>1.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>1</TurnRate>
</Position>
<Selectable>
<EditorOnly/>
@@ -53,8 +53,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>3.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>3</TurnRate>
</Position>
<Trader>
<GainMultiplier>0.75</GainMultiplier>
@@ -23,8 +23,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>6</TurnRate>
</Position>
<Selectable>
<Overlay>
@@ -12,8 +12,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>6</TurnRate>
</Position>
<Visibility>
<RetainInFog>true</RetainInFog>
@@ -74,8 +74,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>6</TurnRate>
</Position>
<ProductionQueue>
<BatchTimeModifier>1.0</BatchTimeModifier>
@@ -10,8 +10,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>6</TurnRate>
</Position>
<Selectable>
<EditorOnly/>
@@ -11,8 +11,8 @@
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>6</TurnRate>
</Position>
<Selectable>
<EditorOnly/>
@@ -70,7 +70,7 @@
<Anchor>pitch</Anchor>
<Floating>false</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>6.0</TurnRate>
<TurnRate>6</TurnRate>
</Position>
<RangeOverlayManager/>
<RangeOverlayRenderer/>
@@ -44,6 +44,9 @@
<stone>0</stone>
<metal>0</metal>
</Loot>
<Position>
<TurnRate>4</TurnRate>
</Position>
<Promotion>
<RequiredXp>150</RequiredXp>
</Promotion>
@@ -29,6 +29,9 @@
<wood>8</wood>
<metal>10</metal>
</Loot>
<Position>
<TurnRate>4</TurnRate>
</Position>
<Resistance>
<Entity>
<Damage>
@@ -24,6 +24,9 @@
<food>30</food>
<metal>20</metal>
</Loot>
<Position>
<TurnRate>2</TurnRate>
</Position>
<Resistance>
<Entity>
<Damage>
@@ -13,6 +13,9 @@
<Loot>
<xp>260</xp>
</Loot>
<Position>
<TurnRate>2</TurnRate>
</Position>
<Promotion>
<RequiredXp>150</RequiredXp>
</Promotion>
@@ -18,6 +18,9 @@
<Type>food</Type>
<Color r="227" g="130" b="141"/>
</Minimap>
<Position>
<TurnRate>4</TurnRate>
</Position>
<ResourceGatherer disable=""/>
<Selectable>
<Overlay>
@@ -27,6 +27,9 @@
<Loot>
<xp>50</xp>
</Loot>
<Position>
<TurnRate>2</TurnRate>
</Position>
<Resistance>
<Entity>
<Damage>
@@ -23,6 +23,9 @@
special/formations/wedge
</Formations>
</Identity>
<Position>
<TurnRate>4</TurnRate>
</Position>
<Resistance>
<Entity>
<Damage>
@@ -31,6 +31,9 @@
<Identity>
<VisibleClasses datatype="tokens">Elephant Melee</VisibleClasses>
</Identity>
<Position>
<TurnRate>2</TurnRate>
</Position>
<Resistance>
<Entity>
<Damage>
@@ -32,8 +32,8 @@
<Position>
<Anchor>upright</Anchor>
<Floating>true</Floating>
<FloatDepth>0.0</FloatDepth>
<TurnRate>3.0</TurnRate>
<FloatDepth>0</FloatDepth>
<TurnRate>3</TurnRate>
</Position>
<Repairable>
<RepairTimeRatio>4.0</RepairTimeRatio>
@@ -66,6 +66,9 @@
<wood>40</wood>
<metal>30</metal>
</Loot>
<Position>
<TurnRate>2</TurnRate>
</Position>
<ResourceGatherer disable=""/>
<Sound>
<SoundGroups>
@@ -55,6 +55,9 @@
<wood>30</wood>
<metal>20</metal>
</Loot>
<Position>
<TurnRate>2</TurnRate>
</Position>
<ResourceGatherer disable=""/>
<Sound>
<SoundGroups>
@@ -31,6 +31,7 @@
</Loot>
<Position>
<Anchor>pitch-roll</Anchor>
<TurnRate>2</TurnRate>
</Position>
<Repairable>
<RepairTimeRatio>4.0</RepairTimeRatio>
@@ -33,6 +33,7 @@
</Loot>
<Position>
<Anchor>pitch</Anchor>
<TurnRate>2</TurnRate>
</Position>
<Resistance>
<Entity>
@@ -54,9 +54,9 @@
<Obstruction disable=""/>
<Position>
<Anchor>upright</Anchor>
<TurnRate>1.0</TurnRate>
<Floating>true</Floating>
<FloatDepth>0.0</FloatDepth>
<FloatDepth>0</FloatDepth>
<TurnRate>1</TurnRate>
</Position>
<Repairable>
<RepairTimeRatio>3.0</RepairTimeRatio>
+13 -4
View File
@@ -70,7 +70,8 @@ public:
bool m_Floating;
entity_pos_t m_FloatDepth;
float m_RotYSpeed; // maximum radians per second, used by InterpolatedRotY to follow RotY
// Maximum radians per second, used by InterpolatedRotY to follow RotY and the unitMotion.
fixed m_RotYSpeed;
// Dynamic state:
@@ -129,7 +130,7 @@ public:
"<element name='FloatDepth' a:help='The depth at which an entity floats on water (needs Floating to be true)'>"
"<ref name='nonNegativeDecimal'/>"
"</element>"
"<element name='TurnRate' a:help='Maximum graphical rotation speed around Y axis, in radians per second'>"
"<element name='TurnRate' a:help='Maximum rotation speed around Y axis, in radians per second. Used for all graphical rotations and some real unitMotion driven rotations.'>"
"<ref name='positiveDecimal'/>"
"</element>";
}
@@ -154,7 +155,7 @@ public:
m_Floating = paramNode.GetChild("Floating").ToBool();
m_FloatDepth = paramNode.GetChild("FloatDepth").ToFixed();
m_RotYSpeed = paramNode.GetChild("TurnRate").ToFixed().ToFloat();
m_RotYSpeed = paramNode.GetChild("TurnRate").ToFixed();
m_RotX = m_RotY = m_RotZ = entity_angle_t::FromInt(0);
m_InterpolatedRotX = m_InterpolatedRotY = m_InterpolatedRotZ = 0.f;
@@ -189,6 +190,7 @@ public:
serialize.NumberFixed_Unbounded("rot x", m_RotX);
serialize.NumberFixed_Unbounded("rot y", m_RotY);
serialize.NumberFixed_Unbounded("rot z", m_RotZ);
serialize.NumberFixed_Unbounded("rot y speed", m_RotYSpeed);
serialize.NumberFixed_Unbounded("altitude", m_Y);
serialize.Bool("relative", m_RelativeToGround);
serialize.Bool("floating", m_Floating);
@@ -246,6 +248,7 @@ public:
deserialize.NumberFixed_Unbounded("rot x", m_RotX);
deserialize.NumberFixed_Unbounded("rot y", m_RotY);
deserialize.NumberFixed_Unbounded("rot z", m_RotZ);
deserialize.NumberFixed_Unbounded("rot y speed", m_RotYSpeed);
deserialize.NumberFixed_Unbounded("altitude", m_Y);
deserialize.Bool("relative", m_RelativeToGround);
deserialize.Bool("floating", m_Floating);
@@ -536,6 +539,11 @@ public:
return CFixedVector2D(m_PrevX, m_PrevZ);
}
virtual fixed GetTurnRate() const
{
return m_RotYSpeed;
}
virtual void TurnTo(entity_angle_t y)
{
if (m_TurretParent != INVALID_ENTITY)
@@ -780,13 +788,14 @@ public:
if (rotY != m_InterpolatedRotY)
{
float rotYSpeed = m_RotYSpeed.ToFloat();
float delta = rotY - m_InterpolatedRotY;
// Wrap delta to -M_PI..M_PI
delta = fmodf(delta + (float)M_PI, 2*(float)M_PI); // range -2PI..2PI
if (delta < 0) delta += 2*(float)M_PI; // range 0..2PI
delta -= (float)M_PI; // range -M_PI..M_PI
// Clamp to max rate
float deltaClamped = Clamp(delta, -m_RotYSpeed*msgData.deltaSimTime, +m_RotYSpeed*msgData.deltaSimTime);
float deltaClamped = Clamp(delta, -rotYSpeed*msgData.deltaSimTime, +rotYSpeed*msgData.deltaSimTime);
// Calculate new orientation, in a peculiar way in order to make sure the
// result gets close to m_orientation (rather than being n*2*M_PI out)
m_InterpolatedRotY = rotY + deltaClamped - delta;
@@ -616,7 +616,7 @@ private:
* 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, const fixed& turnRate, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, entity_angle_t& angle);
/**
* Update other components on our speed.
@@ -872,28 +872,33 @@ void CCmpUnitMotion::Move(fixed dt)
return;
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
// Keep track of the current unit's position during the update
// Keep track of the current unit's position and rotation during the update.
CFixedVector2D pos = initialPos;
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, cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, pos, angle);
// Update our speed over this turn so that the visual actor shows the correct animation.
if (pos == initialPos)
{
if (angle != initialAngle)
cmpPosition->TurnTo(angle);
UpdateMovementState(fixed::Zero());
}
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 +947,18 @@ bool CCmpUnitMotion::PossiblyAtDestination() const
return false;
}
bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const
bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, 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
@@ -957,26 +968,24 @@ bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath
ENSURE(cmpPathfinder);
fixed basicSpeed = m_Speed;
// If in formation, run to keep up; otherwise just walk
// If in formation, run to keep up; otherwise just walk.
if (IsFormationMember())
basicSpeed = m_Speed.Multiply(m_RunMultiplier);
// Find the speed factor of the underlying terrain
// Find the speed factor of the underlying terrain.
// (We only care about the tile we start on - it doesn't matter if we're moving
// partially onto a much slower/faster tile)
// TODO: Terrain-dependent speeds are not currently supported
// partially onto a much slower/faster tile).
// TODO: Terrain-dependent speeds are not currently supported.
fixed terrainSpeed = fixed::FromInt(1);
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();
while (timeLeft > zero)
{
// If we ran out of path, we have to stop
// If we ran out of path, we have to stop.
if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty())
break;
@@ -987,11 +996,37 @@ bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath
target = CFixedVector2D(shortPath.m_Waypoints.back().x, shortPath.m_Waypoints.back().z);
CFixedVector2D offset = target - pos;
if (turnRate > zero)
{
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;
// Work out how far we can travel in timeLeft
// 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 towards the next waypoint and continue moving.
angle = atan2_approx(offset.X, offset.Y);
timeLeft = (maxRotation - absoluteAngleDiff) / turnRate;
}
}
// Work out how far we can travel in timeLeft.
fixed maxdist = maxSpeed.Multiply(timeLeft);
// If the target is close, we can move there directly
// If the target is close, we can move there directly.
fixed offsetLength = offset.Length();
if (offsetLength <= maxdist)
{
@@ -999,7 +1034,7 @@ bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath
{
pos = target;
// Spend the rest of the time heading towards the next waypoint
// Spend the rest of the time heading towards the next waypoint.
timeLeft = (maxdist - offsetLength) / maxSpeed;
if (shortPath.m_Waypoints.empty())
@@ -1011,13 +1046,13 @@ bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath
}
else
{
// Error - path was obstructed
// Error - path was obstructed.
return true;
}
}
else
{
// Not close enough, so just move in the right direction
// Not close enough, so just move in the right direction.
offset.Normalize(maxdist);
target = pos + offset;
@@ -42,6 +42,7 @@ DEFINE_INTERFACE_METHOD_CONST_0("GetPosition", CFixedVector3D, ICmpPosition, Get
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", fixed, 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)
@@ -184,6 +184,11 @@ public:
*/
virtual CFixedVector2D GetPreviousPosition2D() const = 0;
/**
* Returns the turn rate in radians per second.
*/
virtual fixed GetTurnRate() const = 0;
/**
* Rotate smoothly to the given angle around the upwards axis.
* @param y clockwise radians from the +Z axis.
@@ -62,6 +62,7 @@ public:
virtual CFixedVector2D GetPosition2D() const { return CFixedVector2D(m_Pos.X, m_Pos.Z); }
virtual CFixedVector3D GetPreviousPosition() const { return CFixedVector3D(); }
virtual CFixedVector2D GetPreviousPosition2D() const { return CFixedVector2D(); }
virtual fixed GetTurnRate() const { return fixed::Zero(); }
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)) { }