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
@@ -865,7 +865,7 @@
{
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
- minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed());
+ minSpeed = Math.min(minSpeed, cmpUnitMotion.GetSpeed());
}
minSpeed *= this.GetSpeedMultiplier();
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -596,8 +596,8 @@
let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
ret.speed = {
- "walk": cmpUnitMotion.GetWalkSpeed(),
- "run": cmpUnitMotion.GetRunSpeed()
+ "walk": cmpUnitMotion.GetSpeed(),
+ "run": cmpUnitMotion.GetSpeed() * cmpUnitMotion.GetTopSpeedRatio()
};
return ret;
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
@@ -1,3 +1,5 @@
+const WALKING_SPEED = 1.0
+
function UnitAI() {}
UnitAI.prototype.Schema =
@@ -1391,11 +1393,12 @@
"enter": function () {
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
- if (cmpFormation && cmpVisual)
+/* TOREPLACE if (cmpFormation && cmpVisual)
{
cmpVisual.ReplaceMoveAnimation("walk", cmpFormation.GetFormationAnimation(this.entity, "walk"));
cmpVisual.ReplaceMoveAnimation("run", cmpFormation.GetFormationAnimation(this.entity, "run"));
}
+ */
this.SelectAnimation("move");
},
@@ -1405,13 +1408,13 @@
// We can only finish this order if the move was really completed.
if (!msg.data.error && this.FinishOrder())
return;
- var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
+/* TOREPLACE var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
{
cmpVisual.ResetMoveAnimation("walk");
cmpVisual.ResetMoveAnimation("run");
}
-
+*/
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
if (cmpFormation)
cmpFormation.SetInPosition(this.entity);
@@ -1699,27 +1702,29 @@
},
"leave": function(msg) {
- this.SetMoveSpeed(this.GetWalkSpeed());
+ this.SetMoveSpeed(WALKING_SPEED);
this.StopTimer();
},
"MoveStarted": function(msg) {
// Adapt the speed to the one of the target if needed
- var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
if (cmpUnitMotion.IsInTargetRange(this.isGuardOf, 0, 3*this.guardRange))
{
- var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI);
- if (cmpUnitAI)
+ var cmpOtherMotion = Engine.QueryInterface(this.isGuardOf, IID_UnitMotion);
+ if (cmpOtherMotion)
{
- var speed = cmpUnitAI.GetWalkSpeed();
- if (speed < this.GetWalkSpeed())
+ let otherSpeed = cmpOtherMotion.GetSpeed();
+ let mySpeed = cmpUnitMotion.GetSpeed();
+ let speed = otherSpeed / mySpeed;
+ if (speed < WALKING_SPEED)
this.SetMoveSpeed(speed);
}
}
},
"MoveCompleted": function() {
- this.SetMoveSpeed(this.GetWalkSpeed());
+ this.SetMoveSpeed(WALKING_SPEED);
if (!this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange))
this.SetNextState("GUARDING");
},
@@ -1774,19 +1779,12 @@
this.PlaySound("panic");
// Run quickly
- var speed = this.GetRunSpeed();
- this.SelectAnimation("move");
- this.SetMoveSpeed(speed);
- },
-
- "HealthChanged": function() {
- var speed = this.GetRunSpeed();
- this.SetMoveSpeed(speed);
+ this.SetMoveSpeed(this.GetRunSpeed());
},
"leave": function() {
// Reset normal speed
- this.SetMoveSpeed(this.GetWalkSpeed());
+ this.SetMoveSpeed(WALKING_SPEED);
},
"MoveCompleted": function() {
@@ -2100,12 +2098,9 @@
this.SelectAnimation("move");
var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI);
+ // Run after a fleeing target
if (cmpUnitAI && cmpUnitAI.IsFleeing())
- {
- // Run after a fleeing target
- var speed = this.GetRunSpeed();
- this.SetMoveSpeed(speed);
- }
+ this.SetMoveSpeed(this.GetRunSpeed());
this.StartTimer(1000, 1000);
},
@@ -2113,13 +2108,14 @@
var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI);
if (!cmpUnitAI || !cmpUnitAI.IsFleeing())
return;
- var speed = this.GetRunSpeed();
- this.SetMoveSpeed(speed);
+ // TODO: figure out what to do with fleeing
+ //var speed = this.GetRunSpeed();
+ //this.SetMoveSpeed(speed);
},
"leave": function() {
// Reset normal speed in case it was changed
- this.SetMoveSpeed(this.GetWalkSpeed());
+ this.SetMoveSpeed(WALKING_SPEED);
// Show carried resources when walking.
this.SetGathererAnimationOverride();
@@ -3227,7 +3223,6 @@
"ROAMING": {
"enter": function() {
// Walk in a random direction
- this.SelectAnimation("walk", false, this.GetWalkSpeed());
this.MoveRandomly(+this.template.RoamDistance);
// Set a random timer to switch to feeding state
this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
@@ -4010,12 +4005,14 @@
//// Message handlers /////
-UnitAI.prototype.OnMotionChanged = function(msg)
+UnitAI.prototype.OnBeginMove = function(msg)
+{
+ this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
+};
+
+UnitAI.prototype.OnFinishedMove = function(msg)
{
- if (msg.starting && !msg.error)
- this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
- else if (!msg.starting || msg.error)
- this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
+ this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
};
UnitAI.prototype.OnGlobalConstructionFinished = function(msg)
@@ -4076,22 +4073,10 @@
//// Helper functions to be called by the FSM ////
-UnitAI.prototype.GetWalkSpeed = function()
-{
- var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- return cmpUnitMotion.GetWalkSpeed();
-};
-
UnitAI.prototype.GetRunSpeed = function()
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- var runSpeed = cmpUnitMotion.GetRunSpeed();
- var walkSpeed = cmpUnitMotion.GetWalkSpeed();
- if (runSpeed <= walkSpeed)
- return runSpeed;
- var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
- var health = cmpHealth.GetHitpoints()/cmpHealth.GetMaxHitpoints();
- return (health*runSpeed + (1-health)*walkSpeed);
+ return cmpUnitMotion.GetTopSpeedRatio();
};
/**
@@ -4290,11 +4275,12 @@
// Remove the animation override, so that weapons are shown again.
if (disable)
{
- cmpVisual.ResetMoveAnimation("walk");
+//TOREPLACE cmpVisual.ResetMoveAnimation("walk");
return;
}
// Work out what we're carrying, in order to select an appropriate animation
+ /*
var type = cmpResourceGatherer.GetLastCarriedType();
if (type)
{
@@ -4304,10 +4290,11 @@
if (type.specific == "meat")
typename = "carry_" + type.specific;
- cmpVisual.ReplaceMoveAnimation("walk", typename);
+// TOREPLACE cmpVisual.ReplaceMoveAnimation("walk", typename);
}
else
- cmpVisual.ResetMoveAnimation("walk");
+ cmpVisual.ResetMoveAnimation("idle");
+ */
};
UnitAI.prototype.SelectAnimation = function(name, once, speed, sound)
@@ -4316,17 +4303,6 @@
if (!cmpVisual)
return;
- // Special case: the "move" animation gets turned into a special
- // movement mode that deals with speeds and walk/run automatically
- if (name == "move")
- {
- // Speed to switch from walking to running animations
- var runThreshold = (this.GetWalkSpeed() + this.GetRunSpeed()) / 2;
-
- cmpVisual.SelectMovementAnimation(runThreshold);
- return;
- }
-
var soundgroup;
if (sound)
{
@@ -4359,18 +4335,20 @@
UnitAI.prototype.StopMoving = function()
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- cmpUnitMotion.StopMoving();
+ cmpUnitMotion.DiscardMove();
};
UnitAI.prototype.MoveToPoint = function(x, z)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(3);
return cmpUnitMotion.MoveToPointRange(x, z, 0, 0);
};
UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(3);
return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
};
@@ -4380,6 +4358,7 @@
return false;
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(5);
return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
};
@@ -4394,6 +4373,7 @@
var range = cmpRanged.GetRange(type);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(5);
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
@@ -4450,6 +4430,7 @@
var guessedMaxRange = (range.max + parabolicMaxRange)/2;
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(9);
if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange))
return true;
@@ -4463,6 +4444,7 @@
return false;
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(5);
return cmpUnitMotion.MoveToTargetRange(target, min, max);
};
@@ -4477,6 +4459,7 @@
var range = cmpGarrisonHolder.GetLoadingRange();
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+ cmpUnitMotion.SetAbortIfStuck(5);
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
@@ -5694,7 +5677,7 @@
UnitAI.prototype.SetMoveSpeed = function(speed)
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
- cmpMotion.SetSpeed(speed);
+ cmpMotion.SetSpeed(1.0);
};
UnitAI.prototype.SetHeldPosition = function(x, z)
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
@@ -295,7 +295,7 @@
return this.IsInPointRange(targetPos.x, targetPos.y, minRange, maxRange);
};
-UnitMotionFlying.prototype.GetWalkSpeed = function()
+UnitMotionFlying.prototype.GetSpeed = function()
{
return +this.template.MaxSpeed;
};
Index: binaries/data/mods/public/simulation/data/pathfinder.xml
===================================================================
--- binaries/data/mods/public/simulation/data/pathfinder.xml
+++ binaries/data/mods/public/simulation/data/pathfinder.xml
@@ -11,7 +11,7 @@
pathfinding
2
1.0
- 0.8
+ 0.7
pathfinding
Index: binaries/data/mods/public/simulation/templates/gaia/fauna_hawk.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/gaia/fauna_hawk.xml
+++ binaries/data/mods/public/simulation/templates/gaia/fauna_hawk.xml
@@ -6,11 +6,9 @@
false
1.0
-
- 1000.0
-
+
-
+
fauna/hawk.xml
Index: source/simulation2/MessageTypes.h
===================================================================
--- source/simulation2/MessageTypes.h
+++ source/simulation2/MessageTypes.h
@@ -317,21 +317,55 @@
};
/**
- * Sent by CCmpUnitMotion during Update, whenever the motion status has changed
- * since the previous update.
+ * Sent by CCmpUnitMotion during Update,
+ * whenever we have started actually moving and were not moving before.
+ * We may or may not already have been trying to move
*/
-class CMessageMotionChanged : public CMessage
+class CMessageBeginMove : public CMessage
{
public:
- DEFAULT_MESSAGE_IMPL(MotionChanged)
+ DEFAULT_MESSAGE_IMPL(BeginMove)
- CMessageMotionChanged(bool starting, bool error) :
- starting(starting), error(error)
+ CMessageBeginMove()
+ {
+ }
+};
+
+/**
+ * Sent by CCmpUnitMotion during Update,
+ * whenever we were actually moving before, and cannot continue
+ * this can be because we've arrived (failed=false) or we failed moving (failed=true)
+ * After this message is sent, the unit won't remove/repath without orders.
+ * Will never be sent on the same turn as MT_BeginMove.
+ */
+class CMessageFinishedMove : public CMessage
+{
+public:
+ DEFAULT_MESSAGE_IMPL(FinishedMove)
+
+ CMessageFinishedMove(bool fail) : failed(fail)
{
}
- bool starting; // whether this is a start or end of movement
- bool error; // whether we failed to start moving (couldn't find any path)
+ bool failed; // move failed
+};
+
+/**
+ * Sent by CCmpUnitMotion during Update,
+ * whenever we were actually moving before, and now stopped
+ * In this case, we will retry moving/pathing in the future on our own
+ * Unless ordered otherwise.
+ * We are just possibly stuck short-term, or must repath.
+ * Will never be sent on the same turn as MT_BeginMove.
+ */
+class CMessagePausedMove : public CMessage
+{
+public:
+ DEFAULT_MESSAGE_IMPL(PausedMove)
+
+ CMessagePausedMove()
+ {
+ }
};
/**
Index: source/simulation2/TypeList.h
===================================================================
--- source/simulation2/TypeList.h
+++ source/simulation2/TypeList.h
@@ -45,7 +45,9 @@
MESSAGE(PositionChanged)
MESSAGE(InterpolatedPositionChanged)
MESSAGE(TerritoryPositionChanged)
-MESSAGE(MotionChanged)
+MESSAGE(BeginMove)
+MESSAGE(FinishedMove)
+MESSAGE(PausedMove)
MESSAGE(RangeUpdate)
MESSAGE(TerrainChanged)
MESSAGE(VisibilityChanged)
Index: source/simulation2/components/CCmpObstructionManager.cpp
===================================================================
--- source/simulation2/components/CCmpObstructionManager.cpp
+++ source/simulation2/components/CCmpObstructionManager.cpp
@@ -20,6 +20,7 @@
#include "simulation2/system/Component.h"
#include "ICmpObstructionManager.h"
+#include "ICmpPosition.h"
#include "ICmpTerrain.h"
#include "simulation2/MessageTypes.h"
@@ -465,6 +466,9 @@
}
}
+ virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange);
+ virtual bool IsInTargetRange(entity_pos_t x, entity_pos_t z, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
+
virtual bool TestLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, bool relaxClearanceForUnits = false);
virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector* out);
virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector* out);
@@ -658,6 +662,83 @@
REGISTER_COMPONENT_TYPE(ObstructionManager)
+bool CCmpObstructionManager::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange)
+{
+ CFixedVector2D pos(x, z);
+
+ entity_pos_t distance = (pos - CFixedVector2D(px, pz)).Length();
+
+ if (distance < minRange)
+ return false;
+ else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
+ return false;
+ else
+ return true;
+}
+
+bool CCmpObstructionManager::IsInTargetRange(entity_pos_t x, entity_pos_t z, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
+{
+ CFixedVector2D pos(x, z);
+
+ CmpPtr cmpObstructionManager(GetSystemEntity());
+ if (!cmpObstructionManager)
+ return false;
+
+ bool hasObstruction = false;
+ ICmpObstructionManager::ObstructionSquare obstruction;
+ CmpPtr cmpObstruction(GetSimContext(), target);
+ if (cmpObstruction)
+ hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
+
+ if (hasObstruction)
+ {
+ CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
+ entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize, true);
+
+ // Compare with previous obstruction
+ ICmpObstructionManager::ObstructionSquare previousObstruction;
+ cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
+ entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize, true);
+
+ // See if we're too close to the target square
+ bool inside = distance.IsZero() && !Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize).IsZero();
+ if ((distance < minRange && previousDistance < minRange) || inside)
+ return false;
+
+ // See if we're close enough to the target square
+ if (maxRange < entity_pos_t::Zero() || distance <= maxRange || previousDistance <= maxRange)
+ return true;
+
+ entity_pos_t circleRadius = halfSize.Length();
+
+ if (Geometry::ShouldTreatTargetAsCircle(maxRange, circleRadius))
+ {
+ // The target is small relative to our range, so pretend it's a circle
+ // and see if we're close enough to that.
+ // Also check circle around previous position.
+ entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
+ entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
+
+ return circleDistance <= maxRange || previousCircleDistance <= maxRange;
+ }
+
+ // take minimal clearance required in MoveToTargetRange into account, multiplying by 3/2 for diagonals
+ return distance <= maxRange || previousDistance <= maxRange;
+ }
+ else
+ {
+ CmpPtr cmpTargetPosition(GetSimContext(), target);
+ if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
+ return false;
+
+ CFixedVector2D targetPos = cmpTargetPosition->GetPreviousPosition2D();
+ entity_pos_t distance = (pos - targetPos).Length();
+
+ return minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange);
+ }
+}
+
+
bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, bool relaxClearanceForUnits)
{
PROFILE("TestLine");
@@ -1084,7 +1165,7 @@
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = defaultColor;
float a = atan2f(it->second.v.X.ToFloat(), it->second.v.Y.ToFloat());
- SimRender::ConstructSquareOnGround(GetSimContext(), it->second.x.ToFloat(), it->second.z.ToFloat(), it->second.hw.ToFloat()*2, it->second.hh.ToFloat()*2, a, m_DebugOverlayLines.back(), true);
+ SimRender::ConstructSquareOnGround(GetSimContext(), it->second.x.ToFloat(), it->second.z.ToFloat(), it->second.hw.ToFloat()*2 + 1, it->second.hh.ToFloat()*2 + 1, a, m_DebugOverlayLines.back(), true);
}
m_DebugOverlayDirty = false;
Index: source/simulation2/components/CCmpPathfinder_Vertex.cpp
===================================================================
--- source/simulation2/components/CCmpPathfinder_Vertex.cpp
+++ source/simulation2/components/CCmpPathfinder_Vertex.cpp
@@ -654,8 +654,6 @@
edges.emplace_back(Edge{ ev3, ev0 });
}
- // TODO: should clip out vertexes and edges that are outside the range,
- // to reduce the search space
}
// Add terrain obstructions
Index: source/simulation2/components/CCmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/CCmpUnitMotion.cpp
+++ source/simulation2/components/CCmpUnitMotion.cpp
@@ -27,6 +27,7 @@
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpValueModificationManager.h"
+#include "simulation2/components/ICmpVisual.h"
#include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/MessageTypes.h"
@@ -106,15 +107,104 @@
*/
static const fixed CHECK_TARGET_MOVEMENT_MIN_COS = fixed::FromInt(866)/1000;
+/**
+ * See unitmotion logic for details. Higher means units will retry more often before potentially failing.
+ */
+static const size_t MAX_PATH_REATTEMPS = 8;
+
static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1);
static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1);
class CCmpUnitMotion : public ICmpUnitMotion
{
+private:
+ struct SMotionGoal
+ {
+ private:
+ bool m_Valid = false;
+
+ entity_pos_t m_TargetMinRange;
+ entity_pos_t m_TargetMaxRange;
+
+ entity_id_t m_TargetEntity;
+ // pathfinder-compliant goal.
+ PathGoal m_Goal;
+ public:
+ SMotionGoal() : m_Valid(false) {};
+
+ SMotionGoal(PathGoal& goal, entity_pos_t minRange, entity_pos_t maxRange)
+ {
+ m_TargetEntity = INVALID_ENTITY;
+
+ m_TargetMinRange = minRange;
+ m_TargetMaxRange = maxRange;
+
+ m_Goal = goal;
+ m_Valid = true;
+ }
+
+ SMotionGoal(const CSimContext& context, entity_id_t target, PathGoal& goal, entity_pos_t minRange, entity_pos_t maxRange)
+ {
+ m_TargetEntity = target;
+ m_TargetMinRange = minRange;
+ m_TargetMaxRange = maxRange;
+
+ m_Goal = goal;
+ m_Valid = true;
+
+ UpdateTargetPosition(context);
+ }
+
+ template
+ void SerializeCommon(S& serialize)
+ {
+ serialize.Bool("valid", m_Valid);
+
+ serialize.NumberFixed_Unbounded("target min range", m_TargetMinRange);
+ serialize.NumberFixed_Unbounded("target max range", m_TargetMaxRange);
+
+ serialize.NumberU32_Unbounded("target entity", m_TargetEntity);
+
+ SerializeGoal()(serialize, "goal", m_Goal);
+ }
+
+ const PathGoal& Goal() const { return m_Goal; };
+
+ bool TargetIsEntity() const { return m_TargetEntity != INVALID_ENTITY; }
+ entity_id_t GetEntity() const { return m_TargetEntity; }
+
+ bool Valid() const { return m_Valid; }
+ void Clear() { m_Valid = false; }
+
+ entity_pos_t MinRange() const { return m_TargetMinRange; };
+ entity_pos_t MaxRange() const { return m_TargetMaxRange; };
+
+ CFixedVector2D Pos() const { return CFixedVector2D(m_Goal.x, m_Goal.z); }
+ entity_pos_t X() const { return m_Goal.x; }
+ entity_pos_t Z() const { return m_Goal.z; }
+
+ void UpdateTargetPosition(const CSimContext& context)
+ {
+ if (!TargetIsEntity())
+ return;
+
+ CmpPtr cmpPosition(context, m_TargetEntity);
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return;
+
+ m_Goal.x = cmpPosition->GetPosition2D().X;
+ m_Goal.z = cmpPosition->GetPosition2D().Y;
+ }
+
+ bool IsNotAPoint() const
+ {
+ return m_TargetMaxRange > fixed::Zero() || m_Goal.type != PathGoal::POINT;
+ }
+ };
+
public:
static void ClassInit(CComponentManager& componentManager)
{
- componentManager.SubscribeToMessageType(MT_Update_MotionFormation);
componentManager.SubscribeToMessageType(MT_Update_MotionUnit);
componentManager.SubscribeToMessageType(MT_PathResult);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
@@ -128,121 +218,43 @@
std::vector m_DebugOverlayLongPathLines;
std::vector m_DebugOverlayShortPathLines;
- // Template state:
-
- bool m_FormationController;
- fixed m_WalkSpeed, m_OriginalWalkSpeed; // in metres per second
- fixed m_RunSpeed, m_OriginalRunSpeed;
+ // Template state, never changed after init.
+ fixed m_TemplateSpeed, m_TopSpeedRatio;
pass_class_t m_PassClass;
std::string m_PassClassName;
-
- // Dynamic state:
-
entity_pos_t m_Clearance;
- bool m_Moving;
- bool m_FacePointAfterMove;
-
- enum State
- {
- /*
- * Not moving at all.
- */
- STATE_IDLE,
-
- /*
- * Not moving at all. Will go to IDLE next turn.
- * (This one-turn delay is a hack to fix animation timings.)
- */
- STATE_STOPPING,
-
- /*
- * Member of a formation.
- * Pathing to the target (depending on m_PathState).
- * Target is m_TargetEntity plus m_TargetOffset.
- */
- STATE_FORMATIONMEMBER_PATH,
-
- /*
- * Individual unit or formation controller.
- * Pathing to the target (depending on m_PathState).
- * Target is m_TargetPos, m_TargetMinRange, m_TargetMaxRange;
- * if m_TargetEntity is not INVALID_ENTITY then m_TargetPos is tracking it.
- */
- STATE_INDIVIDUAL_PATH,
-
- STATE_MAX
- };
- u8 m_State;
-
- enum PathState
- {
- /*
- * There is no path.
- * (This should only happen in IDLE and STOPPING.)
- */
- PATHSTATE_NONE,
-
- /*
- * We have an outstanding long path request.
- * No paths are usable yet, so we can't move anywhere.
- */
- PATHSTATE_WAITING_REQUESTING_LONG,
-
- /*
- * We have an outstanding short path request.
- * m_LongPath is valid.
- * m_ShortPath is not yet valid, so we can't move anywhere.
- */
- PATHSTATE_WAITING_REQUESTING_SHORT,
-
- /*
- * We are following our path, and have no path requests.
- * m_LongPath and m_ShortPath are valid.
- */
- PATHSTATE_FOLLOWING,
-
- /*
- * We are following our path, and have an outstanding long path request.
- * (This is because our target moved a long way and we need to recompute
- * the whole path).
- * m_LongPath and m_ShortPath are valid.
- */
- PATHSTATE_FOLLOWING_REQUESTING_LONG,
-
- /*
- * We are following our path, and have an outstanding short path request.
- * (This is because our target moved and we've got a new long path
- * which we need to follow).
- * m_LongPath is valid; m_ShortPath is valid but obsolete.
- */
- PATHSTATE_FOLLOWING_REQUESTING_SHORT,
-
- PATHSTATE_MAX
- };
- u8 m_PathState;
-
- u32 m_ExpectedPathTicket; // asynchronous request ID we're waiting for, or 0 if none
-
- entity_id_t m_TargetEntity;
- CFixedVector2D m_TargetPos;
- CFixedVector2D m_TargetOffset;
- entity_pos_t m_TargetMinRange;
- entity_pos_t m_TargetMaxRange;
+ // TARGET
+ // As long as we have a valid target, the unit is considered "on the move".
+ // It may not be actually moving for a variety of reasons (no path, blocked path)… but it will shortly.
+ SMotionGoal m_FinalGoal;
+
+ // MOTION PLANNING
+ // We will abort if we are stuck after X tries.
+ u8 m_AbortIfStuck;
+ // turn towards our target at the end
+ bool m_FacePointAfterMove;
+ // actual unit speed, after technology and ratio
fixed m_Speed;
+ // cached for convenience
+ fixed m_SpeedRatio;
- // Current mean speed (over the last turn).
- fixed m_CurSpeed;
+ // asynchronous request ID we're waiting for, or 0 if none
+ u32 m_ExpectedPathTicket;
// Currently active paths (storing waypoints in reverse order).
// The last item in each path is the point we're currently heading towards.
- WaypointPath m_LongPath;
- WaypointPath m_ShortPath;
-
- // Motion planning
- u8 m_Tries; // how many tries we've done to get to our current Final Goal.
-
- PathGoal m_FinalGoal;
+ WaypointPath m_Path;
+ // used for the short pathfinder, incremented on each unsuccessful try.
+ u8 m_Tries;
+ // Turns to wait before a certain action.
+ u8 m_WaitingTurns;
+ // if we actually started moving at some point.
+ bool m_StartedMoving;
+
+ // Speed over the last turn
+ // cached so we can tell the visual actor when it changes
+ fixed m_ActualSpeed;
static std::string GetSchema()
{
@@ -259,6 +271,10 @@
""
""
""
+ ""
+ ""
+ ""
+ ""
""
""
""
@@ -276,19 +292,15 @@
virtual void Init(const CParamNode& paramNode)
{
- m_FormationController = paramNode.GetChild("FormationController").ToBool();
-
- m_Moving = false;
m_FacePointAfterMove = true;
- m_WalkSpeed = m_OriginalWalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
- m_Speed = m_WalkSpeed;
- m_CurSpeed = fixed::Zero();
-
- if (paramNode.GetChild("Run").IsOk())
- m_RunSpeed = m_OriginalRunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed();
- else
- m_RunSpeed = m_OriginalRunSpeed = m_WalkSpeed;
+ m_TemplateSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
+ m_ActualSpeed = fixed::Zero();
+ m_SpeedRatio = fixed::FromInt(1);
+
+ m_TopSpeedRatio = fixed::FromInt(1);
+ if (paramNode.GetChild("RunMultiplier").IsOk())
+ m_TopSpeedRatio = paramNode.GetChild("WalkSpeed").ToFixed();
CmpPtr cmpPathfinder(GetSystemEntity());
if (cmpPathfinder)
@@ -302,18 +314,13 @@
cmpObstruction->SetUnitClearance(m_Clearance);
}
- m_State = STATE_IDLE;
- m_PathState = PATHSTATE_NONE;
-
m_ExpectedPathTicket = 0;
m_Tries = 0;
-
- m_TargetEntity = INVALID_ENTITY;
-
- m_FinalGoal.type = PathGoal::POINT;
+ m_WaitingTurns = 0;
m_DebugOverlayEnabled = false;
+ m_AbortIfStuck = 0;
}
virtual void Deinit()
@@ -323,33 +330,24 @@
template
void SerializeCommon(S& serialize)
{
- serialize.NumberU8("state", m_State, 0, STATE_MAX-1);
- serialize.NumberU8("path state", m_PathState, 0, PATHSTATE_MAX-1);
-
- serialize.StringASCII("pass class", m_PassClassName, 0, 64);
+ serialize.NumberU8("abort if stuck", m_AbortIfStuck, 0, 255);
+ serialize.Bool("face point after move", m_FacePointAfterMove);
+ serialize.NumberFixed_Unbounded("speed", m_Speed);
+ serialize.NumberFixed_Unbounded("speed ratio", m_SpeedRatio);
serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket);
- serialize.NumberU32_Unbounded("target entity", m_TargetEntity);
- serialize.NumberFixed_Unbounded("target pos x", m_TargetPos.X);
- serialize.NumberFixed_Unbounded("target pos y", m_TargetPos.Y);
- serialize.NumberFixed_Unbounded("target offset x", m_TargetOffset.X);
- serialize.NumberFixed_Unbounded("target offset y", m_TargetOffset.Y);
- serialize.NumberFixed_Unbounded("target min range", m_TargetMinRange);
- serialize.NumberFixed_Unbounded("target max range", m_TargetMaxRange);
-
- serialize.NumberFixed_Unbounded("speed", m_Speed);
- serialize.NumberFixed_Unbounded("current speed", m_CurSpeed);
-
- serialize.Bool("moving", m_Moving);
- serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
+ SerializeVector()(serialize, "path", m_Path.m_Waypoints);
serialize.NumberU8("tries", m_Tries, 0, 255);
+ serialize.NumberU8("waiting turns", m_WaitingTurns, 0, 255);
+
+ serialize.Bool("started moving", m_StartedMoving);
- SerializeVector()(serialize, "long path", m_LongPath.m_Waypoints);
- SerializeVector()(serialize, "short path", m_ShortPath.m_Waypoints);
+ // strictly speaking this doesn't need to be serialized since it's graphics-only, but it's nicer to.
+ serialize.NumberFixed_Unbounded("actual speed", m_ActualSpeed);
- SerializeGoal()(serialize, "goal", m_FinalGoal);
+ m_FinalGoal.SerializeCommon(serialize);
}
virtual void Serialize(ISerializer& serialize)
@@ -372,22 +370,10 @@
{
switch (msg.GetType())
{
- case MT_Update_MotionFormation:
- {
- if (m_FormationController)
- {
- fixed dt = static_cast (msg).turnLength;
- Move(dt);
- }
- break;
- }
case MT_Update_MotionUnit:
{
- if (!m_FormationController)
- {
- fixed dt = static_cast (msg).turnLength;
- Move(dt);
- }
+ fixed dt = static_cast (msg).turnLength;
+ Move(dt);
break;
}
case MT_RenderSubmit:
@@ -410,24 +396,22 @@
break;
}
// fall-through
- case MT_OwnershipChanged:
case MT_Deserialized:
{
+ // tell the visual actor our speed.
+ // don't call setactualspeed since the if check will return immediately.
+ CmpPtr cmpVisualActor(GetEntityHandle());
+ if (cmpVisualActor)
+ cmpVisualActor->SetMovingSpeed(m_ActualSpeed);
+ }
+ case MT_OwnershipChanged:
+ {
CmpPtr cmpValueModificationManager(GetSystemEntity());
if (!cmpValueModificationManager)
break;
- fixed newWalkSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_OriginalWalkSpeed, GetEntityId());
- fixed newRunSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/Run/Speed", m_OriginalRunSpeed, GetEntityId());
+ m_Speed = m_SpeedRatio.Multiply(cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateSpeed, GetEntityId()));
- // update m_Speed (the actual speed) if set to one of the variables
- if (m_Speed == m_WalkSpeed)
- m_Speed = newWalkSpeed;
- else if (m_Speed == m_RunSpeed)
- m_Speed = newRunSpeed;
-
- m_WalkSpeed = newWalkSpeed;
- m_RunSpeed = newRunSpeed;
break;
}
}
@@ -439,19 +423,62 @@
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
}
- virtual bool IsMoving()
+ virtual bool IsActuallyMoving()
+ {
+ return m_StartedMoving;
+ }
+
+ virtual bool IsTryingToMove()
+ {
+ // speed check as sanity check to avoid infinite loops.
+ return m_FinalGoal.Valid() && m_Speed > fixed::Zero();
+ }
+
+ virtual fixed GetTemplateSpeed()
{
- return m_Moving;
+ return m_TemplateSpeed;
}
- virtual fixed GetWalkSpeed()
+ virtual fixed GetSpeed()
{
- return m_WalkSpeed;
+ return m_Speed;
}
- virtual fixed GetRunSpeed()
+ virtual fixed GetSpeedRatio()
{
- return m_RunSpeed;
+ return m_SpeedRatio;
+ }
+
+ virtual fixed GetTopSpeedRatio()
+ {
+ return m_TopSpeedRatio;
+ }
+
+ // don't call this all the time
+ // it's voluntarily too slow, because you shouldn't be doing this.
+ virtual void SetSpeed(fixed ratio)
+ {
+ m_SpeedRatio = ratio;
+ CmpPtr cmpValueModificationManager(GetSystemEntity());
+ if (cmpValueModificationManager)
+ {
+ m_Speed = m_SpeedRatio.Multiply(cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateSpeed, GetEntityId()));
+ return;
+ }
+
+ m_Speed = m_SpeedRatio.Multiply(m_TemplateSpeed);
+ }
+
+ // convenience wrapper
+ void SetActualSpeed(fixed newRealSpeed)
+ {
+ if (m_ActualSpeed == newRealSpeed)
+ return;
+
+ m_ActualSpeed = newRealSpeed;
+ CmpPtr cmpVisualActor(GetEntityHandle());
+ if (cmpVisualActor)
+ cmpVisualActor->SetMovingSpeed(m_ActualSpeed);
}
virtual pass_class_t GetPassabilityClass()
@@ -472,16 +499,6 @@
m_PassClass = cmpPathfinder->GetPassabilityClass(passClassName);
}
- virtual fixed GetCurrentSpeed()
- {
- return m_CurSpeed;
- }
-
- virtual void SetSpeed(fixed speed)
- {
- m_Speed = speed;
- }
-
virtual void SetFacePointAfterMove(bool facePointAfterMove)
{
m_FacePointAfterMove = facePointAfterMove;
@@ -493,101 +510,108 @@
UpdateMessageSubscriptions();
}
+ virtual entity_pos_t GetUnitClearance()
+ {
+ return m_Clearance;
+ }
+
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
- virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z);
- virtual void StopMoving()
+ virtual void SetAbortIfStuck(u8 shouldAbort)
{
- m_Moving = false;
- m_ExpectedPathTicket = 0;
- m_State = STATE_STOPPING;
- m_PathState = PATHSTATE_NONE;
- m_LongPath.m_Waypoints.clear();
- m_ShortPath.m_Waypoints.clear();
+ m_AbortIfStuck = shouldAbort;
}
- virtual entity_pos_t GetUnitClearance()
+ virtual void DiscardMove()
{
- return m_Clearance;
+ StopMovingQuietly();
}
-private:
- bool ShouldAvoidMovingUnits() const
+ // stop moving and send message
+ virtual void CompleteMove()
{
- return !m_FormationController;
+ // highlight bugs.
+ if (!IsTryingToMove())
+ {
+ LOGERROR("Entity %i trying to stop moving but has not actually started", GetEntityId());
+ return;
+ }
+
+ StopMovingQuietly();
+
+ if (m_FacePointAfterMove)
+ {
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (cmpPosition && cmpPosition->IsInWorld())
+ FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), m_FinalGoal.X(), m_FinalGoal.Z());
+ }
+
+ CMessageFinishedMove msg(false);
+ GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
+private:
+/*
+ TODO: reimplement
bool IsFormationMember() const
{
return m_State == STATE_FORMATIONMEMBER_PATH;
}
-
+*/
entity_id_t GetGroup() const
{
- return IsFormationMember() ? m_TargetEntity : GetEntityId();
+ //return IsFormationMember() ? m_TargetEntity : GetEntityId();
+ return GetEntityId();
}
bool HasValidPath() const
{
- return m_PathState == PATHSTATE_FOLLOWING
- || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG
- || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT;
+ return !m_Path.m_Waypoints.empty();
}
- void StartFailed()
+ void StopMovingQuietly()
{
- StopMoving();
- m_State = STATE_IDLE; // don't go through the STOPPING state since we never even started
+ // sanity
+ m_Tries = 0;
+ m_WaitingTurns = 0;
+ m_StartedMoving = false;
+
+ // reset state.
+ m_ExpectedPathTicket = 0;
+ m_FinalGoal.Clear();
+ m_Path.m_Waypoints.clear();
CmpPtr cmpObstruction(GetEntityHandle());
if (cmpObstruction)
cmpObstruction->SetMovingFlag(false);
-
- CMessageMotionChanged msg(true, true);
- GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
void MoveFailed()
{
- StopMoving();
+ StopMovingQuietly();
- CmpPtr cmpObstruction(GetEntityHandle());
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(false);
-
- CMessageMotionChanged msg(false, true);
+ CMessageFinishedMove msg(true);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
- void StartSucceeded()
+ void MovePaused()
{
- CmpPtr cmpObstruction(GetEntityHandle());
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(true);
+ m_StartedMoving = false;
- m_Moving = true;
-
- CMessageMotionChanged msg(true, false);
+ CMessagePausedMove msg;
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
- void MoveSucceeded()
+ void MoveStarted()
{
- m_Moving = false;
+ m_StartedMoving = true;
- CmpPtr cmpObstruction(GetEntityHandle());
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(false);
-
- // No longer moving, so speed is 0.
- m_CurSpeed = fixed::Zero();
-
- CMessageMotionChanged msg(false, false);
+ CMessageBeginMove msg;
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
@@ -604,45 +628,16 @@
void Move(fixed dt);
/**
- * Decide whether to approximate the given range from a square target as a circle,
- * rather than as a square.
- */
- bool ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const;
-
- /**
- * Computes the current location of our target entity (plus offset).
- * Returns false if no target entity or no valid position.
- */
- bool ComputeTargetPosition(CFixedVector2D& out);
-
- /**
- * Attempts to replace the current path with a straight line to the goal,
- * if this goal is a point, is close enough and the route is not obstructed.
- */
- bool TryGoingStraightToGoalPoint(const CFixedVector2D& from);
-
- /**
- * Attempts to replace the current path with a straight line to the target
- * entity, if it's close enough and the route is not obstructed.
- */
- bool TryGoingStraightToTargetEntity(const CFixedVector2D& from);
-
- /**
* Returns whether the target entity has moved more than minDelta since our
* last path computations, and we're close enough to it to care.
*/
bool CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta);
/**
- * Update goal position if moving target
- */
- void UpdateFinalGoal();
-
- /**
* Returns whether we are close enough to the target to assume it's a good enough
* position to stop.
*/
- bool ShouldConsiderOurselvesAtDestination(const CFixedVector2D& from);
+ bool ShouldConsiderOurselvesAtDestination();
/**
* Returns whether the length of the given path, plus the distance from
@@ -663,11 +658,11 @@
ControlGroupMovementObstructionFilter GetObstructionFilter(bool noTarget = false) const;
/**
- * Start moving to the given goal, from our current position 'from'.
+ * Dump current paths and request a new one.
* Might go in a straight line immediately, or might start an asynchronous
* path request.
*/
- void BeginPathing(const CFixedVector2D& from, const PathGoal& goal);
+ void RequestNewPath();
/**
* Start an asynchronous long path query.
@@ -691,489 +686,306 @@
void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
{
- // reset our state for sanity.
- CmpPtr cmpObstruction(GetEntityHandle());
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(false);
-
- m_Moving = false;
-
// Ignore obsolete path requests
if (ticket != m_ExpectedPathTicket)
return;
m_ExpectedPathTicket = 0; // we don't expect to get this result again
- // Check that we are still able to do something with that path
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- {
- if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT)
- StartFailed();
- else if (m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT)
- StopMoving();
+ if (!m_FinalGoal.Valid())
return;
- }
- if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG)
+ if (path.m_Waypoints.empty())
{
- m_LongPath = path;
-
- // If we are following a path, leave the old m_ShortPath so we can carry on following it
- // until a new short path has been computed
- if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG)
- m_ShortPath.m_Waypoints.clear();
-
- // If there's no waypoints then we couldn't get near the target.
- // Sort of hack: Just try going directly to the goal point instead
- // (via the short pathfinder), so if we're stuck and the user clicks
- // close enough to the unit then we can probably get unstuck
- if (m_LongPath.m_Waypoints.empty())
- m_LongPath.m_Waypoints.emplace_back(Waypoint{ m_FinalGoal.x, m_FinalGoal.z });
-
- if (!HasValidPath())
- StartSucceeded();
-
- m_PathState = PATHSTATE_FOLLOWING;
-
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(true);
+ // no waypoints, path failed.
+ // if we have some room, pop waypoint
+ // TODO: this isn't particularly bright.
+ if (!m_Path.m_Waypoints.empty())
+ m_Path.m_Waypoints.pop_back();
- m_Moving = true;
+ // we will then deal with this on the next Move() call.
+ return;
}
- else if (m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT)
- {
- m_ShortPath = path;
-
- // If there's no waypoints then we couldn't get near the target
- if (m_ShortPath.m_Waypoints.empty())
- {
- // If we're globally following a long path, try to remove the next waypoint, it might be obstructed
- // If not, and we are not in a formation, retry
- // unless we are close to our target and we don't have a target entity.
- // This makes sure that units don't clump too much when they are not in a formation and tasked to move.
- if (m_LongPath.m_Waypoints.size() > 1)
- m_LongPath.m_Waypoints.pop_back();
- else if (IsFormationMember())
- {
- m_Moving = false;
- CMessageMotionChanged msg(true, true);
- GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
- return;
- }
-
- CMessageMotionChanged msg(false, false);
- GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
-
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return;
-
- CFixedVector2D pos = cmpPosition->GetPosition2D();
- if (ShouldConsiderOurselvesAtDestination(pos))
- return;
-
- UpdateFinalGoal();
- RequestLongPath(pos, m_FinalGoal);
- m_PathState = PATHSTATE_WAITING_REQUESTING_LONG;
- return;
- }
-
- // else we could, so reset our number of tries.
- m_Tries = 0;
-
- // Now we've got a short path that we can follow
- if (!HasValidPath())
- StartSucceeded();
-
- m_PathState = PATHSTATE_FOLLOWING;
-
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(true);
-
- m_Moving = true;
- }
- else
- LOGWARNING("unexpected PathResult (%u %d %d)", GetEntityId(), m_State, m_PathState);
+ // add to the top of our current waypoints
+ m_Path.m_Waypoints.insert(m_Path.m_Waypoints.end(), path.m_Waypoints.begin(), path.m_Waypoints.end());
}
void CCmpUnitMotion::Move(fixed dt)
{
PROFILE("Move");
- if (m_State == STATE_STOPPING)
+ /**
+ * TODO: the visual actor doesn't interpolate, it merely changes things on update
+ * This means if a unit wants to change animation between turns (because it stops…)
+ * It will look slightly glitchy for a very short while
+ */
+
+ if (!IsTryingToMove())
{
- m_State = STATE_IDLE;
- MoveSucceeded();
+ SetActualSpeed(fixed::Zero());
return;
}
- if (m_State == STATE_IDLE)
+ m_FinalGoal.UpdateTargetPosition(GetSimContext());
+
+ // TODO: units will look at each other's position in an arbitrary order that must be the same for any simulation
+ // In particular this means no threading. Maybe we should update this someday if it's possible.
+
+ CmpPtr cmpPathfinder(GetSystemEntity());
+ if (!cmpPathfinder)
return;
- switch (m_PathState)
- {
- case PATHSTATE_NONE:
- {
- // If we're not pathing, do nothing
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
return;
- }
- case PATHSTATE_WAITING_REQUESTING_LONG:
- case PATHSTATE_WAITING_REQUESTING_SHORT:
+ CFixedVector2D initialPos = cmpPosition->GetPosition2D();
+
+ // Preliminary check: our target may be an entity and may have moved before us
+ if (ShouldConsiderOurselvesAtDestination())
{
- // If we're waiting for a path and don't have one yet, do nothing
+ CompleteMove();
return;
}
- case PATHSTATE_FOLLOWING:
- case PATHSTATE_FOLLOWING_REQUESTING_SHORT:
- case PATHSTATE_FOLLOWING_REQUESTING_LONG:
- {
- // 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
- // that problem.
+ // TODO: here should go things such as:
+ // - has our target moved enough that we should re-path?
+ // end TODO
- CmpPtr cmpPathfinder(GetSystemEntity());
- if (!cmpPathfinder)
- return;
+ // Keep track of the current unit's position during the update
+ CFixedVector2D pos = initialPos;
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return;
+ // 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
+ // TODO: note that this is also linked to pathfinding so maybe never supported
+ // fixed terrainSpeed = fixed::FromInt(1);
- CFixedVector2D initialPos = cmpPosition->GetPosition2D();
+ bool wasObstructed = false;
- // 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
- if (m_PathState == PATHSTATE_FOLLOWING)
- TryGoingStraightToTargetEntity(initialPos);
-
- // Keep track of the current unit's position during the update
- CFixedVector2D pos = initialPos;
-
- // If in formation, run to keep up; otherwise just walk
- fixed basicSpeed;
- if (IsFormationMember())
- basicSpeed = GetRunSpeed();
- else
- basicSpeed = m_Speed; // (typically but not always WalkSpeed)
-
- // 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
- fixed terrainSpeed = fixed::FromInt(1);
+ // We want to move (at most) m_Speed*dt units from pos towards the next waypoint
- fixed maxSpeed = basicSpeed.Multiply(terrainSpeed);
+ fixed timeLeft = dt;
- bool wasObstructed = false;
+ // TODO: I think this may be a little buggy if we want to compute it several times per turn.
+ while (timeLeft > fixed::Zero())
+ {
+ // If we ran out of path, we have to stop
+ if (!HasValidPath())
+ break;
- // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint
+ CFixedVector2D target;
+ target = CFixedVector2D(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z);
- fixed timeLeft = dt;
- fixed zero = fixed::Zero();
+ CFixedVector2D offset = target - pos;
+ fixed offsetLength = offset.Length();
+ // Work out how far we can travel in timeLeft
+ fixed maxdist = m_Speed.Multiply(timeLeft);
+
+ CFixedVector2D destination;
+ if (offsetLength <= maxdist)
+ destination = target;
+ else
+ {
+ offset.Normalize(maxdist);
+ destination = pos + offset;
+ }
- while (timeLeft > zero)
+ // TODO: try moving as much as we can still?
+ // TODO: get more information about what blocked us.
+ if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, destination.X, destination.Y, m_Clearance, m_PassClass))
{
- // If we ran out of path, we have to stop
- if (m_ShortPath.m_Waypoints.empty() && m_LongPath.m_Waypoints.empty())
- break;
+ pos = destination;
- CFixedVector2D target;
- if (m_ShortPath.m_Waypoints.empty())
- target = CFixedVector2D(m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z);
- else
- target = CFixedVector2D(m_ShortPath.m_Waypoints.back().x, m_ShortPath.m_Waypoints.back().z);
+ timeLeft = (timeLeft.Multiply(m_Speed) - offsetLength) / m_Speed;
- CFixedVector2D offset = target - pos;
+ if (destination == target)
+ m_Path.m_Waypoints.pop_back();
+ continue;
+ }
+ else
+ {
+ // Error - path was obstructed
+ wasObstructed = true;
+ break;
+ }
+ }
- // Work out how far we can travel in timeLeft
- fixed maxdist = maxSpeed.Multiply(timeLeft);
+ if (!m_StartedMoving && wasObstructed)
+ // If this is the turn we start moving, and we're already obstructed,
+ // fail the move entirely to avoid weirdness.
+ // (we would need to send a "move started" and a "move failed" message in the same turn)
+ pos = initialPos;
- // If the target is close, we can move there directly
- fixed offsetLength = offset.Length();
- if (offsetLength <= maxdist)
- {
- if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
- {
- pos = target;
-
- // Spend the rest of the time heading towards the next waypoint
- timeLeft = timeLeft - (offsetLength / maxSpeed);
-
- if (m_ShortPath.m_Waypoints.empty())
- m_LongPath.m_Waypoints.pop_back();
- else
- m_ShortPath.m_Waypoints.pop_back();
-
- continue;
- }
- else
- {
- // Error - path was obstructed
- wasObstructed = true;
- break;
- }
- }
- else
- {
- // Not close enough, so just move in the right direction
- offset.Normalize(maxdist);
- target = pos + offset;
-
- if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
- pos = target;
- else
- wasObstructed = true; // Error - path was obstructed
+ // Update the Position component after our movement (if we actually moved anywhere)
+ if (pos != initialPos)
+ {
+ CFixedVector2D offset = pos - initialPos;
- break;
- }
- }
+ // Face towards the target
+ entity_angle_t angle = atan2_approx(offset.X, offset.Y);
+ cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle);
- // Update the Position component after our movement (if we actually moved anywhere)
- if (pos != initialPos)
- {
- CFixedVector2D offset = pos - initialPos;
+ // Calculate the mean speed over this past turn.
+ SetActualSpeed(cmpPosition->GetDistanceTravelled() / dt);
- // Face towards the target
- entity_angle_t angle = atan2_approx(offset.X, offset.Y);
- cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle);
+ // tell other components and visual actor we are moving.
+ if (!m_StartedMoving)
+ MoveStarted();
- // Calculate the mean speed over this past turn.
- m_CurSpeed = cmpPosition->GetDistanceTravelled() / dt;
+ // Check if we are at our destination
+ // since we're already checking in the general case at the beginning of this function,
+ // no need to do this outside this if block.
+ if (ShouldConsiderOurselvesAtDestination())
+ {
+ CompleteMove();
+ return;
}
- if (wasObstructed)
+ if (!wasObstructed)
{
- // Oops, we hit something (very likely another unit).
- // This is when we might easily get stuck wrongly.
+ // everything is going smoothly, return.
+ m_Tries = 0;
+ m_WaitingTurns = 0;
+ return;
+ }
+ }
+ else
+ SetActualSpeed(fixed::Zero());
- // check if we've arrived.
- if (ShouldConsiderOurselvesAtDestination(pos))
- return;
- // If we still have long waypoints, try and compute a short path
- // This will get us around units, amongst others.
- // However in some cases a long waypoint will be in located in the obstruction of
- // an idle unit. In that case, we need to scrap that waypoint or we might never be able to reach it.
- // I am not sure why this happens but the following code seems to work.
- if (!m_LongPath.m_Waypoints.empty())
- {
- CmpPtr cmpObstructionManager(GetSystemEntity());
- if (cmpObstructionManager)
- {
- // create a fake obstruction to represent our waypoint.
- ICmpObstructionManager::ObstructionSquare square;
- square.hh = m_Clearance;
- square.hw = m_Clearance;
- square.u = CFixedVector2D(entity_pos_t::FromInt(1),entity_pos_t::FromInt(0));
- square.v = CFixedVector2D(entity_pos_t::FromInt(0),entity_pos_t::FromInt(1));
- square.x = m_LongPath.m_Waypoints.back().x;
- square.z = m_LongPath.m_Waypoints.back().z;
- std::vector unitOnGoal;
- // don't ignore moving units as those might be units like us, ie not really moving.
- cmpObstructionManager->GetUnitsOnObstruction(square, unitOnGoal, GetObstructionFilter(), true);
- if (!unitOnGoal.empty())
- m_LongPath.m_Waypoints.pop_back();
- }
- if (!m_LongPath.m_Waypoints.empty())
- {
- PathGoal goal;
- if (m_LongPath.m_Waypoints.size() > 1 || m_FinalGoal.DistanceToPoint(pos) > LONG_PATH_MIN_DIST)
- goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z };
- else
- {
- UpdateFinalGoal();
- goal = m_FinalGoal;
- m_LongPath.m_Waypoints.clear();
- CFixedVector2D target = goal.NearestPointOnGoal(pos);
- m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y });
- }
- RequestShortPath(pos, goal, true);
- m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT;
- return;
- }
- }
- // Else, just entirely recompute
- UpdateFinalGoal();
- BeginPathing(pos, m_FinalGoal);
+ // tell relevant components we have paused if necessary
+ if (m_StartedMoving)
+ MovePaused();
- // potential TODO: We could switch the short-range pathfinder for something else entirely.
- return;
- }
+ // Oops, we've had a problem. Either we were obstructed, or we ran out of path (but still have a goal).
+ // Handle it.
+ // Failure to handle it will result in stuckness and players complaining.
- // We successfully moved along our path, until running out of
- // waypoints or time.
+ if (m_ExpectedPathTicket != 0)
+ // wait until we get our path to see where that leads us.
+ return;
- if (m_PathState == PATHSTATE_FOLLOWING)
+ // if our next waypoint is close enough to our goal and our goal isn't a point, drop our path and recompute directly.
+ if (m_FinalGoal.IsNotAPoint() && !m_Path.m_Waypoints.empty())
+ {
+
+ CmpPtr cmpObstructionManager(GetSystemEntity());
+ if (cmpObstructionManager)
{
- // If we're not currently computing any new paths:
- if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty())
+ bool inRange = false;
+ if (m_FinalGoal.TargetIsEntity())
+ inRange = cmpObstructionManager->IsInTargetRange(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z,
+ m_FinalGoal.GetEntity(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange());
+ else
+ inRange = cmpObstructionManager->IsInPointRange(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z,
+ m_FinalGoal.X(), m_FinalGoal.Z(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange());
+ if (inRange)
{
- if (IsFormationMember())
- {
- // We've reached our assigned position. If the controller
- // is idle, send a notification in case it should disband,
- // otherwise continue following the formation next turn.
- CmpPtr cmpUnitMotion(GetSimContext(), m_TargetEntity);
- if (cmpUnitMotion && !cmpUnitMotion->IsMoving())
- {
- CmpPtr cmpObstruction(GetEntityHandle());
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(false);
-
- m_Moving = false;
- CMessageMotionChanged msg(false, false);
- GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
- }
- }
- else
- {
- // check if target was reached in case of a moving target
- CmpPtr cmpUnitMotion(GetSimContext(), m_TargetEntity);
- if (cmpUnitMotion && cmpUnitMotion->IsMoving() &&
- MoveToTargetRange(m_TargetEntity, m_TargetMinRange, m_TargetMaxRange))
- return;
-
- // Not in formation, so just finish moving
- StopMoving();
- m_State = STATE_IDLE;
- MoveSucceeded();
-
- if (m_FacePointAfterMove)
- FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z);
- // TODO: if the goal was a square building, we ought to point towards the
- // nearest point on the square, not towards its center
- }
+ m_Path.m_Waypoints.clear();
+ m_WaitingTurns = MAX_PATH_REATTEMPS; // short path
}
-
- // If we have a target entity, and we're not miles away from the end of
- // our current path, and the target moved enough, then recompute our
- // whole path
- if (IsFormationMember())
- CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION);
- else
- CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA);
}
}
+
+ // give us some turns to recover.
+ // TODO: only do this if we ran into a moving unit and not something else, because something else won't move
+ // specifically: if we ran into a moving unit, we should wait a turn and see what happens
+ // if we ran into a static unit, recompute a short-path directly
+ // if we ran into a static obstruction, recompute long-path directly
+ // And then maybe we could add some finetuning based on target.
+ if (m_WaitingTurns == 0)
+ {
+ if (HasValidPath())
+ m_WaitingTurns = MAX_PATH_REATTEMPS;
+ else
+ m_WaitingTurns = 3;
}
-}
-bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out)
-{
- if (m_TargetEntity == INVALID_ENTITY)
- return false;
+ --m_WaitingTurns;
- CmpPtr cmpPosition(GetSimContext(), m_TargetEntity);
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return false;
+ // Try again next turn, no changes
+ if (m_WaitingTurns >= MAX_PATH_REATTEMPS)
+ return;
- if (m_TargetOffset.IsZero())
+ // already waited one turn, no changes, so try computing a short path.
+ if (m_WaitingTurns >= 3)
{
- // No offset, just return the position directly
- out = cmpPosition->GetPosition2D();
+ PathGoal goal;
+ if (m_Path.m_Waypoints.empty())
+ goal = { PathGoal::POINT, m_FinalGoal.X(), m_FinalGoal.Z() };
+ else
+ {
+ goal = { PathGoal::POINT, m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z };
+ m_Path.m_Waypoints.pop_back();
+ }
+ RequestShortPath(pos, goal, true);
+ return;
}
- else
+
+ // Last resort, compute a long path
+ if (m_WaitingTurns == 2)
{
- // There is an offset, so compute it relative to orientation
- entity_angle_t angle = cmpPosition->GetRotation().Y;
- CFixedVector2D offset = m_TargetOffset.Rotate(angle);
- out = cmpPosition->GetPosition2D() + offset;
+ PathGoal goal;
+ if (m_Path.m_Waypoints.empty())
+ goal = { PathGoal::POINT, m_FinalGoal.X(), m_FinalGoal.Z() };
+ else
+ {
+ goal = { PathGoal::POINT, m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z };
+ m_Path.m_Waypoints.pop_back();
+ }
+ RequestLongPath(pos, goal);
+ return;
}
- return true;
-}
-bool CCmpUnitMotion::TryGoingStraightToGoalPoint(const CFixedVector2D& from)
-{
- // Make sure the goal is a point (and not a point-like target like a formation controller)
- if (m_FinalGoal.type != PathGoal::POINT || m_TargetEntity != INVALID_ENTITY)
- return false;
- // Fail if the goal is too far away
- CFixedVector2D goalPos(m_FinalGoal.x, m_FinalGoal.z);
- if ((goalPos - from).CompareLength(DIRECT_PATH_RANGE) > 0)
- return false;
+ // m_waitingTurns == 1 here
- CmpPtr cmpPathfinder(GetSystemEntity());
- if (!cmpPathfinder)
- return false;
+ // we tried getting a renewed path and still got stuck
+ if (m_AbortIfStuck == 0)
+ {
+ MoveFailed();
+ return;
+ }
- // Check if there's any collisions on that route
- if (!cmpPathfinder->CheckMovement(GetObstructionFilter(), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
- return false;
+ --m_AbortIfStuck;
- // That route is okay, so update our path
- m_LongPath.m_Waypoints.clear();
- m_ShortPath.m_Waypoints.clear();
- m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
+ // Recompute a new path, but wait a few turns first
+ m_WaitingTurns = 4 + MAX_PATH_REATTEMPS;
- return true;
+ return;
}
-bool CCmpUnitMotion::TryGoingStraightToTargetEntity(const CFixedVector2D& from)
+// TODO: this should care about target movement
+bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta)
{
- CFixedVector2D targetPos;
- if (!ComputeTargetPosition(targetPos))
- return false;
-
- // Fail if the target is too far away
- if ((targetPos - from).CompareLength(DIRECT_PATH_RANGE) > 0)
+ if (!m_FinalGoal.TargetIsEntity())
return false;
- CmpPtr cmpPathfinder(GetSystemEntity());
- if (!cmpPathfinder)
- return false;
-
- // Move the goal to match the target entity's new position
- PathGoal goal = m_FinalGoal;
- goal.x = targetPos.X;
- goal.z = targetPos.Y;
- // (we ignore changes to the target's rotation, since only buildings are
- // square and buildings don't move)
+ if (!HasValidPath())
+ return true;
- // Find the point on the goal shape that we should head towards
- CFixedVector2D goalPos = goal.NearestPointOnGoal(from);
+ // Fail unless the target has moved enough
+ CFixedVector2D oldTargetPos = CFixedVector2D(m_Path.m_Waypoints[0].x,m_Path.m_Waypoints[0].z);
- // Check if there's any collisions on that route
- if (!cmpPathfinder->CheckMovement(GetObstructionFilter(true), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
+ if ((m_FinalGoal.Pos() - oldTargetPos).CompareLength(minDelta) < 0)
return false;
- // That route is okay, so update our path
- m_FinalGoal = goal;
- m_LongPath.m_Waypoints.clear();
- m_ShortPath.m_Waypoints.clear();
- m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
-
- return true;
-}
-
-bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta)
-{
- CFixedVector2D targetPos;
- if (!ComputeTargetPosition(targetPos))
- return false;
-
- // Fail unless the target has moved enough
- CFixedVector2D oldTargetPos(m_FinalGoal.x, m_FinalGoal.z);
- if ((targetPos - oldTargetPos).CompareLength(minDelta) < 0)
- return false;
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return false;
CFixedVector2D pos = cmpPosition->GetPosition2D();
CFixedVector2D oldDir = (oldTargetPos - pos);
- CFixedVector2D newDir = (targetPos - pos);
+ CFixedVector2D newDir = (m_FinalGoal.Pos() - pos);
oldDir.Normalize();
newDir.Normalize();
// Fail unless we're close enough to the target to care about its movement
// and the angle between the (straight-line) directions of the previous and new target positions is small
- if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_LongPath, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))
+ if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_Path, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))
return false;
// Fail if the target is no longer visible to this entity's owner
@@ -1183,47 +995,26 @@
if (cmpOwnership)
{
CmpPtr cmpRangeManager(GetSystemEntity());
- if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_TargetEntity, cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)
+ if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FinalGoal.GetEntity(), cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)
return false;
}
// The target moved and we need to update our current path;
- // change the goal here and expect our caller to start the path request
- m_FinalGoal.x = targetPos.X;
- m_FinalGoal.z = targetPos.Y;
- RequestLongPath(from, m_FinalGoal);
- m_PathState = PATHSTATE_FOLLOWING_REQUESTING_LONG;
+ // Expect our caller to recompute
+ // Dump our current path.
+ m_Path.m_Waypoints.clear();
return true;
}
-void CCmpUnitMotion::UpdateFinalGoal()
+// TODO: ought to be cleverer here.
+// In particular maybe we should support some "margin" for error.
+bool CCmpUnitMotion::ShouldConsiderOurselvesAtDestination()
{
- if (m_TargetEntity == INVALID_ENTITY)
- return;
- CmpPtr cmpUnitMotion(GetSimContext(), m_TargetEntity);
- if (!cmpUnitMotion)
- return;
- if (IsFormationMember())
- return;
- CFixedVector2D targetPos;
- if (!ComputeTargetPosition(targetPos))
- return;
- m_FinalGoal.x = targetPos.X;
- m_FinalGoal.z = targetPos.Y;
-}
-
-bool CCmpUnitMotion::ShouldConsiderOurselvesAtDestination(const CFixedVector2D& from)
-{
- if (m_TargetEntity != INVALID_ENTITY || m_FinalGoal.DistanceToPoint(from) > SHORT_PATH_GOAL_RADIUS)
- return false;
-
- StopMoving();
- MoveSucceeded();
-
- if (m_FacePointAfterMove)
- FaceTowardsPointFromPos(from, m_FinalGoal.x, m_FinalGoal.z);
- return true;
+ if (m_FinalGoal.TargetIsEntity())
+ return IsInTargetRange(m_FinalGoal.GetEntity(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange());
+ else
+ return IsInPointRange(m_FinalGoal.X(),m_FinalGoal.Z(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange());
}
bool CCmpUnitMotion::PathIsShort(const WaypointPath& path, const CFixedVector2D& from, entity_pos_t minDistance) const
@@ -1275,79 +1066,47 @@
ControlGroupMovementObstructionFilter CCmpUnitMotion::GetObstructionFilter(bool noTarget) const
{
- entity_id_t group = noTarget ? m_TargetEntity : GetGroup();
- return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), group);
+ entity_id_t group = noTarget ? m_FinalGoal.GetEntity() : GetGroup();
+ // TODO: if we sometimes want to consider moving units, change here.
+ return ControlGroupMovementObstructionFilter(false, group);
}
-
-
-void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal)
+// TODO: this should be improved, it's a little limited
+// EG use of hierarchical pathfinder,…
+// also it should probably make the goal passable directly, to avoid conflict with the paths returned.
+void CCmpUnitMotion::RequestNewPath()
{
- // reset our state for sanity.
- m_ExpectedPathTicket = 0;
+ ENSURE(m_ExpectedPathTicket == 0);
- CmpPtr cmpObstruction(GetEntityHandle());
- if (cmpObstruction)
- cmpObstruction->SetMovingFlag(false);
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (!cmpPosition)
+ return;
- m_Moving = false;
+ // dump current path
+ m_Path.m_Waypoints.clear();
- m_PathState = PATHSTATE_NONE;
+ CFixedVector2D position = cmpPosition->GetPosition2D();
#if DISABLE_PATHFINDER
{
CmpPtr cmpPathfinder (GetSimContext(), SYSTEM_ENTITY);
- CFixedVector2D goalPos = m_FinalGoal.NearestPointOnGoal(from);
+ CFixedVector2D goalPos = m_FinalGoal.Goal().NearestPointOnGoal(position);
m_LongPath.m_Waypoints.clear();
m_ShortPath.m_Waypoints.clear();
m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
- m_PathState = PATHSTATE_FOLLOWING;
return;
}
#endif
- // If we're aiming at a target entity and it's close and we can reach
- // it in a straight line, then we'll just go along the straight line
- // instead of computing a path.
- if (TryGoingStraightToTargetEntity(from))
- {
- if (!HasValidPath())
- StartSucceeded();
- m_PathState = PATHSTATE_FOLLOWING;
- return;
- }
-
- // Same thing applies to non-entity points
- if (TryGoingStraightToGoalPoint(from))
- {
- if (!HasValidPath())
- StartSucceeded();
- m_PathState = PATHSTATE_FOLLOWING;
- return;
- }
-
- // Otherwise we need to compute a path.
-
// If it's close then just do a short path, not a long path
// TODO: If it's close on the opposite side of a river then we really
// need a long path, so we shouldn't simply check linear distance
// the check is arbitrary but should be a reasonably small distance.
- if (goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST)
- {
- // add our final goal as a long range waypoint so we don't forget
- // where we are going if the short-range pathfinder returns
- // an aborted path.
- m_LongPath.m_Waypoints.clear();
- CFixedVector2D target = m_FinalGoal.NearestPointOnGoal(from);
- m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y });
- m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT;
- RequestShortPath(from, goal, true);
- }
+ // Maybe use PathIsShort?
+ if (m_FinalGoal.Goal().DistanceToPoint(position) < LONG_PATH_MIN_DIST)
+ RequestShortPath(position, m_FinalGoal.Goal(), true);
else
- {
- m_PathState = PATHSTATE_WAITING_REQUESTING_LONG;
- RequestLongPath(from, goal);
- }
+ RequestLongPath(position, m_FinalGoal.Goal());
}
void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal)
@@ -1373,7 +1132,7 @@
return;
// wrapping around on m_Tries isn't really a problem so don't check for overflow.
- fixed searchRange = std::max(SHORT_PATH_MIN_SEARCH_RANGE * ++m_Tries, goal.DistanceToPoint(from));
+ fixed searchRange = std::max(SHORT_PATH_MIN_SEARCH_RANGE * (++m_Tries + 1), goal.DistanceToPoint(from));
if (goal.type != PathGoal::POINT && searchRange < goal.hw && searchRange < SHORT_PATH_MIN_SEARCH_RANGE * 2)
searchRange = std::min(goal.hw, SHORT_PATH_MIN_SEARCH_RANGE * 2);
if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE)
@@ -1389,8 +1148,11 @@
bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target)
{
+ // Must closely mirror CmpObstructionManager::IsInPointRange
PROFILE("MoveToPointRange");
+ DiscardMove();
+
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return false;
@@ -1442,76 +1204,23 @@
}
}
- m_State = STATE_INDIVIDUAL_PATH;
- m_TargetEntity = target;
- m_TargetOffset = CFixedVector2D();
- m_TargetMinRange = minRange;
- m_TargetMaxRange = maxRange;
- m_FinalGoal = goal;
- m_Tries = 0;
-
- BeginPathing(pos, goal);
-
- return true;
-}
-
-bool CCmpUnitMotion::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
-{
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return false;
-
- CFixedVector2D pos = cmpPosition->GetPosition2D();
-
- bool hasObstruction = false;
- CmpPtr cmpObstructionManager(GetSystemEntity());
- ICmpObstructionManager::ObstructionSquare obstruction;
-//TODO if (cmpObstructionManager)
-// hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(), x, z, m_Radius, obstruction);
-
- if (minRange.IsZero() && maxRange.IsZero() && hasObstruction)
- {
- // Handle the non-ranged mode:
- CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
- entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
-
- // See if we're too close to the target square
- if (distance < minRange)
- return false;
-
- // See if we're close enough to the target square
- if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
- return true;
-
- return false;
- }
+ if (target == INVALID_ENTITY)
+ m_FinalGoal = SMotionGoal(goal, minRange, maxRange);
else
- {
- entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
+ m_FinalGoal = SMotionGoal(GetSimContext(), target, goal, minRange, maxRange);
- if (distance < minRange)
- return false;
- else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
- return false;
- else
- return true;
- }
-}
+ RequestNewPath();
-bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const
-{
- // Given a square, plus a target range we should reach, the shape at that distance
- // is a round-cornered square which we can approximate as either a circle or as a square.
- // Previously, we used the shape that minimized the worst-case error.
- // However that is unsage in some situations. So let's be less clever and
- // just check if our range is at least three times bigger than the circleradius
- return (range > circleRadius*3);
+ return true;
}
bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
{
+ // Must closely mirror CmpObstructionManager::IsInTargetRange
PROFILE("MoveToTargetRange");
+ DiscardMove();
+
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return false;
@@ -1588,7 +1297,7 @@
entity_pos_t goalDistance = minRange + Pathfinding::GOAL_DELTA;
- if (ShouldTreatTargetAsCircle(minRange, circleRadius))
+ if (Geometry::ShouldTreatTargetAsCircle(minRange, circleRadius))
{
// The target is small relative to our range, so pretend it's a circle
goal.type = PathGoal::INVERTED_CIRCLE;
@@ -1616,7 +1325,7 @@
// Circumscribe the square
entity_pos_t circleRadius = halfSize.Length();
- if (ShouldTreatTargetAsCircle(maxRange, circleRadius))
+ if (Geometry::ShouldTreatTargetAsCircle(maxRange, circleRadius))
{
// The target is small relative to our range, so pretend it's a circle
@@ -1655,117 +1364,47 @@
}
}
- m_State = STATE_INDIVIDUAL_PATH;
- m_TargetEntity = target;
- m_TargetOffset = CFixedVector2D();
- m_TargetMinRange = minRange;
- m_TargetMaxRange = maxRange;
- m_FinalGoal = goal;
- m_Tries = 0;
+ if (target == INVALID_ENTITY)
+ m_FinalGoal = SMotionGoal(goal, minRange, maxRange);
+ else
+ m_FinalGoal = SMotionGoal(GetSimContext(), target, goal, minRange, maxRange);
- BeginPathing(pos, goal);
+ RequestNewPath();
return true;
}
-bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
+bool CCmpUnitMotion::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
{
- // This function closely mirrors MoveToTargetRange - it needs to return true
- // after that Move has completed
-
CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
+ if (!cmpPosition)
return false;
- CFixedVector2D pos = cmpPosition->GetPosition2D();
-
CmpPtr cmpObstructionManager(GetSystemEntity());
if (!cmpObstructionManager)
- return false;
-
- bool hasObstruction = false;
- ICmpObstructionManager::ObstructionSquare obstruction;
- CmpPtr cmpObstruction(GetSimContext(), target);
- if (cmpObstruction)
- hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
-
- if (hasObstruction)
- {
- CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
- entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize, true);
-
- // Compare with previous obstruction
- ICmpObstructionManager::ObstructionSquare previousObstruction;
- cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
- entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize, true);
-
- // See if we're too close to the target square
- bool inside = distance.IsZero() && !Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize).IsZero();
- if ((distance < minRange && previousDistance < minRange) || inside)
- return false;
-
- // See if we're close enough to the target square
- if (maxRange < entity_pos_t::Zero() || distance <= maxRange || previousDistance <= maxRange)
- return true;
-
- entity_pos_t circleRadius = halfSize.Length();
-
- if (ShouldTreatTargetAsCircle(maxRange, circleRadius))
- {
- // The target is small relative to our range, so pretend it's a circle
- // and see if we're close enough to that.
- // Also check circle around previous position.
- entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
- entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
-
- return circleDistance <= maxRange || previousCircleDistance <= maxRange;
- }
-
- // take minimal clearance required in MoveToTargetRange into account, multiplying by 3/2 for diagonals
- entity_pos_t maxDist = std::max(maxRange, (m_Clearance + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16)*3/2);
- return distance <= maxDist || previousDistance <= maxDist;
- }
- else
- {
- CmpPtr cmpTargetPosition(GetSimContext(), target);
- if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
- return false;
+ return true; // what's a sane default here?
- CFixedVector2D targetPos = cmpTargetPosition->GetPreviousPosition2D();
- entity_pos_t distance = (pos - targetPos).Length();
+ CFixedVector2D pos = cmpPosition->GetPosition2D();
- return minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange);
- }
+ return cmpObstructionManager->IsInPointRange(pos.X, pos.Y, x, z, minRange, maxRange + m_Clearance);
}
-void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
+bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
{
- CmpPtr cmpPosition(GetSimContext(), target);
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return;
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (!cmpPosition)
+ return false;
- CFixedVector2D pos = cmpPosition->GetPosition2D();
+ CmpPtr cmpObstructionManager(GetSystemEntity());
+ if (!cmpObstructionManager)
+ return true; // what's a sane default here?
- PathGoal goal;
- goal.type = PathGoal::POINT;
- goal.x = pos.X;
- goal.z = pos.Y;
-
- m_State = STATE_FORMATIONMEMBER_PATH;
- m_TargetEntity = target;
- m_TargetOffset = CFixedVector2D(x, z);
- m_TargetMinRange = entity_pos_t::Zero();
- m_TargetMaxRange = entity_pos_t::Zero();
- m_FinalGoal = goal;
- m_Tries = 0;
+ CFixedVector2D pos = cmpPosition->GetPosition2D();
- BeginPathing(pos, goal);
+ return cmpObstructionManager->IsInTargetRange(pos.X, pos.Y, target, minRange, maxRange + m_Clearance);
}
-
-
-
void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector& lines, CColor color)
{
bool floating = false;
@@ -1800,8 +1439,7 @@
if (!m_DebugOverlayEnabled)
return;
- RenderPath(m_LongPath, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH);
- RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOR_SHORT_PATH);
+ RenderPath(m_Path, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH);
for (size_t i = 0; i < m_DebugOverlayLongPathLines.size(); ++i)
collector.Submit(&m_DebugOverlayLongPathLines[i]);
Index: source/simulation2/components/CCmpVisualActor.cpp
===================================================================
--- source/simulation2/components/CCmpVisualActor.cpp
+++ source/simulation2/components/CCmpVisualActor.cpp
@@ -54,7 +54,6 @@
public:
static void ClassInit(CComponentManager& componentManager)
{
- componentManager.SubscribeToMessageType(MT_Update_Final);
componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
@@ -71,10 +70,7 @@
fixed m_R, m_G, m_B; // shading color
- std::map m_AnimOverride;
-
// Current animation state
- fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode
std::string m_AnimName;
bool m_AnimOnce;
fixed m_AnimSpeed;
@@ -83,6 +79,9 @@
fixed m_AnimSyncRepeatTime; // 0.0 if not synced
fixed m_AnimSyncOffsetTime;
+ std::string m_MovingPrefix;
+ fixed m_MovingSpeed;
+
std::map m_VariantSelections;
u32 m_Seed; // seed used for random variations
@@ -225,9 +224,6 @@
serialize.NumberFixed_Unbounded("g", m_G);
serialize.NumberFixed_Unbounded("b", m_B);
- SerializeMap()(serialize, "anim overrides", m_AnimOverride);
-
- serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold);
serialize.StringASCII("anim name", m_AnimName, 0, 256);
serialize.Bool("anim once", m_AnimOnce);
serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
@@ -282,12 +278,6 @@
{
switch (msg.GetType())
{
- case MT_Update_Final:
- {
- const CMessageUpdate_Final& msgData = static_cast (msg);
- Update(msgData.turnLength);
- break;
- }
case MT_OwnershipChanged:
{
if (!m_Unit)
@@ -423,7 +413,6 @@
virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup)
{
- m_AnimRunThreshold = fixed::Zero();
m_AnimName = name;
m_AnimOnce = once;
m_AnimSpeed = speed;
@@ -432,28 +421,36 @@
m_AnimSyncRepeatTime = fixed::Zero();
m_AnimSyncOffsetTime = fixed::Zero();
- SetVariant("animation", m_AnimName);
+ // TODO: change this once we support walk/run-anims
+ std::string animName = name;
+ /*if (!m_MovingPrefix.empty() && m_AnimName != "idle")
+ animName = m_MovingPrefix + "-" + m_AnimName;
+ else */if (!m_MovingPrefix.empty())
+ animName = m_MovingPrefix;
- if (m_Unit && m_Unit->GetAnimation())
- m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
- }
+ SetVariant("animation", animName);
- virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace)
- {
- m_AnimOverride[name] = replace;
+ if (m_Unit && m_Unit->GetAnimation())
+ m_Unit->GetAnimation()->SetAnimationState(animName, m_AnimOnce, m_MovingSpeed.Multiply(m_AnimSpeed).ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
}
- virtual void ResetMoveAnimation(const std::string& name)
+ virtual void SetMovingSpeed(fixed movingSpeed)
{
- std::map::const_iterator it = m_AnimOverride.find(name);
- if (it != m_AnimOverride.end())
- m_AnimOverride.erase(name);
- }
+ // TODO: don't copy strings for fun.
+ std::string prefix;
+ if (movingSpeed.IsZero())
+ prefix = "";
+ else
+ {
+ CmpPtr cmpUnitMotion(GetEntityHandle());
+ if (!cmpUnitMotion)
+ return;
+ prefix = cmpUnitMotion->GetSpeedRatio() <= fixed::FromInt(1) ? "walk" : "run";
+ }
+ m_MovingPrefix = prefix;
+ m_MovingSpeed = movingSpeed.IsZero() ? fixed::FromInt(1) : movingSpeed;
- virtual void SelectMovementAnimation(fixed runThreshold)
- {
- SelectAnimation("walk", false, fixed::FromFloat(1.f), L"");
- m_AnimRunThreshold = runThreshold;
+ SelectAnimation(m_AnimName, m_AnimOnce, m_AnimSpeed, m_SoundGroup);
}
virtual void SetAnimationSyncRepeat(fixed repeattime)
@@ -534,8 +531,6 @@
// ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical.
// It is also used by ReloadActor.
void ReloadUnitAnimation();
-
- void Update(fixed turnLength);
};
REGISTER_COMPONENT_TYPE(VisualActor)
@@ -741,45 +736,3 @@
m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
}
-void CCmpVisualActor::Update(fixed UNUSED(turnLength))
-{
- // This function is currently only used to update the animation if the speed in
- // CCmpUnitMotion changes. This also only happens in the "special movement mode"
- // triggered by SelectMovementAnimation.
-
- // TODO: This should become event based, in order to save performance and to make the code
- // far less hacky. We should also take into account the speed when the animation is different
- // from the "special movement mode" walking animation.
-
- // If we're not in the special movement mode, nothing to do.
- if (m_AnimRunThreshold.IsZero())
- return;
-
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return;
-
- CmpPtr cmpUnitMotion(GetEntityHandle());
- if (!cmpUnitMotion)
- return;
-
- fixed speed = cmpUnitMotion->GetCurrentSpeed();
- std::string name;
-
- if (speed.IsZero())
- {
- speed = fixed::FromFloat(1.f);
- name = "idle";
- }
- else
- name = speed < m_AnimRunThreshold ? "walk" : "run";
-
- std::map::const_iterator it = m_AnimOverride.find(name);
- if (it != m_AnimOverride.end())
- name = it->second;
-
- // Selecting the animation is going to reset the anim run threshold, so save it
- fixed runThreshold = m_AnimRunThreshold;
- SelectAnimation(name, false, speed, L"");
- m_AnimRunThreshold = runThreshold;
-}
Index: source/simulation2/components/ICmpObstructionManager.h
===================================================================
--- source/simulation2/components/ICmpObstructionManager.h
+++ source/simulation2/components/ICmpObstructionManager.h
@@ -159,6 +159,16 @@
virtual void RemoveShape(tag_t tag) = 0;
/**
+ * Check if the given point is in range of the other point given those parameters
+ */
+ virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange) = 0;
+
+ /**
+ * Check if the given point is in range of the target given those parameters
+ */
+ virtual bool IsInTargetRange(entity_pos_t x, entity_pos_t z, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
+
+ /**
* Collision test a flat-ended thick line against the current set of shapes.
* The line caps extend by @p r beyond the end points.
* Only intersections going from outside to inside a shape are counted.
Index: source/simulation2/components/ICmpUnitMotion.h
===================================================================
--- source/simulation2/components/ICmpUnitMotion.h
+++ source/simulation2/components/ICmpUnitMotion.h
@@ -47,14 +47,12 @@
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
- * Determine wether the givven point is within the given range, using the same measurement
- * as MoveToPointRange.
+ * Wrapper around ObstructionManager::IsInPointRange with unit position
*/
virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
- * Determine whether the target is within the given range, using the same measurement
- * as MoveToTargetRange.
+ * Wrapper around ObstructionManager::IsInTargetRange with unit position
*/
virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
@@ -71,45 +69,70 @@
virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
- * Join a formation, and move towards a given offset relative to the formation controller entity.
- * Continues following the formation until given a different command.
+ * Turn to look towards the given point.
*/
- virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) = 0;
+ virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0;
/**
- * Turn to look towards the given point.
+ * Determine whether to abort or retry X times if pathing fails.
+ * Generally safer to let it abort and inform us.
*/
- virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0;
+ virtual void SetAbortIfStuck(u8 shouldAbort) = 0;
+
+ /**
+ * Stop moving immediately, don't send messages.
+ * This should be used if you are going to ask for a new path,
+ * in the same function, for example.
+ * In doubt, UnitAI should probably call this.
+ * Use with caution.
+ */
+ virtual void DiscardMove() = 0;
/**
- * Stop moving immediately.
+ * Stop moving immediately, send messages.
+ * In doubt, components that are not UnitIA should probably call this.
*/
- virtual void StopMoving() = 0;
+ virtual void CompleteMove() = 0;
/**
- * Get the current movement speed.
+ * Get how much faster/slower we are at than normal.
*/
- virtual fixed GetCurrentSpeed() = 0;
+ virtual fixed GetSpeedRatio() = 0;
+
+ /**
+ * Get how much faster than our regular speed we can go.
+ */
+ virtual fixed GetTopSpeedRatio() = 0;
/**
* Set the current movement speed.
+ * 'speed' in % of top speed (ie 3.0 will be 3 times top speed).
*/
virtual void SetSpeed(fixed speed) = 0;
/**
- * Get whether the unit is moving.
+ * Get whether the unit is actually moving on the map this turn.
+ */
+ virtual bool IsActuallyMoving() = 0;
+
+ /**
+ * Get whether a unit is trying to go somewhere
+ * NB: this does not mean its position is actually changing right now.
*/
- virtual bool IsMoving() = 0;
+ virtual bool IsTryingToMove() = 0;
/**
- * Get the default speed that this unit will have when walking, in metres per second.
+ * Get the unit theoretical speed in metres per second.
+ * GetActualSpeed will return historical speed
+ * This is affected by SetSpeed.
*/
- virtual fixed GetWalkSpeed() = 0;
+ virtual fixed GetSpeed() = 0;
/**
- * Get the default speed that this unit will have when running, in metres per second.
+ * Get the unit template speed in metres per second.
+ * This is NOT affected by SetSpeed.
*/
- virtual fixed GetRunSpeed() = 0;
+ virtual fixed GetTemplateSpeed() = 0;
/**
* Set whether the unit will turn to face the target point after finishing moving.
Index: source/simulation2/components/ICmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/ICmpUnitMotion.cpp
+++ source/simulation2/components/ICmpUnitMotion.cpp
@@ -27,14 +27,16 @@
DEFINE_INTERFACE_METHOD_4("IsInPointRange", bool, ICmpUnitMotion, IsInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
-DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t)
-DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
-DEFINE_INTERFACE_METHOD_0("GetCurrentSpeed", fixed, ICmpUnitMotion, GetCurrentSpeed)
+DEFINE_INTERFACE_METHOD_1("SetAbortIfStuck", void, ICmpUnitMotion, SetAbortIfStuck, u8)
+DEFINE_INTERFACE_METHOD_0("DiscardMove", void, ICmpUnitMotion, DiscardMove)
+DEFINE_INTERFACE_METHOD_0("CompleteMove", void, ICmpUnitMotion, CompleteMove)
+DEFINE_INTERFACE_METHOD_0("GetTopSpeedRatio", fixed, ICmpUnitMotion, GetTopSpeedRatio)
DEFINE_INTERFACE_METHOD_1("SetSpeed", void, ICmpUnitMotion, SetSpeed, fixed)
-DEFINE_INTERFACE_METHOD_0("IsMoving", bool, ICmpUnitMotion, IsMoving)
-DEFINE_INTERFACE_METHOD_0("GetWalkSpeed", fixed, ICmpUnitMotion, GetWalkSpeed)
-DEFINE_INTERFACE_METHOD_0("GetRunSpeed", fixed, ICmpUnitMotion, GetRunSpeed)
+DEFINE_INTERFACE_METHOD_0("IsActuallyMoving", bool, ICmpUnitMotion, IsActuallyMoving)
+DEFINE_INTERFACE_METHOD_0("IsTryingToMove", bool, ICmpUnitMotion, IsTryingToMove)
+DEFINE_INTERFACE_METHOD_0("GetSpeed", fixed, ICmpUnitMotion, GetSpeed)
+DEFINE_INTERFACE_METHOD_0("GetTemplateSpeed", fixed, ICmpUnitMotion, GetTemplateSpeed)
DEFINE_INTERFACE_METHOD_0("GetPassabilityClassName", std::string, ICmpUnitMotion, GetPassabilityClassName)
DEFINE_INTERFACE_METHOD_0("GetUnitClearance", entity_pos_t, ICmpUnitMotion, GetUnitClearance)
DEFINE_INTERFACE_METHOD_1("SetFacePointAfterMove", void, ICmpUnitMotion, SetFacePointAfterMove, bool)
@@ -66,24 +68,29 @@
return m_Script.Call("MoveToTargetRange", target, minRange, maxRange);
}
- virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
+ virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)
{
- m_Script.CallVoid("MoveToFormationOffset", target, x, z);
+ m_Script.CallVoid("FaceTowardsPoint", x, z);
}
- virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)
+ virtual void DiscardMove()
{
- m_Script.CallVoid("FaceTowardsPoint", x, z);
+ m_Script.CallVoid("DiscardMove");
}
- virtual void StopMoving()
+ virtual void CompleteMove()
{
- m_Script.CallVoid("StopMoving");
+ m_Script.CallVoid("CompleteMove");
}
- virtual fixed GetCurrentSpeed()
+ virtual void SetAbortIfStuck(u8 shouldAbort)
{
- return m_Script.Call("GetCurrentSpeed");
+ m_Script.CallVoid("SetAbortIfStuck", shouldAbort);
+ }
+
+ virtual fixed GetActualSpeed()
+ {
+ return m_Script.Call("GetActualSpeed");
}
virtual void SetSpeed(fixed speed)
@@ -91,19 +98,29 @@
m_Script.CallVoid("SetSpeed", speed);
}
- virtual bool IsMoving()
+ virtual fixed GetTopSpeedRatio()
+ {
+ return m_Script.Call("GetTopSpeedRatio");
+ }
+
+ virtual bool IsActuallyMoving()
+ {
+ return m_Script.Call("IsActuallyMoving");
+ }
+
+ virtual bool IsTryingToMove()
{
- return m_Script.Call("IsMoving");
+ return m_Script.Call("IsTryingToMove");
}
- virtual fixed GetWalkSpeed()
+ virtual fixed GetSpeed()
{
- return m_Script.Call("GetWalkSpeed");
+ return m_Script.Call("GetSpeed");
}
- virtual fixed GetRunSpeed()
+ virtual fixed GetTemplateSpeed()
{
- return m_Script.Call("GetRunSpeed");
+ return m_Script.Call("GetTemplateSpeed");
}
virtual void SetFacePointAfterMove(bool facePointAfterMove)
@@ -116,6 +133,11 @@
return m_Script.Call("GetPassabilityClass");
}
+ virtual fixed GetSpeedRatio()
+ {
+ return fixed::FromInt(1);
+ }
+
virtual std::string GetPassabilityClassName()
{
return m_Script.Call("GetPassabilityClassName");
Index: source/simulation2/components/ICmpVisual.h
===================================================================
--- source/simulation2/components/ICmpVisual.h
+++ source/simulation2/components/ICmpVisual.h
@@ -99,25 +99,10 @@
virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup) = 0;
/**
- * Replaces a specified animation with another. Only affects the special speed-based
- * animation determination behaviour.
- * @param name Animation to match.
- * @param replace Animation that should replace the matched animation.
+ * Tell the visual actor that the unit is currently moving at the given speed.
+ * If speed is 0, the unit will become idle.
*/
- virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace) = 0;
-
- /**
- * Ensures that the given animation will be used when it normally would be,
- * removing reference to any animation that might replace it.
- * @param name Animation name to remove from the replacement map.
- */
- virtual void ResetMoveAnimation(const std::string& name) = 0;
-
- /**
- * Start playing the walk/run animations, scaled to the unit's movement speed.
- * @param runThreshold movement speed at which to switch to the run animation
- */
- virtual void SelectMovementAnimation(fixed runThreshold) = 0;
+ virtual void SetMovingSpeed(fixed movingSpeed) = 0;
/**
* Adjust the speed of the current animation, so it can match simulation events.
Index: source/simulation2/components/ICmpVisual.cpp
===================================================================
--- source/simulation2/components/ICmpVisual.cpp
+++ source/simulation2/components/ICmpVisual.cpp
@@ -24,9 +24,6 @@
BEGIN_INTERFACE_WRAPPER(Visual)
DEFINE_INTERFACE_METHOD_2("SetVariant", void, ICmpVisual, SetVariant, CStr, CStr)
DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed, std::wstring)
-DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed)
-DEFINE_INTERFACE_METHOD_1("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string)
-DEFINE_INTERFACE_METHOD_2("ReplaceMoveAnimation", void, ICmpVisual, ReplaceMoveAnimation, std::string, std::string)
DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed)
DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed)
DEFINE_INTERFACE_METHOD_4("SetShadingColor", void, ICmpVisual, SetShadingColor, fixed, fixed, fixed, fixed)
Index: source/simulation2/helpers/Geometry.h
===================================================================
--- source/simulation2/helpers/Geometry.h
+++ source/simulation2/helpers/Geometry.h
@@ -30,6 +30,21 @@
namespace Geometry
{
+/*
+ * Check if we should treat a square as a circle, given the radius
+ * of the resulting circle and a distance to it
+ * used by UnitMotion and ObstructionManager
+ */
+inline bool ShouldTreatTargetAsCircle(const fixed& range, const fixed& circleRadius)
+{
+ // Given a square, plus a target range we should reach, the shape at that distance
+ // is a round-cornered square which we can approximate as either a circle or as a square.
+ // Previously, we used the shape that minimized the worst-case error.
+ // However that is unsage in some situations. So let's be less clever and
+ // just check if our range is at least three times bigger than the circleradius
+ return (range > circleRadius*3);
+}
+
/**
* Checks if a point is inside the given rotated rectangle.
* Points precisely on an edge are considered to be inside.
Index: source/simulation2/scripting/MessageTypeConversions.cpp
===================================================================
--- source/simulation2/scripting/MessageTypeConversions.cpp
+++ source/simulation2/scripting/MessageTypeConversions.cpp
@@ -267,20 +267,46 @@
////////////////////////////////
-JS::Value CMessageMotionChanged::ToJSVal(ScriptInterface& scriptInterface) const
+JS::Value CMessageBeginMove::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
- SET_MSG_PROPERTY(starting);
- SET_MSG_PROPERTY(error);
return JS::ObjectValue(*obj);
}
-CMessage* CMessageMotionChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
+CMessage* CMessageBeginMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
- GET_MSG_PROPERTY(bool, starting);
- GET_MSG_PROPERTY(bool, error);
- return new CMessageMotionChanged(starting, error);
+ return new CMessageBeginMove();
+}
+
+////////////////////////////////
+
+JS::Value CMessagePausedMove::ToJSVal(ScriptInterface& scriptInterface) const
+{
+ TOJSVAL_SETUP();
+ return JS::ObjectValue(*obj);
+}
+
+CMessage* CMessagePausedMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
+{
+ FROMJSVAL_SETUP();
+ return new CMessagePausedMove();
+}
+
+////////////////////////////////
+
+JS::Value CMessageFinishedMove::ToJSVal(ScriptInterface& scriptInterface) const
+{
+ TOJSVAL_SETUP();
+ SET_MSG_PROPERTY(failed);
+ return JS::ObjectValue(*obj);
+}
+
+CMessage* CMessageFinishedMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
+{
+ FROMJSVAL_SETUP();
+ GET_MSG_PROPERTY(bool, failed);
+ return new CMessageFinishedMove(failed);
}
////////////////////////////////
Index: source/tools/atlas/GameInterface/ActorViewer.cpp
===================================================================
--- source/tools/atlas/GameInterface/ActorViewer.cpp
+++ source/tools/atlas/GameInterface/ActorViewer.cpp
@@ -375,7 +375,7 @@
{
CmpPtr cmpUnitMotion(m.Simulation2, m.Entity);
if (cmpUnitMotion)
- speed = cmpUnitMotion->GetWalkSpeed().ToFloat();
+ speed = cmpUnitMotion->GetTemplateSpeed().ToFloat();
else
speed = 7.f; // typical unit speed
@@ -385,7 +385,7 @@
{
CmpPtr cmpUnitMotion(m.Simulation2, m.Entity);
if (cmpUnitMotion)
- speed = cmpUnitMotion->GetRunSpeed().ToFloat();
+ speed = cmpUnitMotion->GetTemplateSpeed().ToFloat();
else
speed = 12.f; // typical unit speed