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
@@ -184,25 +184,109 @@
};
/**
+ * Starts gathering on the specified target.
+ * @param {number} target - The target to gather from.
+ * @return {number} - The gathering rate.
+ */
+ResourceGatherer.prototype.StartGathering = function(target)
+{
+ // We were already gathering!
+ if (this.target)
+ this.StopGathering();
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (!cmpOwnership)
+ return 0;
+
+ // Check if the resource is full.
+ // We'll only be added if we're not already in.
+ let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
+ if (!cmpResourceSupply || !cmpResourceSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
+ return 0;
+
+ let rate = this.GetTargetGatherRate(target);
+ if (!rate)
+ return 0;
+
+ // Try to gather a treasure.
+ if (this.TryInstantGather(target))
+ return 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;
+
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.gatherTimer = cmpTimer.SetInterval(this.entity, IID_ResourceGatherer, "PerformGather", timing, timing, null);
+
+ return rate;
+};
+
+/**
+ * Stop gathering.
+ */
+ResourceGatherer.prototype.StopGathering = function()
+{
+ if (this.gatherTimer)
+ {
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.gatherTimer);
+ delete this.gatherTimer;
+ }
+
+ if (!this.target)
+ return;
+
+ let cmpResourceSupply = Engine.QueryInterface(this.target, IID_ResourceSupply);
+ if (cmpResourceSupply)
+ cmpResourceSupply.RemoveGatherer(this.entity);
+
+ delete this.target;
+};
+
+/**
* 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.
*/
-ResourceGatherer.prototype.PerformGather = function(target)
+ResourceGatherer.prototype.PerformGather = function()
{
- if (!this.GetTargetGatherRate(target))
- return { "exhausted": true };
+ if (!this.target)
+ {
+ this.StopGathering();
+ return;
+ }
+ if (!this.GetTargetGatherRate(this.target))
+ {
+ this.StopGathering();
+ Engine.PostMessage(this.entity, MT_GatheringStateChanged, {
+ "exhausted": true,
+ "filled": undefined
+ });
+ return;
+ }
- let gatherAmount = 1;
+ let cmpResourceSupply = Engine.QueryInterface(this.target, IID_ResourceSupply);
+ if (!cmpResourceSupply)
+ {
+ this.StopGathering();
+ return;
+ }
- let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
+ let gatherAmount = 1;
let type = cmpResourceSupply.GetType();
- // Initialise the carried count if necessary
+ // 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(type.generic))
+ this.DropResources();
+
+ // Initialise the carried count if necessary.
if (!this.carrying[type.generic])
this.carrying[type.generic] = 0;
- // Find the maximum so we won't exceed our capacity
+ // 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));
@@ -213,19 +297,22 @@
// 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)
- };
+ let filled = !this.CanCarryMore(type.generic);
+ if (status.exhausted || filled)
+ {
+ this.StopGathering();
+ Engine.PostMessage(this.entity, MT_GatheringStateChanged, {
+ "exhausted": status.exhausted,
+ "filled": filled
+ });
+ }
};
/**
@@ -236,14 +323,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);
@@ -252,7 +339,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;
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
@@ -2230,31 +2230,25 @@
"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 cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
- let cmpSupply;
- if (cmpOwnership)
- cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
- if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
- {
- this.SetNextState("FINDINGNEWTARGET");
- return true;
- }
-
// If this order was forced, the player probably gave it, but now we've reached the target
// switch to an unforced order (can be interrupted by attacks)
this.order.data.force = false;
this.order.data.autoharvest = true;
+ let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
+ if (!cmpResourceGatherer)
+ {
+ this.FinishOrder();
+ return 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);
+ let rate = cmpResourceGatherer.StartGathering(this.gatheringTarget);
if (!rate)
{
- // Try to find another target if the current one stopped existing
+ // Try to find another target if the current one stopped existing.
if (!Engine.QueryInterface(this.gatheringTarget, IID_Identity))
{
this.SetNextState("FINDINGNEWTARGET");
@@ -2289,33 +2283,62 @@
"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.StopGathering();
delete this.gatheringTarget;
this.ResetAnimation();
},
+ "GatheringStateChanged": function(msg) {
+ let status = msg.data;
+
+ // If we've collected as many resources as possible,
+ // return to the nearest dropsite.
+ if (status.filled)
+ {
+ let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
+ if (!cmpResourceGatherer)
+ {
+ this.FinishOrder();
+ return;
+ }
+
+ let nearestDropsite = this.FindNearestDropsite(cmpResourceGatherer.GetLastCarriedType().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 dropsites. Give up on gathering.
+ this.FinishOrder();
+ return;
+ }
+
+ // Find a new target if the current one is exhausted.
+ if (status.exhausted)
+ this.SetNextState("FINDINGNEWTARGET");
+ },
+
"Timer": function(msg) {
let resourceTemplate = this.order.data.template;
let resourceType = this.order.data.type;
- let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
- if (!cmpOwnership)
- return;
-
// 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.IsAvailable(cmpOwnership.GetOwner(), this.entity) ||
- !this.CanGather(this.gatheringTarget))
+ if (!this.CanGather(this.gatheringTarget))
{
this.SetNextState("FINDINGNEWTARGET");
return;
@@ -2339,49 +2362,7 @@
return;
}
- // Gather the resources:
-
- let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
-
- // Try to gather treasure
- if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget))
- return;
-
- // 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);
-
- // Collect from the target
- let status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
-
- // If we've collected as many resources as possible,
- // return to the nearest dropsite
- 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.
- this.FinishOrder();
- return;
- }
-
- // Find a new target if the current one is exhausted
- if (status.exhausted)
- this.SetNextState("FINDINGNEWTARGET");
},
},
@@ -4136,6 +4117,11 @@
this.UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed});
};
+UnitAI.prototype.OnGatheringStateChanged = function(msg)
+{
+ this.UnitFsm.ProcessMessage(this, { "type": "GatheringStateChanged", "data": msg });
+};
+
//// Helper functions to be called by the FSM ////
UnitAI.prototype.GetWalkSpeed = function()
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,13 @@
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");
+
+/**
+ * Message of the form { "exhausted": {boolean}, "filled": {boolean} }
+ * sent from ResourceGatherer component whenever it has stopped gathering.
+ */
+Engine.RegisterMessageType("GatheringStateChanged");
Index: binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js
@@ -0,0 +1,285 @@
+Resources = {
+ "BuildSchema": () => {
+ let schema = "";
+ for (let res of ["food", "wood"])
+ {
+ for (let subtype in ["meat", "grain"])
+ schema += "" + res + "." + subtype + "";
+ schema += " treasure." + res + "";
+ }
+ return "" + schema + "";
+ }
+};
+
+Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
+Engine.LoadComponentScript("interfaces/ResourceSupply.js");
+Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
+Engine.LoadComponentScript("interfaces/Timer.js");
+Engine.LoadComponentScript("interfaces/Trigger.js");
+Engine.LoadComponentScript("ResourceGatherer.js");
+Engine.LoadComponentScript("Timer.js");
+Engine.LoadHelperScript("Player.js");
+
+let ApplyValueModificationsToEntity = (valueName, value, entity) => value;
+Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity);
+let QueryOwnerInterface = () => {};
+Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
+let cmpTimer;
+
+let gathererID = 101;
+let supplyID = 102;
+
+let template = {
+ "MaxDistance": "10",
+ "BaseSpeed": "1",
+ "Rates": {
+ "food": "1",
+ "wood": "2"
+ },
+ "Capacities": {
+ "food": "10",
+ "wood": "20"
+ }
+};
+let cmpResourceGatherer = ConstructComponent(gathererID, "ResourceGatherer", template);
+function reset()
+{
+ cmpResourceGatherer = ConstructComponent(gathererID, "ResourceGatherer", template);
+ cmpResourceGatherer.RecalculateGatherRatesAndCapacities(); // Force updating values.
+ cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", {});
+};
+
+// General getters.
+TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
+TS_ASSERT_EQUALS(cmpResourceGatherer.GetMainCarryingType(), undefined);
+TS_ASSERT_EQUALS(cmpResourceGatherer.GetLastCarriedType(), undefined);
+reset();
+TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetGatherRates(), {
+ "food": 1,
+ "wood": 2
+});
+TS_ASSERT_EQUALS(cmpResourceGatherer.GetGatherRate("food"), 1);
+TS_ASSERT_EQUALS(cmpResourceGatherer.GetGatherRate("bogus"), 0);
+TS_ASSERT_EQUALS(cmpResourceGatherer.GetCapacity("wood"), 20);
+TS_ASSERT_EQUALS(cmpResourceGatherer.GetCapacity("bogus"), 0);
+TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetRange(), {
+ "max": 10,
+ "min": 0
+});
+TS_ASSERT(cmpResourceGatherer.CanCarryMore("food"));
+TS_ASSERT(!cmpResourceGatherer.CanCarryMore("bogus"));
+TS_ASSERT(!cmpResourceGatherer.IsCarrying("food"));
+TS_ASSERT(!cmpResourceGatherer.IsCarrying("bogus"));
+
+function testTreasure()
+{
+ reset();
+ AddMock(supplyID, IID_ResourceSupply, {
+ "GetType": () => { return { "generic": "bogus" }; }
+ });
+
+ TS_ASSERT(!cmpResourceGatherer.TryInstantGather(supplyID));
+
+ AddMock(supplyID, IID_ResourceSupply, {
+ "GetType": () => { return { "generic": "treasure" }; },
+ "GetCurrentAmount": () => 10,
+ "TakeResources": (value) => { return {
+ "amount": value,
+ "exhausted": true
+ }; }
+ });
+
+ TS_ASSERT(cmpResourceGatherer.TryInstantGather(supplyID));
+};
+testTreasure();
+
+function testNormalGathering()
+{
+ reset();
+ // No owner.
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 0);
+
+ AddMock(gathererID, IID_Ownership, {
+ "GetOwner": () => 1
+ });
+
+ // Resource supply is full (or something else).
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => false
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 0);
+
+ // Supply is empty.
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 0
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 0);
+
+ // Supply is wrong type.
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 1,
+ "GetType": () => { return { "generic": "bogus" }; },
+ "GetDiminishingReturns": () => 1
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 0);
+
+ // Supply is non-empty and right type.
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 2,
+ "GetType": () => { return { "generic": "food" }; },
+ "GetDiminishingReturns": () => 1,
+ "TakeResources": (amount) => { return {
+ "amount": amount,
+ "exhausted": false
+ }; },
+ "RemoveGatherer": () => {}
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 1);
+ 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" });
+ 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"));
+};
+testNormalGathering();
+
+function testSecondTypeDitchesFirst()
+{
+ reset();
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 2,
+ "GetType": () => { return { "generic": "food" }; },
+ "GetDiminishingReturns": () => 1,
+ "TakeResources": (amount) => { return {
+ "amount": amount,
+ "exhausted": false
+ }; },
+ "RemoveGatherer": () => {}
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 1);
+ cmpTimer.OnUpdate({ "turnLength": 1 });
+ TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(),
+ [{ "type": "food", "amount": 1, "max": 10 }]);
+ cmpResourceGatherer.StopGathering();
+
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 3,
+ "GetType": () => { return { "generic": "wood" }; },
+ "GetDiminishingReturns": () => 1,
+ "TakeResources": (amount) => { return {
+ "amount": amount,
+ "exhausted": false
+ }; },
+ "RemoveGatherer": () => {}
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 2);
+ 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"));
+};
+testSecondTypeDitchesFirst();
+
+function testStopIfExhausted()
+{
+ reset();
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 1,
+ "GetType": () => { return { "generic": "food" }; },
+ "GetDiminishingReturns": () => 1,
+ "TakeResources": (amount) => { return {
+ "amount": amount,
+ "exhausted": true
+ }; },
+ "RemoveGatherer": () => {}
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 1);
+ 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 }]);
+};
+testStopIfExhausted();
+
+function testStopIfFilled()
+{
+ reset();
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 11,
+ "GetType": () => { return { "generic": "food" }; },
+ "GetDiminishingReturns": () => 1,
+ "TakeResources": (amount) => { return {
+ "amount": amount,
+ "exhausted": false
+ }; },
+ "RemoveGatherer": () => {}
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 1);
+ 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 }]);
+};
+testStopIfFilled();
+
+function testAddingTwiceDoesntMatter()
+{
+ reset();
+ AddMock(supplyID, IID_ResourceSupply, {
+ "AddGatherer": () => true,
+ "GetCurrentAmount": () => 3,
+ "GetType": () => { return { "generic": "food" }; },
+ "GetDiminishingReturns": () => 1,
+ "TakeResources": (amount) => { return {
+ "amount": amount,
+ "exhausted": false
+ }; },
+ "RemoveGatherer": () => {}
+
+ });
+ TS_ASSERT_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 1);
+ 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_EQUALS(cmpResourceGatherer.StartGathering(supplyID), 1);
+ 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 }]);
+};
+testAddingTwiceDoesntMatter();