1
0
forked from 0ad/0ad

Update UnitMotion logic, to partially simplify it.

Do all pathfinding asynchronously.
Recalculate paths when the target has moved.
Fix vertex pathfinder start point heuristic, to avoid finding
zero-length paths.
When units fail to reach a resource to gather, look for another resource
of the same type.

This was SVN commit r8751.
This commit is contained in:
Ykkrosh 2010-11-30 12:31:54 +00:00
parent 3401672887
commit ab186c2b16
8 changed files with 929 additions and 569 deletions

View File

@ -258,6 +258,9 @@ var UnitFsmSpec = {
"FORMATIONMEMBER": {
"FormationLeave": function(msg) {
// Stop moving as soon as the formation disbands
this.StopMoving();
// We're leaving the formation, so stop our FormationWalk order
if (this.FinishOrder())
return;
@ -290,7 +293,8 @@ var UnitFsmSpec = {
this.PushOrderFront("Attack", { "target": msg.data.attacker });
}
else
{ // TODO: If unit can't attack, run away
{
// TODO: If unit can't attack, run away
}
},
@ -420,7 +424,34 @@ var UnitFsmSpec = {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
"MoveCompleted": function(msg) {
if (msg.data.error)
{
// We failed to reach the target
// Save the current order's data in case we need it later
var oldType = this.order.data.type;
var oldTarget = this.order.data.target;
// Try the next queued order if there is any
if (this.FinishOrder())
return;
// Try to find another nearby target of the same type
var nearby = this.FindNearbyResource(oldType, oldTarget);
if (nearby)
{
this.Gather(nearby, true);
return;
}
// Couldn't find anything else. Just try this one again,
// maybe we'll succeed next time
this.Gather(oldTarget, true);
return;
}
// We reached the target - start gathering from it now
this.SetNextState("GATHERING");
},
},
@ -509,26 +540,11 @@ var UnitFsmSpec = {
// Try to find a nearby target of the same type
var range = 64; // TODO: what's a sensible number?
// Accept any resources owned by Gaia
var players = [0];
// Also accept resources owned by this unit's player:
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
players.push(cmpOwnership.GetOwner());
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var nearby = rangeMan.ExecuteQuery(this.entity, 0, range, players, IID_ResourceSupply);
for each (var ent in nearby)
var nearby = this.FindNearbyResource(oldType);
if (nearby)
{
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
if (type.specific == oldType.specific)
{
this.Gather(ent, true);
return;
}
this.Gather(nearby, true);
return;
}
// Nothing else to gather - just give up
@ -894,14 +910,14 @@ UnitAI.prototype.OnRangeUpdate = function(msg)
UnitAI.prototype.GetWalkSpeed = function()
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.GetWalkSpeed();
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.GetWalkSpeed();
};
UnitAI.prototype.GetRunSpeed = function()
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.GetRunSpeed();
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.GetRunSpeed();
};
/**
@ -932,6 +948,40 @@ UnitAI.prototype.MustKillGatherTarget = function(ent)
return this.TargetIsAlive(ent);
};
/**
* Returns the entity ID of the nearest resource supply of the given
* type, excluding the one with ID 'exclude',
* or undefined if none can be found.
* TODO: extend this to exclude resources that already have lots of
* gatherers.
*/
UnitAI.prototype.FindNearbyResource = function(requestedType, exclude)
{
var range = 64; // TODO: what's a sensible number?
// Accept any resources owned by Gaia
var players = [0];
// Also accept resources owned by this unit's player:
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
players.push(cmpOwnership.GetOwner());
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var nearby = rangeMan.ExecuteQuery(this.entity, 0, range, players, IID_ResourceSupply);
for each (var ent in nearby)
{
if (ent == exclude)
continue;
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
if (type.specific == requestedType.specific)
return ent;
}
return undefined;
};
/**
* Play a sound appropriate to the current entity.
*/
@ -998,23 +1048,22 @@ UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime)
cmpVisual.SetAnimationSyncOffset(actiontime);
};
UnitAI.prototype.StopMoving = function()
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.StopMoving();
};
UnitAI.prototype.MoveToPoint = function(x, z)
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.MoveToPoint(x, z);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToPointRange(x, z, 0, 0);
};
UnitAI.prototype.MoveToTarget = function(target)
{
var cmpPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpPosition)
return false;
if (!cmpPosition.IsInWorld())
return false;
var pos = cmpPosition.GetPosition();
return this.MoveToPoint(pos.x, pos.z);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
};
UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
@ -1022,8 +1071,8 @@ UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
var cmpRanged = Engine.QueryInterface(this.entity, iid);
var range = cmpRanged.GetRange(type);
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.MoveToAttackRange(target, range.min, range.max);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
UnitAI.prototype.CheckTargetRange = function(target, iid, type)
@ -1031,8 +1080,8 @@ UnitAI.prototype.CheckTargetRange = function(target, iid, type)
var cmpRanged = Engine.QueryInterface(this.entity, iid);
var range = cmpRanged.GetRange(type);
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpMotion.IsInAttackRange(target, range.min, range.max);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
};
UnitAI.prototype.GetBestAttack = function()

