mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 22:03:56 -07:00
Gather using ResourceGatherer instead of UnitAI.
Moves the gathering logic from UnitAI to ResourceGatherer. Makes it easier for modders to change gathering behaviour, e.g. letting structures gather. Refs. #4293 by optimising a bit. Differential revision: D2662 Comments by: @bb, @Stan, @wraitii This was SVN commit r25206.
This commit is contained in:
parent
48d6b84c62
commit
3c4a341906
6 changed files with 414 additions and 198 deletions
|
|
@ -31,6 +31,12 @@ ResourceGatherer.prototype.Schema =
|
|||
Resources.BuildSchema("positiveDecimal") +
|
||||
"</element>";
|
||||
|
||||
/*
|
||||
* Call interval will be determined by gather rate,
|
||||
* so always gather integer amount.
|
||||
*/
|
||||
ResourceGatherer.prototype.GATHER_AMOUNT = 1;
|
||||
|
||||
ResourceGatherer.prototype.Init = function()
|
||||
{
|
||||
this.capacities = {};
|
||||
|
|
@ -161,52 +167,133 @@ ResourceGatherer.prototype.GetCapacity = function(resourceType)
|
|||
ResourceGatherer.prototype.GetRange = function()
|
||||
{
|
||||
return { "max": +this.template.MaxDistance, "min": 0 };
|
||||
// maybe this should depend on the unit or target or something?
|
||||
};
|
||||
|
||||
/**
|
||||
* Gather from the target entity. This should only be called after a successful range check,
|
||||
* and if the target has a compatible ResourceSupply.
|
||||
* Call interval will be determined by gather rate, so always gather 1 amount when called.
|
||||
* @param {number} target - The target to gather from.
|
||||
* @param {number} callerIID - The IID to notify on specific events.
|
||||
* @return {boolean} - Whether we started gathering.
|
||||
*/
|
||||
ResourceGatherer.prototype.PerformGather = function(target)
|
||||
ResourceGatherer.prototype.StartGathering = function(target, callerIID)
|
||||
{
|
||||
if (!this.GetTargetGatherRate(target))
|
||||
return { "exhausted": true };
|
||||
if (this.target)
|
||||
this.StopGathering();
|
||||
|
||||
let gatherAmount = 1;
|
||||
let rate = this.GetTargetGatherRate(target);
|
||||
if (!rate)
|
||||
return false;
|
||||
|
||||
let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
|
||||
let type = cmpResourceSupply.GetType();
|
||||
if (!cmpResourceSupply || !cmpResourceSupply.AddActiveGatherer(this.entity))
|
||||
return false;
|
||||
|
||||
// Initialise the carried count if necessary
|
||||
let resourceType = cmpResourceSupply.GetType();
|
||||
|
||||
// If we've already got some resources but they're the wrong type,
|
||||
// drop them first to ensure we're only ever carrying one type.
|
||||
if (this.IsCarryingAnythingExcept(resourceType.generic))
|
||||
this.DropResources();
|
||||
this.AddToPlayerCounter(resourceType.generic);
|
||||
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("gather_" + resourceType.specific, false, 1.0);
|
||||
|
||||
// Calculate timing based on gather rates.
|
||||
// This allows the gather rate to control how often we gather, instead of how much.
|
||||
let timing = 1000 / rate;
|
||||
|
||||
this.target = target;
|
||||
this.callerIID = callerIID;
|
||||
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.timer = cmpTimer.SetInterval(this.entity, IID_ResourceGatherer, "PerformGather", timing, timing, null);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} reason - The reason why we stopped gathering used to notify the caller.
|
||||
*/
|
||||
ResourceGatherer.prototype.StopGathering = function(reason)
|
||||
{
|
||||
if (this.timer)
|
||||
{
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.CancelTimer(this.timer);
|
||||
delete this.timer;
|
||||
}
|
||||
|
||||
if (this.target)
|
||||
{
|
||||
let cmpResourceSupply = Engine.QueryInterface(this.target, IID_ResourceSupply);
|
||||
if (cmpResourceSupply)
|
||||
cmpResourceSupply.RemoveGatherer(this.entity);
|
||||
this.RemoveFromPlayerCounter();
|
||||
delete this.target;
|
||||
}
|
||||
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("idle", false, 1.0);
|
||||
|
||||
// The callerIID component may start gathering again,
|
||||
// replacing the callerIID, hence save that.
|
||||
let callerIID = this.callerIID;
|
||||
delete this.callerIID;
|
||||
|
||||
if (reason && callerIID)
|
||||
{
|
||||
let component = Engine.QueryInterface(this.entity, callerIID);
|
||||
if (component)
|
||||
component.ProcessMessage(reason, null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gather from our target entity.
|
||||
* @params - data and lateness are unused.
|
||||
*/
|
||||
ResourceGatherer.prototype.PerformGather = function(data, lateness)
|
||||
{
|
||||
let cmpResourceSupply = Engine.QueryInterface(this.target, IID_ResourceSupply);
|
||||
if (!cmpResourceSupply || cmpResourceSupply.GetCurrentAmount() <= 0)
|
||||
{
|
||||
this.StopGathering("TargetInvalidated");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.IsTargetInRange(this.target))
|
||||
{
|
||||
this.StopGathering("OutOfRange");
|
||||
return;
|
||||
}
|
||||
|
||||
// ToDo: Enable entities to keep facing a target.
|
||||
Engine.QueryInterface(this.entity, IID_UnitAI)?.FaceTowardsTarget(this.target);
|
||||
|
||||
let type = cmpResourceSupply.GetType();
|
||||
if (!this.carrying[type.generic])
|
||||
this.carrying[type.generic] = 0;
|
||||
|
||||
// Find the maximum so we won't exceed our capacity
|
||||
let maxGathered = this.GetCapacity(type.generic) - this.carrying[type.generic];
|
||||
|
||||
let status = cmpResourceSupply.TakeResources(Math.min(gatherAmount, maxGathered));
|
||||
|
||||
let status = cmpResourceSupply.TakeResources(Math.min(this.GATHER_AMOUNT, maxGathered));
|
||||
this.carrying[type.generic] += status.amount;
|
||||
|
||||
this.lastCarriedType = type;
|
||||
|
||||
// Update stats of how much the player collected.
|
||||
// (We have to do it here rather than at the dropsite, because we
|
||||
// need to know what subtype it was)
|
||||
// need to know what subtype it was.)
|
||||
let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
|
||||
if (cmpStatisticsTracker)
|
||||
cmpStatisticsTracker.IncreaseResourceGatheredCounter(type.generic, status.amount, type.specific);
|
||||
|
||||
Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() });
|
||||
|
||||
|
||||
return {
|
||||
"amount": status.amount,
|
||||
"exhausted": status.exhausted,
|
||||
"filled": this.carrying[type.generic] >= this.GetCapacity(type.generic)
|
||||
};
|
||||
if (!this.CanCarryMore(type.generic))
|
||||
this.StopGathering("InventoryFilled");
|
||||
else if (status.exhausted)
|
||||
this.StopGathering("TargetInvalidated");
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -217,14 +304,14 @@ ResourceGatherer.prototype.PerformGather = function(target)
|
|||
ResourceGatherer.prototype.GetTargetGatherRate = function(target)
|
||||
{
|
||||
let cmpResourceSupply = QueryMiragedInterface(target, IID_ResourceSupply);
|
||||
if (!cmpResourceSupply)
|
||||
if (!cmpResourceSupply || cmpResourceSupply.GetCurrentAmount() <= 0)
|
||||
return 0;
|
||||
|
||||
let type = cmpResourceSupply.GetType();
|
||||
|
||||
let rate = 0;
|
||||
if (type.specific)
|
||||
rate = this.GetGatherRate(type.generic+"."+type.specific);
|
||||
rate = this.GetGatherRate(type.generic + "." + type.specific);
|
||||
if (rate == 0 && type.generic)
|
||||
rate = this.GetGatherRate(type.generic);
|
||||
|
||||
|
|
@ -244,6 +331,15 @@ ResourceGatherer.prototype.CanGather = function(target)
|
|||
return this.GetTargetGatherRate(target) > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} target - The entity ID of the target to check.
|
||||
* @return {boolean} - Whether we can gather from the target.
|
||||
*/
|
||||
ResourceGatherer.prototype.CanGather = function(target)
|
||||
{
|
||||
return this.GetTargetGatherRate(target) > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether this unit can carry more of the given type of resource.
|
||||
* (This ignores whether the unit is actually able to gather that
|
||||
|
|
@ -376,6 +472,16 @@ ResourceGatherer.prototype.RemoveFromPlayerCounter = function(playerid)
|
|||
delete this.lastGathered;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} - The entity ID of the target to check.
|
||||
* @return {boolean} - Whether this entity is in range of its target.
|
||||
*/
|
||||
ResourceGatherer.prototype.IsTargetInRange = function(target)
|
||||
{
|
||||
return Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager).
|
||||
IsInTargetRange(this.entity, target, 0, +this.template.MaxDistance, false);
|
||||
};
|
||||
|
||||
// Since we cache gather rates, we need to make sure we update them when tech changes.
|
||||
// and when our owner change because owners can had different techs.
|
||||
ResourceGatherer.prototype.OnValueModification = function(msg)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ TreasureCollecter.prototype.StartCollecting = function(target, callerIID)
|
|||
if (!cmpTreasure || !cmpTreasure.IsAvailable())
|
||||
return false;
|
||||
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("collecting_treasure", false, 1.0);
|
||||
|
||||
this.target = target;
|
||||
this.callerIID = callerIID;
|
||||
|
||||
|
|
@ -69,9 +73,12 @@ TreasureCollecter.prototype.StopCollecting = function(reason)
|
|||
}
|
||||
delete this.target;
|
||||
|
||||
// The callerIID component may start gathering again,
|
||||
// replacing the callerIID, which gets deleted after
|
||||
// the callerIID has finished. Hence save the data.
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("idle", false, 1.0);
|
||||
|
||||
// The callerIID component may start collecting again,
|
||||
// replacing the callerIID, hence save that.
|
||||
let callerIID = this.callerIID;
|
||||
delete this.callerIID;
|
||||
|
||||
|
|
|
|||
|
|
@ -2495,14 +2495,16 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
|
||||
"GATHERING": {
|
||||
"enter": function() {
|
||||
this.gatheringTarget = this.order.data.target || INVALID_ENTITY; // deleted in "leave".
|
||||
|
||||
// Check if the resource is full.
|
||||
// Will only be added if we're not already in.
|
||||
let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (!cmpSupply || !cmpSupply.AddActiveGatherer(this.entity))
|
||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
if (!cmpResourceGatherer)
|
||||
{
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
this.FinishOrder();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
|
||||
{
|
||||
this.ProcessMessage("OutOfRange");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2511,134 +2513,48 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
this.order.data.force = false;
|
||||
this.order.data.autoharvest = true;
|
||||
|
||||
// Calculate timing based on gather rates
|
||||
// This allows the gather rate to control how often we gather, instead of how much.
|
||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
let rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget);
|
||||
|
||||
if (!rate)
|
||||
if (!cmpResourceGatherer.StartGathering(this.order.data.target, IID_UnitAI))
|
||||
{
|
||||
// Try to find another target if the current one stopped existing
|
||||
if (!Engine.QueryInterface(this.gatheringTarget, IID_Identity))
|
||||
{
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return true;
|
||||
}
|
||||
|
||||
// No rate, give up on gathering
|
||||
this.FinishOrder();
|
||||
this.ProcessMessage("TargetInvalidated");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scale timing interval based on rate, and start timer
|
||||
// The offset should be at least as long as the repeat time so we use the same value for both.
|
||||
let offset = 1000 / rate;
|
||||
this.StartTimer(offset, offset);
|
||||
|
||||
// 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.gatheringTarget, IID_ResourceGatherer))
|
||||
{
|
||||
this.SetDefaultAnimationVariant();
|
||||
this.FaceTowardsTarget(this.order.data.target);
|
||||
this.SelectAnimation("gather_" + this.order.data.type.specific);
|
||||
cmpResourceGatherer.AddToPlayerCounter(this.order.data.type.generic);
|
||||
}
|
||||
this.FaceTowardsTarget(this.order.data.target);
|
||||
return false;
|
||||
},
|
||||
|
||||
"leave": function() {
|
||||
this.StopTimer();
|
||||
|
||||
// Don't use ownership because this is called after a conversion/resignation
|
||||
// and the ownership would be invalid then.
|
||||
let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (cmpSupply)
|
||||
cmpSupply.RemoveGatherer(this.entity);
|
||||
|
||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
if (cmpResourceGatherer)
|
||||
cmpResourceGatherer.RemoveFromPlayerCounter();
|
||||
|
||||
delete this.gatheringTarget;
|
||||
|
||||
this.ResetAnimation();
|
||||
cmpResourceGatherer.StopGathering();
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
let resourceTemplate = this.order.data.template;
|
||||
let resourceType = this.order.data.type;
|
||||
|
||||
// TODO: we are leaking information here - if the target died in FOW, we'll know it's dead
|
||||
// straight away.
|
||||
// Seems one would have to listen to ownership changed messages to make it work correctly
|
||||
// but that's likely prohibitively expansive performance wise.
|
||||
|
||||
let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
// If we can't gather from the target, find a new one.
|
||||
if (!cmpSupply || !cmpSupply.IsAvailableTo(this.entity) ||
|
||||
!this.CanGather(this.gatheringTarget))
|
||||
{
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer))
|
||||
{
|
||||
// Try to follow the target
|
||||
if (this.MoveToTargetRange(this.gatheringTarget, IID_ResourceGatherer))
|
||||
this.SetNextState("APPROACHING");
|
||||
// Our target is no longer visible - go to its last known position first
|
||||
// and then hopefully it will become visible.
|
||||
else if (!this.CheckTargetVisible(this.gatheringTarget) && this.order.data.lastPos)
|
||||
this.PushOrderFront("Walk", {
|
||||
"x": this.order.data.lastPos.x,
|
||||
"z": this.order.data.lastPos.z,
|
||||
"force": this.order.data.force
|
||||
});
|
||||
else
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
|
||||
// If we've already got some resources but they're the wrong type,
|
||||
// drop them first to ensure we're only ever carrying one type
|
||||
if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic))
|
||||
cmpResourceGatherer.DropResources();
|
||||
|
||||
this.FaceTowardsTarget(this.order.data.target);
|
||||
|
||||
let status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
|
||||
|
||||
if (status.filled)
|
||||
{
|
||||
let nearestDropsite = this.FindNearestDropsite(resourceType.generic);
|
||||
if (nearestDropsite)
|
||||
{
|
||||
// (Keep this Gather order on the stack so we'll
|
||||
// continue gathering after returning)
|
||||
// However mark our target as invalid if it's exhausted, so we don't waste time
|
||||
// trying to gather from it.
|
||||
if (status.exhausted)
|
||||
this.order.data.target = INVALID_ENTITY;
|
||||
this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false });
|
||||
return;
|
||||
}
|
||||
|
||||
// Oh no, couldn't find any drop sites. Give up on gathering.
|
||||
"InventoryFilled": function(msg) {
|
||||
let nearestDropsite = this.FindNearestDropsite(this.order.data.type.generic);
|
||||
if (!nearestDropsite)
|
||||
this.FinishOrder();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.exhausted)
|
||||
this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false });
|
||||
},
|
||||
|
||||
"OutOfRange": function(msg) {
|
||||
if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
|
||||
this.SetNextState("APPROACHING");
|
||||
// Our target is no longer visible - go to its last known position first
|
||||
// and then hopefully it will become visible.
|
||||
else if (!this.CheckTargetVisible(this.order.data.target) && this.order.data.lastPos)
|
||||
this.PushOrderFront("Walk", {
|
||||
"x": this.order.data.lastPos.x,
|
||||
"z": this.order.data.lastPos.z,
|
||||
"force": this.order.data.force
|
||||
});
|
||||
else
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
},
|
||||
|
||||
"TargetInvalidated": function(msg) {
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
},
|
||||
},
|
||||
|
||||
"FINDINGNEWTARGET": {
|
||||
|
|
@ -2972,7 +2888,6 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
return true;
|
||||
}
|
||||
this.FaceTowardsTarget(this.order.data.target);
|
||||
this.SelectAnimation("collecting_treasure");
|
||||
return false;
|
||||
},
|
||||
|
||||
|
|
@ -2980,7 +2895,6 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||
let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter);
|
||||
if (cmpTreasureCollecter)
|
||||
cmpTreasureCollecter.StopCollecting();
|
||||
this.ResetAnimation();
|
||||
},
|
||||
|
||||
"OutOfRange": function(msg) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
Engine.RegisterInterface("ResourceGatherer");
|
||||
|
||||
/**
|
||||
* Message of the form { "to": [{ "type": string, "amount": number, "max":number }] }
|
||||
* Message of the form { "to": [{ "type": string, "amount": number, "max": number }] }
|
||||
* sent from ResourceGatherer component whenever the amount of carried resources changes.
|
||||
*/
|
||||
Engine.RegisterMessageType("ResourceCarryingChanged");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
Resources = {
|
||||
"BuildSchema": () => {
|
||||
let schema = "";
|
||||
for (let res of ["food", "metal"])
|
||||
for (let res of ["food", "metal", "wood"])
|
||||
{
|
||||
for (let subtype in ["meat", "grain"])
|
||||
schema += "<value>" + res + "." + subtype + "</value>";
|
||||
|
|
@ -13,7 +13,8 @@ Resources = {
|
|||
return {
|
||||
"subtypes": {
|
||||
"meat": "meat",
|
||||
"grain": "grain"
|
||||
"grain": "grain",
|
||||
"tree": "tree"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -24,25 +25,37 @@ Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
|
|||
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
|
||||
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
|
||||
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||
Engine.LoadComponentScript("ResourceGatherer.js");
|
||||
Engine.LoadComponentScript("Timer.js");
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => true
|
||||
});
|
||||
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oVal);
|
||||
Engine.RegisterGlobal("QueryOwnerInterface", () => {});
|
||||
let cmpTimer;
|
||||
|
||||
const entity = 11;
|
||||
const target = 12;
|
||||
const gathererID = 11;
|
||||
const dropsiteID = 12;
|
||||
const supplyID = 13;
|
||||
|
||||
let template = {
|
||||
"MaxDistance": "10",
|
||||
"BaseSpeed": "1",
|
||||
"Rates": {
|
||||
"food.grain": "1"
|
||||
"food.grain": "1",
|
||||
"wood.tree": "2"
|
||||
},
|
||||
"Capacities": {
|
||||
"food": "10"
|
||||
"food": "10",
|
||||
"wood": "20"
|
||||
}
|
||||
};
|
||||
|
||||
let cmpResourceGatherer = ConstructComponent(entity, "ResourceGatherer", template);
|
||||
let cmpResourceGatherer = ConstructComponent(gathererID, "ResourceGatherer", template);
|
||||
cmpResourceGatherer.RecalculateGatherRates();
|
||||
cmpResourceGatherer.RecalculateCapacities();
|
||||
|
||||
|
|
@ -56,44 +69,17 @@ TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [ {
|
|||
cmpResourceGatherer.DropResources();
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
|
||||
|
||||
// Test gathering.
|
||||
AddMock(target, IID_ResourceSupply, {
|
||||
"GetType": () => {
|
||||
return {
|
||||
"generic": "food",
|
||||
"specific": "grain"
|
||||
};
|
||||
},
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": false
|
||||
};
|
||||
},
|
||||
"GetDiminishingReturns": () => null
|
||||
});
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.PerformGather(target), {
|
||||
"amount": 1,
|
||||
"exhausted": false,
|
||||
"filled": false
|
||||
});
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [{
|
||||
"type": "food",
|
||||
"amount": 1,
|
||||
"max": 10
|
||||
}]);
|
||||
|
||||
// Test committing resources.
|
||||
AddMock(target, IID_ResourceDropsite, {
|
||||
AddMock(dropsiteID, IID_ResourceDropsite, {
|
||||
"ReceiveResources": (resources, ent) => {
|
||||
return {
|
||||
"food": resources.food
|
||||
};
|
||||
}
|
||||
});
|
||||
cmpResourceGatherer.GiveResources([{ "type": "food", "amount": 1 }]);
|
||||
|
||||
cmpResourceGatherer.CommitResources(target);
|
||||
cmpResourceGatherer.CommitResources(dropsiteID);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
|
||||
|
||||
cmpResourceGatherer.GiveResources([{
|
||||
|
|
@ -103,9 +89,208 @@ cmpResourceGatherer.GiveResources([{
|
|||
"type": "wood",
|
||||
"amount": 1
|
||||
}]);
|
||||
cmpResourceGatherer.CommitResources(target);
|
||||
cmpResourceGatherer.CommitResources(dropsiteID);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [ {
|
||||
"type": "wood",
|
||||
"amount": 1,
|
||||
"max": 0
|
||||
"max": 20
|
||||
}]);
|
||||
cmpResourceGatherer.DropResources();
|
||||
|
||||
|
||||
function reset()
|
||||
{
|
||||
cmpResourceGatherer = ConstructComponent(gathererID, "ResourceGatherer", template);
|
||||
cmpResourceGatherer.OnGlobalInitGame(); // Force updating values.
|
||||
cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", null);
|
||||
}
|
||||
|
||||
// Test normal gathering.
|
||||
reset();
|
||||
|
||||
// Supply is empty.
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 0
|
||||
});
|
||||
|
||||
// Supply is wrong type.
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 1,
|
||||
"GetType": () => ({ "generic": "bogus" }),
|
||||
"GetDiminishingReturns": () => 1
|
||||
|
||||
});
|
||||
TS_ASSERT(!cmpResourceGatherer.StartGathering(supplyID));
|
||||
TS_ASSERT(!cmpResourceGatherer.StartGathering(supplyID));
|
||||
|
||||
// Resource supply is full (or something else).
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => false,
|
||||
"GetCurrentAmount": () => 1,
|
||||
"GetType": () => ({ "generic": "food" }),
|
||||
"GetDiminishingReturns": () => 1
|
||||
});
|
||||
TS_ASSERT(!cmpResourceGatherer.StartGathering(supplyID));
|
||||
|
||||
|
||||
// Supply is non-empty and right type.
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 2,
|
||||
"GetType": () => ({ "generic": "food", "specific": "grain" }),
|
||||
"GetDiminishingReturns": () => 1,
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": false
|
||||
};
|
||||
},
|
||||
"RemoveGatherer": () => {}
|
||||
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 1, "max": 10 }]);
|
||||
TS_ASSERT_EQUALS(cmpResourceGatherer.GetMainCarryingType(), "food");
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetLastCarriedType(), { "generic": "food", "specific": "grain" });
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 2, "max": 10 }]);
|
||||
cmpResourceGatherer.StopGathering();
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 2, "max": 10 }]);
|
||||
TS_ASSERT(cmpResourceGatherer.IsCarrying("food"));
|
||||
TS_ASSERT(cmpResourceGatherer.CanCarryMore("food"));
|
||||
|
||||
|
||||
// Test that when gathering a second type the first gathered type is ditched.
|
||||
reset();
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 2,
|
||||
"GetType": () => ({ "generic": "food", "specific": "grain" }),
|
||||
"GetDiminishingReturns": () => 1,
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": false
|
||||
};
|
||||
},
|
||||
"RemoveGatherer": () => {}
|
||||
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 1, "max": 10 }]);
|
||||
cmpResourceGatherer.StopGathering();
|
||||
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 3,
|
||||
"GetType": () => ({ "generic": "wood", "specific": "tree" }),
|
||||
"GetDiminishingReturns": () => 1,
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": false
|
||||
};
|
||||
},
|
||||
"RemoveGatherer": () => {}
|
||||
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "wood", "amount": 2, "max": 20 }]);
|
||||
|
||||
TS_ASSERT(!cmpResourceGatherer.IsCarrying("food"));
|
||||
TS_ASSERT(cmpResourceGatherer.CanCarryMore("food"));
|
||||
TS_ASSERT(cmpResourceGatherer.IsCarrying("wood"));
|
||||
TS_ASSERT(cmpResourceGatherer.CanCarryMore("wood"));
|
||||
|
||||
|
||||
// Test that we stop gathering when the target is exhausted.
|
||||
reset();
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 1,
|
||||
"GetType": () => ({ "generic": "food", "specific": "grain" }),
|
||||
"GetDiminishingReturns": () => 1,
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": true
|
||||
};
|
||||
},
|
||||
"RemoveGatherer": () => {}
|
||||
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 1, "max": 10 }]);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 1, "max": 10 }]);
|
||||
|
||||
|
||||
// Test that we stop gathering when we are filled.
|
||||
reset();
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 11,
|
||||
"GetType": () => ({ "generic": "food", "specific": "grain" }),
|
||||
"GetDiminishingReturns": () => 1,
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": false
|
||||
};
|
||||
},
|
||||
"RemoveGatherer": () => {}
|
||||
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 10 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 10, "max": 10 }]);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 10, "max": 10 }]);
|
||||
|
||||
|
||||
// Test that starting to gather twice does not add resources at twice the speed.
|
||||
reset();
|
||||
AddMock(supplyID, IID_ResourceSupply, {
|
||||
"AddActiveGatherer": () => true,
|
||||
"GetCurrentAmount": () => 3,
|
||||
"GetType": () => ({ "generic": "food", "specific": "grain" }),
|
||||
"GetDiminishingReturns": () => 1,
|
||||
"TakeResources": (amount) => {
|
||||
return {
|
||||
"amount": amount,
|
||||
"exhausted": false
|
||||
};
|
||||
},
|
||||
"RemoveGatherer": () => {}
|
||||
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 1, "max": 10 }]);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 2, "max": 10 }]);
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supplyID));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 3, "max": 10 }]);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
|
||||
[{ "type": "food", "amount": 4, "max": 10 }]);
|
||||
|
|
|
|||
|
|
@ -38,12 +38,20 @@ Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
|
|||
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
|
||||
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
|
||||
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||
Engine.LoadComponentScript("Player.js");
|
||||
Engine.LoadComponentScript("ResourceDropsite.js");
|
||||
Engine.LoadComponentScript("ResourceGatherer.js");
|
||||
Engine.LoadComponentScript("ResourceSupply.js");
|
||||
Engine.LoadComponentScript("Timer.js");
|
||||
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oVal);
|
||||
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", null);
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => true
|
||||
});
|
||||
|
||||
const owner = 1;
|
||||
const gatherer = 11;
|
||||
|
|
@ -99,28 +107,24 @@ TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
|
|||
|
||||
// Test gathering.
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.PerformGather(supply), {
|
||||
"amount": 1,
|
||||
"exhausted": false,
|
||||
"filled": false
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supply));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceSupply.GetCurrentAmount(), 999);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [{
|
||||
"type": "food",
|
||||
"amount": 1,
|
||||
"max": 10
|
||||
}]);
|
||||
cmpResourceGatherer.StopGathering();
|
||||
|
||||
// Test committing resources.
|
||||
|
||||
cmpResourceGatherer.CommitResources(dropsite);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.PerformGather(supply), {
|
||||
"amount": 1,
|
||||
"exhausted": false,
|
||||
"filled": false
|
||||
});
|
||||
TS_ASSERT(cmpResourceGatherer.StartGathering(supply));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
cmpResourceGatherer.StopGathering();
|
||||
cmpResourceGatherer.GiveResources([{
|
||||
"type": "wood",
|
||||
"amount": 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue