1
0
forked from 0ad/0ad

Various resource shuttling improvements. Fixes #672.

This was SVN commit r8811.
This commit is contained in:
Ykkrosh 2010-12-08 16:12:04 +00:00
parent 0b0b11d2e0
commit f04c2561b7
3 changed files with 205 additions and 83 deletions

View File

@ -58,7 +58,7 @@ ResourceGatherer.prototype.Schema =
ResourceGatherer.prototype.Init = function()
{
this.carrying = {}; // { type: integer amount currently carried }
this.carrying = {}; // { generic type: integer amount currently carried }
// (Note that this component supports carrying multiple types of resources,
// each with an independent capacity, but the rest of the game currently
// ensures and assumes we'll only be carrying one type at once)
@ -86,7 +86,7 @@ ResourceGatherer.prototype.GetCarryingStatus = function()
};
/**
* Returns the type of one particular resource this unit is
* Returns the generic type of one particular resource this unit is
* currently carrying, or undefined if none.
*/
ResourceGatherer.prototype.GetMainCarryingType = function()
@ -99,12 +99,16 @@ ResourceGatherer.prototype.GetMainCarryingType = function()
};
/**
* Returns the exact resource type we last picked up, in the form
* Returns the exact resource type we last picked up, as long as
* we're still carrying something similar enough, in the form
* { generic, specific }
*/
ResourceGatherer.prototype.GetLastCarriedType = function()
{
return this.lastCarriedType;
if (this.lastCarriedType.generic in this.carrying)
return this.lastCarriedType;
else
return undefined;
};
ResourceGatherer.prototype.GetGatherRates = function()

View File