View File

@ -58,7 +58,8 @@ function TestFormationExiting(mode)
AddMock(unit, IID_UnitMotion, {
GetWalkSpeed: function() { return 1; },
MoveToFormationOffset: function(target, x, z) { },
MoveToAttackRange: function(target, min, max) { },
MoveToTargetRange: function(target, min, max) { },
StopMoving: function() { },
});
AddMock(unit, IID_Vision, {
@ -97,7 +98,7 @@ function TestFormationExiting(mode)
AddMock(controller, IID_UnitMotion, {
SetUnitRadius: function(r) { },
SetSpeed: function(speed) { },
MoveToPoint: function(x, z) { },
MoveToPointRange: function(x, z, minRange, maxRange) { },
});
controllerAI.OnCreate();

View File

@ -226,6 +226,8 @@ public:
virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass);
virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal);
virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, u8 passClass);
virtual void FinishAsyncRequests();

View File

@ -317,6 +317,14 @@ static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinde
}
}
CFixedVector2D CCmpPathfinder::GetNearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)
{
return NearestPointOnGoal(pos, goal);
// (It's intentional that we don't put the implementation inside this
// function, to avoid the (admittedly unmeasured and probably trivial)
// cost of a virtual call inside ComputeShortPath)
}
typedef PriorityQueueList<u16, fixed> PriorityQueue;
struct TileEdge
@ -541,14 +549,14 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, enti
edges.push_back(e3);
}
CFixedVector2D goalVec(goal.x, goal.z);
// List of obstruction vertexes (plus start/end points); we'll try to find paths through
// the graph defined by these vertexes
std::vector<Vertex> vertexes;
// Add the start point to the graph
Vertex start = { CFixedVector2D(x0, z0), fixed::Zero(), (CFixedVector2D(x0, z0) - goalVec).Length(), 0, Vertex::OPEN, QUADRANT_NONE, QUADRANT_ALL };
CFixedVector2D posStart(x0, z0);
fixed hStart = (posStart - NearestPointOnGoal(posStart, goal)).Length();
Vertex start = { posStart, fixed::Zero(), hStart, 0, Vertex::OPEN, QUADRANT_NONE, QUADRANT_ALL };
vertexes.push_back(start);
const size_t START_VERTEX_ID = 0;

File diff suppressed because it is too large Load Diff

View File

@ -129,6 +129,11 @@ public:
*/
virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass) = 0;
/**
* Returns the coordinates of the point on the goal that is closest to pos in a straight line.
*/
virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal) = 0;
/**
* Check whether the given movement line is valid and doesn't hit any obstructions
* or impassable terrain.

View File

@ -22,10 +22,9 @@
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(UnitMotion)
DEFINE_INTERFACE_METHOD_2("MoveToPoint", bool, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("IsInAttackRange", bool, ICmpUnitMotion, IsInAttackRange, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("MoveToAttackRange", bool, ICmpUnitMotion, MoveToAttackRange, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, 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_0("StopMoving", void, ICmpUnitMotion, StopMoving)
DEFINE_INTERFACE_METHOD_1("SetSpeed", void, ICmpUnitMotion, SetSpeed, fixed)

View File

@ -34,36 +34,30 @@
class ICmpUnitMotion : public IComponent
{
public:
/**
* Attempt to walk to a given point, or as close as possible.
* Attempt to walk into range of a to a given point, or as close as possible.
* If the unit cannot move anywhere at all, or if there is some other error, then
* returns false.
* Otherwise, sends a MotionChanged message and returns true; it will send another
* MotionChanged message (with speed 0) once it has reached the target or otherwise
* given up trying to reach it.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
*/
virtual bool MoveToPoint(entity_pos_t x, entity_pos_t z) = 0;
virtual bool MoveToPointRange(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 MoveToAttackRange.
* as MoveToTargetRange.
*/
virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
* Attempt to walk into range of a given target entity, or as close as possible.
* If the unit is already in range, or cannot move anywhere at all, or if there is
* some other error, then returns false.
* Otherwise, sends a MotionChanged message and returns true; it will send another
* MotionChanged message (with speed 0) once it has reached the target range (such that
* IsInAttackRange should return true) or otherwise given up trying to reach it.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
*/
virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
* See MoveToAttackRange, but the target is the given point.
*/
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0;
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.