Index: binaries/data/mods/public/simulation/components/ResourceGatherer.js
===================================================================
--- binaries/data/mods/public/simulation/components/ResourceGatherer.js
+++ binaries/data/mods/public/simulation/components/ResourceGatherer.js
@@ -30,6 +30,11 @@
"" +
Resources.BuildSchema("positiveDecimal") +
"";
+/*
+ * Call interval will be determined by gather rate,
+ * so always gather integer amount.
+ */
+ResourceGatherer.prototype.GATHER_AMOUNT = 1;
ResourceGatherer.prototype.Init = function()
{
@@ -161,52 +166,123 @@
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.StartGathering = function(target, callerIID)
+{
+ if (this.target)
+ this.StopGathering();
+
+ let rate = this.GetTargetGatherRate(target);
+ if (!rate)
+ return false;
+
+ let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
+ if (!cmpResourceSupply || !cmpResourceSupply.AddActiveGatherer(this.entity))
+ return false;
+
+ let genericResourceType = cmpResourceSupply.GetType().generic;
+ this.AddToPlayerCounter(genericResourceType);
+
+ // 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(genericResourceType))
+ this.DropResources();
+
+ if (!this.carrying[genericResourceType])
+ this.carrying[genericResourceType] = 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.PerformGather = function(target)
+ResourceGatherer.prototype.StopGathering = function(reason)
{
- if (!this.GetTargetGatherRate(target))
- return { "exhausted": true };
+ if (this.timer)
+ {
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.timer);
+ delete this.timer;
+ }
- let gatherAmount = 1;
+ if (this.target)
+ {
+ let cmpResourceSupply = Engine.QueryInterface(this.target, IID_ResourceSupply);
+ if (cmpResourceSupply)
+ cmpResourceSupply.RemoveGatherer(this.entity);
+ this.RemoveFromPlayerCounter();
+ delete this.target;
+ }
- let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
- let type = cmpResourceSupply.GetType();
+ // The callerIID component may start gathering again,
+ // replacing the callerIID, hence save that.
+ let callerIID = this.callerIID;
+ delete this.callerIID;
- // Initialise the carried count if necessary
- if (!this.carrying[type.generic])
- this.carrying[type.generic] = 0;
+ if (reason && callerIID)
+ {
+ let component = Engine.QueryInterface(this.entity, callerIID);
+ if (component)
+ component.ProcessMessage(reason, null);
+ }
+};
- // Find the maximum so we won't exceed our capacity
- let maxGathered = this.GetCapacity(type.generic) - this.carrying[type.generic];
+/**
+ * Gather from our target entity.
+ * @params - data and lateness are unused.
+ */
+ResourceGatherer.prototype.PerformGather = function(data, lateness)
+{
+ if (!this.CanGather(this.target))
+ {
+ this.StopGathering("TargetInvalidated");
+ return;
+ }
+
+ if (!this.IsTargetInRange(this.target))
+ {
+ this.StopGathering("OutOfRange");
+ return;
+ }
- let status = cmpResourceSupply.TakeResources(Math.min(gatherAmount, maxGathered));
+ let cmpResourceSupply = Engine.QueryInterface(this.target, IID_ResourceSupply);
+ let type = cmpResourceSupply.GetType();
+ let maxGathered = this.GetCapacity(type.generic) - this.carrying[type.generic];
+ 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 +293,14 @@
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);
@@ -233,7 +309,7 @@
// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect
// (GetDiminishingReturns will return null). We can assume that for resources that are miraged this is the case
- // (else just add the diminishing returns data to the mirage data and remove the early return above)
+ // (else just add the diminishing returns data to the mirage data and remove the early return above).
let diminishingReturns = cmpResourceSupply.GetDiminishingReturns();
if (diminishingReturns)
rate *= diminishingReturns;
@@ -242,6 +318,15 @@
};
/**
+ * @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
* resource type or not.)
@@ -347,6 +432,17 @@
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)
+{
+ let range = this.GetRange();
+ let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
+ return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, 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)
Index: binaries/data/mods/public/simulation/components/TreasureCollecter.js
===================================================================
--- binaries/data/mods/public/simulation/components/TreasureCollecter.js
+++ binaries/data/mods/public/simulation/components/TreasureCollecter.js
@@ -69,9 +69,8 @@
}
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.
+ // The callerIID component may start collecting again,
+ // replacing the callerIID, hence save that.
let callerIID = this.callerIID;
delete this.callerIID;
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
@@ -2458,14 +2458,16 @@
"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;
+ }
+ let gatheringTarget = this.order.data.target || INVALID_ENTITY;
+ if (!this.CheckTargetRange(gatheringTarget, IID_ResourceGatherer))
+ {
+ this.ProcessMessage("OutOfRange");
return true;
}
@@ -2474,134 +2476,59 @@
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(gatheringTarget, 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.SetNextState("FINDINGNEWTARGET");
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.SetDefaultAnimationVariant();
+ this.FaceTowardsTarget(this.order.data.target);
+ this.SelectAnimation("gather_" + this.order.data.type.specific);
+ this.StartTimer(1000, 1000);
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;
+ cmpResourceGatherer.StopGathering();
this.ResetAnimation();
},
"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": {
Index: binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js
+++ binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js
@@ -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");
Index: binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js
+++ binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js
@@ -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 += "" + res + "." + subtype + "";
@@ -13,7 +13,8 @@
return {
"subtypes": {
"meat": "meat",
- "grain": "grain"
+ "grain": "grain",
+ "tree": "tree"
}
};
}
@@ -24,25 +25,36 @@
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
+Engine.LoadComponentScript("interfaces/Timer.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 +68,17 @@
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 +88,208 @@
"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 }]);
Index: binaries/data/mods/public/simulation/components/tests/test_Resources.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Resources.js
+++ binaries/data/mods/public/simulation/components/tests/test_Resources.js
@@ -38,12 +38,19 @@
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
+Engine.LoadComponentScript("interfaces/Timer.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 +106,24 @@
// 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