Index: ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js +++ ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js @@ -31,6 +31,12 @@ 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() { this.capacities = {}; @@ -161,52 +167,133 @@ 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; + + 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; + } - // Initialise the carried count if necessary + 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.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); @@ -245,6 +332,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.) @@ -376,6 +472,16 @@ 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) Index: ps/trunk/binaries/data/mods/public/simulation/components/TreasureCollecter.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/TreasureCollecter.js +++ ps/trunk/binaries/data/mods/public/simulation/components/TreasureCollecter.js @@ -46,6 +46,10 @@ 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 @@ } 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; Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -2495,14 +2495,16 @@ "GATHERING": { "enter": function() { - this.gatheringTarget = this.order.data.target || INVALID_ENTITY; // deleted in "leave". + let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + if (!cmpResourceGatherer) + { + this.FinishOrder(); + return true; + } - // 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)) + if (!this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) { - this.SetNextState("FINDINGNEWTARGET"); + this.ProcessMessage("OutOfRange"); return true; } @@ -2511,133 +2513,47 @@ 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; + "InventoryFilled": function(msg) { + let nearestDropsite = this.FindNearestDropsite(this.order.data.type.generic); + if (!nearestDropsite) + this.FinishOrder(); - // 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. + this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false }); + }, - 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)) - { + "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"); - 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. - this.FinishOrder(); - return; - } + }, - if (status.exhausted) - this.SetNextState("FINDINGNEWTARGET"); + "TargetInvalidated": function(msg) { + this.SetNextState("FINDINGNEWTARGET"); }, }, @@ -2972,7 +2888,6 @@ return true; } this.FaceTowardsTarget(this.order.data.target); - this.SelectAnimation("collecting_treasure"); return false; }, @@ -2980,7 +2895,6 @@ let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter); if (cmpTreasureCollecter) cmpTreasureCollecter.StopCollecting(); - this.ResetAnimation(); }, "OutOfRange": function(msg) { Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js +++ ps/trunk/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,37 @@ 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 @@ 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 @@ "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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Resources.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Resources.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Resources.js @@ -38,12 +38,20 @@ 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 @@ // 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