@ -426,7 +426,7 @@ var UnitFsmSpec = {
"GATHER": {
"APPROACHING": {
"enter": function () {
"enter": function() {
this.SelectAnimation("move");
},
@ -443,8 +443,10 @@ var UnitFsmSpec = {
if (this.FinishOrder())
return;
// Try to find another nearby target of the same type
var nearby = this.FindNearbyResource(oldType, oldTarget);
// Try to find another nearby target of the same specific type
var nearby = this.FindNearbyResource(function (ent, type) {
return (ent != oldTarget && type.specific == oldType.specific);
});
if (nearby)
{
this.Gather(nearby, true);
@ -464,9 +466,18 @@ var UnitFsmSpec = {
"GATHERING": {
"enter": function() {
var typename = "gather_" + this.order.data.type.specific;
this.SelectAnimation(typename, false, 1.0, typename);
this.StartTimer(1000, 1000);
// We want to start the gather animation as soon as possible,
// but only if we're actually at the target and it's still alive
// (else it'll look like we're chopping empty air).
// (If it's not alive, the Timer handler will deal with sending us
// off to a different target.)
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
{
var typename = "gather_" + this.order.data.type.specific;
this.SelectAnimation(typename, false, 1.0, typename);
}
},
"leave": function() {
@ -500,61 +511,77 @@ var UnitFsmSpec = {
// return to the nearest dropsite
if (status.filled)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
// Find dropsites owned by this unit's player
var players = [];
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
players.push(cmpOwnership.GetOwner());
var dropsites = cmpRangeManager.ExecuteQuery(this.entity, 0, -1, players, IID_ResourceDropsite);
// Try to find the first (nearest) dropsite which supports this resource type
for each (var dropsite in dropsites)
var nearby = this.FindNearestDropsite(this.order.data.type.generic);
if (nearby)
{
var cmpDropsite = Engine.QueryInterface(dropsite, IID_ResourceDropsite);
if (!cmpDropsite.AcceptsType(this.order.data.type.generic))
continue;
// This dropsite is okay - return our resources to it
this.PushOrderFront("ReturnResource", { "target": dropsite });
// (Keep this Gather order on the stack so we'll
// continue gathering after returning)
this.PushOrderFront("ReturnResource", { "target": nearby });
return;
}
// Oh no, couldn't find any drop sites.
// Give up and stand here like a lemon.
// Oh no, couldn't find any drop sites. Give up on gathering.
this.FinishOrder();
}
}
else
{
// Try to follow it
// Try to follow the target
if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
{
this.SetNextState("APPROACHING");
return;
}
else
// Can't reach the target, or it doesn't exist any more
// We want to carry on gathering resources in the same area as
// the old one. So try to get close to the old resource's
// last known position
var maxRange = 8; // get close but not too close
if (this.order.data.lastPos &&
this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z,
0, maxRange))
{
// Save the current order's type in case we need it later
var oldType = this.order.data.type;
// Can't reach it, or it doesn't exist any more - give up on this order
if (this.FinishOrder())
return;
// No remaining orders - pick a useful default behaviour
// Try to find a nearby target of the same type
var nearby = this.FindNearbyResource(oldType);
if (nearby)
{
this.Gather(nearby, true);
return;
}
// Nothing else to gather - just give up
this.SetNextState("APPROACHING");
return;
}
// We're already in range, or can't get anywhere near it.
// Save the current order's type in case we need it later
var oldType = this.order.data.type;
// Give up on this order and try our next queued order
if (this.FinishOrder())
return;
// No remaining orders - pick a useful default behaviour
// Try to find a new resource of the same specific type near our current position:
var nearby = this.FindNearbyResource(function (ent, type) {
return (type.specific == oldType.specific);
});
if (nearby)
{
this.Gather(nearby, true);
return;
}
// Nothing else to gather - if we're carrying anything then we should
// drop it off, and if not then we might as well head to the dropsite
// anyway because that's a nice enough place to congregate and idle
var nearby = this.FindNearestDropsite(oldType.generic);
if (nearby)
{
this.PushOrderFront("ReturnResource", { "target": nearby });
return;
}
// No dropsites - just give up
}
},
},
@ -567,33 +594,62 @@ var UnitFsmSpec = {
// Work out what we're carrying, in order to select an appropriate animation
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
var type = cmpResourceGatherer.GetLastCarriedType();
var typename = "carry_" + type.generic;
// Special case for meat
if (type.specific == "meat")
typename = "carry_" + type.specific;
this.SelectAnimation(typename, false, this.GetWalkSpeed());
},
"MoveCompleted": function() {
var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite);
if (cmpResourceDropsite)
if (type)
{
// TODO: check the dropsite really is in range
// (we didn't get stopped before reaching it)
var typename = "carry_" + type.generic;
var dropsiteTypes = cmpResourceDropsite.GetTypes();
// Special case for meat
if (type.specific == "meat")
typename = "carry_" + type.specific;
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
cmpResourceGatherer.CommitResources(dropsiteTypes);
this.SelectAnimation(typename, false, this.GetWalkSpeed());
}
else
{
// The dropsite was destroyed or something.
// TODO: We ought to look for a new one, probably.
// We're returning empty-handed
this.SelectAnimation("move");
}
},
"MoveCompleted": function() {
// Switch back to idle animation to guarantee we won't
// get stuck with the carry animation after stopping moving
this.SelectAnimation("idle");
// Check the dropsite really is in range
// (we didn't get stopped before reaching it)
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
{
var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
// Dump any resources we can
var dropsiteTypes = cmpResourceDropsite.GetTypes();
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
cmpResourceGatherer.CommitResources(dropsiteTypes);
// Our next order should always be a Gather,
// so just switch back to that order
this.FinishOrder();
return;
}
}
// The dropsite was destroyed, or we couldn't reach it.
// Look for a new one.
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
var genericType = cmpResourceGatherer.GetMainCarryingType();
var nearby = this.FindNearestDropsite(genericType);
if (nearby)
{
this.FinishOrder();
this.PushOrderFront("ReturnResource", { "target": nearby });
return;
}
// Oh no, couldn't find any drop sites. Give up on returning.
this.FinishOrder();
},
},
@ -651,11 +707,26 @@ var UnitFsmSpec = {
if (this.CanGather(msg.data.newentity))
{
this.Gather(msg.data.newentity, true);
return;
}
else
// If this building was e.g. a farmstead, we should look for nearby
// resources we can gather
var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
// TODO: look for a nearby foundation to help with
var types = cmpResourceDropsite.GetTypes();
var nearby = this.FindNearbyResource(function (ent, type) {
return (types.indexOf(type.generic) != -1);
});
if (nearby)
{
this.Gather(nearby, true);
return;
}
}
// TODO: look for a nearby foundation to help with
},
},
@ -860,15 +931,25 @@ UnitAI.prototype.TimerHandler = function(data, lateness)
UnitFsm.ProcessMessage(this, {"type": "Timer", "data": data, "lateness": lateness});
};
/**
* Set up the UnitAI timer to run after 'offset' msecs, and then
* every 'repeat' msecs until StopTimer is called. A "Timer" message
* will be sent each time the timer runs.
*/
UnitAI.prototype.StartTimer = function(offset, repeat)
{
if (this.timer)
error("Called StartTimer when there's already an active timer");
var data = { "timerRepeat": repeat };
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "TimerHandler", offset, { "timerRepeat": repeat });
this.timer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "TimerHandler", offset, data);
};
/**
* Stop the current UnitAI timer.
*/
UnitAI.prototype.StopTimer = function()
{
if (!this.timer)
@ -955,13 +1036,12 @@ UnitAI.prototype.MustKillGatherTarget = function(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.
* Returns the entity ID of the nearest resource supply where the given
* filter returns true, 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)
UnitAI.prototype.FindNearbyResource = function(filter)
{
var range = 64; // TODO: what's a sensible number?
@ -976,18 +1056,41 @@ UnitAI.prototype.FindNearbyResource = function(requestedType, exclude)
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)
if (filter(ent, type))
return ent;
}
return undefined;
};
/**
* Returns the entity ID of the nearest resource dropsite that accepts
* the given type, or undefined if none can be found.
*/
UnitAI.prototype.FindNearestDropsite = function(genericType)
{
// Find dropsites owned by this unit's player
var players = [];
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, -1, players, IID_ResourceDropsite);
for each (var ent in nearby)
{
var cmpDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (!cmpDropsite.AcceptsType(genericType))
continue;
return ent;
}
return undefined;
};
/**
* Play a sound appropriate to the current entity.
*/
@ -1066,6 +1169,12 @@ UnitAI.prototype.MoveToPoint = function(x, z)
return cmpUnitMotion.MoveToPointRange(x, z, 0, 0);
};
UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
};
UnitAI.prototype.MoveToTarget = function(target)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
@ -1260,7 +1369,16 @@ UnitAI.prototype.Gather = function(target, queued)
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
this.AddOrder("Gather", { "target": target, "type": type }, queued);
// Remember the position of our target, if any, in case it disappears
// later and we want to head to its last known position
// (TODO: if the target moves a lot (e.g. it's an animal), maybe we
// need to update this lastPos regularly rather than just here?)
var lastPos = undefined;
var cmpPosition = Engine.QueryInterface(target, IID_Position);
if (cmpPosition && cmpPosition.IsInWorld())
lastPos = cmpPosition.GetPosition();
this.AddOrder("Gather", { "target": target, "type": type, "lastPos": lastPos }, queued);
};
UnitAI.prototype.ReturnResource = function(target, queued)

View File

@ -37,8 +37,8 @@ public:
/**
* 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.
* If the unit is already in range, or cannot move anywhere at all, or if there is
* some other error, then returns false.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
*/