Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -466,6 +466,16 @@ "GainMultiplier": getEntityValue("Trader/GainMultiplier") }; + if (template.Upkeep) + { + ret.upkeep = { + "interval": +template.Upkeep.Interval, + "rates": {} + }; + for (let type in template.Upkeep.Rates) + ret.upkeep.rates[type] = getEntityValue("Upkeep/Rates/" + type); + } + if (template.WallSet) { ret.wallSet = { Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -702,6 +702,30 @@ }); } +function getUpkeepTooltip(template) +{ + if (!template.upkeep) + return ""; + + let resCodes = g_ResourceData.GetCodes().filter(res => !!template.upkeep.rates[res]); + if (!resCodes.length) + return ""; + + return sprintf(translate("%(label)s %(details)s"), { + "label": headerFont(translate("Upkeep:")), + "details": sprintf(translate("%(resources)s / %(time)s"), { + "resources": + resCodes.map( + res => sprintf(translate("%(resourceIcon)s %(rate)s"), { + "resourceIcon": resourceIcon(res), + "rate": template.upkeep.rates[res] + }) + ).join(" "), + "time": getSecondsString(template.upkeep.interval / 1000) + }) + }); +} + /** * Returns an array of strings for a set of wall pieces. If the pieces share * resource type requirements, output will be of the form '10 to 30 Stone', Index: binaries/data/mods/public/gui/reference/common/ReferencePage.js =================================================================== --- binaries/data/mods/public/gui/reference/common/ReferencePage.js +++ binaries/data/mods/public/gui/reference/common/ReferencePage.js @@ -63,5 +63,6 @@ getResourceSupplyTooltip, getPopulationBonusTooltip, getResourceTrickleTooltip, + getUpkeepTooltip, getLootTooltip ]; Index: binaries/data/mods/public/gui/session/selection_details.js =================================================================== --- binaries/data/mods/public/gui/session/selection_details.js +++ binaries/data/mods/public/gui/session/selection_details.js @@ -320,6 +320,7 @@ getGarrisonTooltip, getProjectilesTooltip, getResourceTrickleTooltip, + getUpkeepTooltip, getLootTooltip ].map(func => func(entState)).filter(tip => tip).join("\n"); Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -531,6 +531,13 @@ "run": cmpUnitMotion.GetWalkSpeed() * cmpUnitMotion.GetRunMultiplier() }; + let cmpUpkeep = Engine.QueryInterface(ent, IID_Upkeep); + if (cmpUpkeep) + ret.upkeep = { + "interval": cmpUpkeep.GetInterval(), + "rates": cmpUpkeep.GetRates() + }; + return ret; }; Index: binaries/data/mods/public/simulation/components/Upkeep.js =================================================================== --- binaries/data/mods/public/simulation/components/Upkeep.js +++ binaries/data/mods/public/simulation/components/Upkeep.js @@ -1,26 +1,32 @@ -function ResourceTrickle() {} +function Upkeep() {} -ResourceTrickle.prototype.Schema = - "Controls the resource trickle ability of the unit." + - "" + +Upkeep.prototype.Schema = + "Controls the resource upkeep of an entity." + + "" + Resources.BuildSchema("nonNegativeDecimal") + "" + - "" + + "" + "" + ""; -ResourceTrickle.prototype.Init = function() +Upkeep.prototype.Init = function() { - this.trickleInterval = +this.template.Interval; + this.upkeepInterval = +this.template.Interval; this.CheckTimer(); }; -ResourceTrickle.prototype.GetInterval = function() +/** + * @return {number} - The interval between resource subtractions, in ms. + */ +Upkeep.prototype.GetInterval = function() { - return this.trickleInterval; + return this.upkeepInterval; }; -ResourceTrickle.prototype.GetRates = function() +/** + * @return {Object} - The upkeep rates in the form of { "resourceName": {number} }. + */ +Upkeep.prototype.GetRates = function() { return this.rates; }; @@ -28,42 +34,56 @@ /** * @return {boolean} - Whether this entity has at least one non-zero trickle rate. */ -ResourceTrickle.prototype.ComputeRates = function() +Upkeep.prototype.ComputeRates = function() { this.rates = {}; - let hasTrickle = false; + let hasUpkeep = false; for (let resource in this.template.Rates) { - let rate = ApplyValueModificationsToEntity("ResourceTrickle/Rates/" + resource, +this.template.Rates[resource], this.entity); + let rate = ApplyValueModificationsToEntity("Upkeep/Rates/" + resource, +this.template.Rates[resource], this.entity); if (rate) { this.rates[resource] = rate; - hasTrickle = true; + hasUpkeep = true; } } - return hasTrickle; + return hasUpkeep; }; -ResourceTrickle.prototype.Trickle = function(data, lateness) +/** + * Try to subtract the needed resources. + * Data and lateness are unused. + */ +Upkeep.prototype.Trickle = function(data, lateness) { - // The player entity may also have a ResourceTrickle component - let cmpPlayer = QueryOwnerInterface(this.entity) || Engine.QueryInterface(this.entity, IID_Player); + let cmpPlayer = QueryOwnerInterface(this.entity); if (!cmpPlayer) return; - cmpPlayer.AddResources(this.rates); + if (!cmpPlayer.TrySubtractResources(this.rates)) + this.HandleInsufficientUpkeep(); }; -ResourceTrickle.prototype.OnValueModification = function(msg) +/** + * E.g. take a hitpoint, reduce CP. + */ +Upkeep.prototype.HandleInsufficientUpkeep = function() +{ +}; + +Upkeep.prototype.OnValueModification = function(msg) { - if (msg.component != "ResourceTrickle") + if (msg.component != "Upkeep") return; this.CheckTimer(); }; -ResourceTrickle.prototype.CheckTimer = function() +/** + * Recalculate the interval and update the timer accordingly. + */ +Upkeep.prototype.CheckTimer = function() { if (!this.ComputeRates()) { @@ -76,9 +96,9 @@ return; } - let oldTrickleInterval = this.trickleInterval; - this.trickleInterval = ApplyValueModificationsToEntity("ResourceTrickle/Interval", +this.template.Interval, this.entity); - if (this.trickleInterval < 0) + let oldUpkeepInterval = this.upkeepInterval; + this.upkeepInterval = ApplyValueModificationsToEntity("Upkeep/Interval", +this.template.Interval, this.entity); + if (this.upkeepInterval < 0) { let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.timer); @@ -88,20 +108,20 @@ if (this.timer) { - if (this.trickleInterval == oldTrickleInterval) + if (this.upkeepInterval == oldUpkeepInterval) return; // If the timer wasn't invalidated before (interval <= 0), just update it. - if (oldTrickleInterval > 0) + if (oldUpkeepInterval > 0) { let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.UpdateRepeatTime(this.timer, this.trickleInterval); + cmpTimer.UpdateRepeatTime(this.timer, this.upkeepInterval); return; } } let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - this.timer = cmpTimer.SetInterval(this.entity, IID_ResourceTrickle, "Trickle", this.trickleInterval, this.trickleInterval, undefined); + this.timer = cmpTimer.SetInterval(this.entity, IID_Upkeep, "Trickle", this.upkeepInterval, this.upkeepInterval, undefined); }; -Engine.RegisterComponentType(IID_ResourceTrickle, "ResourceTrickle", ResourceTrickle); +Engine.RegisterComponentType(IID_Upkeep, "Upkeep", Upkeep); Index: binaries/data/mods/public/simulation/components/interfaces/Upkeep.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/Upkeep.js +++ binaries/data/mods/public/simulation/components/interfaces/Upkeep.js @@ -1 +1 @@ -Engine.RegisterInterface("ResourceTrickle"); +Engine.RegisterInterface("Upkeep"); Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -35,6 +35,7 @@ Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("interfaces/Upgrade.js"); +Engine.LoadComponentScript("interfaces/Upkeep.js"); Engine.LoadComponentScript("interfaces/BuildingAI.js"); Engine.LoadComponentScript("GuiInterface.js"); Index: binaries/data/mods/public/simulation/components/tests/test_Upkeep.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Upkeep.js +++ binaries/data/mods/public/simulation/components/tests/test_Upkeep.js @@ -16,22 +16,23 @@ } }; -Engine.LoadComponentScript("interfaces/ResourceTrickle.js"); -Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/Player.js"); +Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); +Engine.LoadComponentScript("interfaces/Timer.js"); +Engine.LoadComponentScript("interfaces/Upkeep.js"); Engine.LoadComponentScript("Player.js"); -Engine.LoadComponentScript("ResourceTrickle.js"); Engine.LoadComponentScript("Timer.js"); +Engine.LoadComponentScript("Upkeep.js"); -// Resource Trickle requires this function to be defined before the component is built. +// Upkeep requires this function to be defined before the component is built. let ApplyValueModificationsToEntity = (valueName, currentValue, entity) => currentValue; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -let wonderEnt = 1; +let testedEnt = 10; let turnLength = 0.2; -let playerEnt = 10; +let playerEnt = 1; let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", {}); -let cmpResourceTrickle = ConstructComponent(wonderEnt, "ResourceTrickle", { +let cmpUpkeep = ConstructComponent(testedEnt, "Upkeep", { "Interval": "200", "Rates": { "food": "0", @@ -55,123 +56,166 @@ let QueryOwnerInterface = () => cmpPlayer; Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface); +Engine.RegisterGlobal("QueryPlayerIDInterface", () => null); TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 300, "metal": 300 }); -TS_ASSERT_EQUALS(cmpResourceTrickle.GetInterval(), 200); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 200); // Since there is no rate > 0, nothing should change. -TS_ASSERT_UNEVAL_EQUALS(cmpResourceTrickle.GetRates(), {}); -TS_ASSERT_EQUALS(cmpResourceTrickle.ComputeRates(), false); +TS_ASSERT_UNEVAL_EQUALS(cmpUpkeep.GetRates(), {}); +TS_ASSERT_EQUALS(cmpUpkeep.ComputeRates(), false); cmpTimer.OnUpdate({ "turnLength": turnLength }); TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 300, "metal": 300 }); -// Test that only trickling food works. +// Test that only requiring food works. ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { - if (valueName == "ResourceTrickle/Rates/food") + if (valueName == "Upkeep/Rates/food") return currentValue + 1; return currentValue; }; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -// Calling OnValueModification will reset the timer, which can then be called, thus increasing the resources of the player. -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_UNEVAL_EQUALS(cmpResourceTrickle.GetRates(), { "food": 1 }); +// Calling OnValueModification will reset the timer, which can then be called, thus decreasing the resources of the player. +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_UNEVAL_EQUALS(cmpUpkeep.GetRates(), { "food": 1 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 301, "metal": 300 }); -TS_ASSERT_EQUALS(cmpResourceTrickle.ComputeRates(), true); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 299, "metal": 300 }); +TS_ASSERT_EQUALS(cmpUpkeep.ComputeRates(), true); // Reset the trickle modification. ApplyValueModificationsToEntity = (valueName, currentValue, entity) => currentValue; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_UNEVAL_EQUALS(cmpResourceTrickle.GetRates(), {}); -TS_ASSERT_EQUALS(cmpResourceTrickle.ComputeRates(), false); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_UNEVAL_EQUALS(cmpUpkeep.GetRates(), {}); +TS_ASSERT_EQUALS(cmpUpkeep.ComputeRates(), false); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 301, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 299, "metal": 300 }); ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { - if (valueName == "ResourceTrickle/Interval") + if (valueName == "Upkeep/Interval") return currentValue + 200; - if (valueName == "ResourceTrickle/Rates/food") + if (valueName == "Upkeep/Rates/food") return currentValue + 1; return currentValue; }; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_EQUALS(cmpResourceTrickle.GetInterval(), 400); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 400); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 301, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 299, "metal": 300 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 302, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 298, "metal": 300 }); // Interval becomes a normal timer, thus cancelled after the first execution. ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { - if (valueName == "ResourceTrickle/Interval") + if (valueName == "Upkeep/Interval") return currentValue - 200; - if (valueName == "ResourceTrickle/Rates/food") + if (valueName == "Upkeep/Rates/food") return currentValue + 1; return currentValue; }; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_EQUALS(cmpResourceTrickle.GetInterval(), 0); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 0); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 302, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 298, "metal": 300 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 303, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 297, "metal": 300 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 303, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 297, "metal": 300 }); // Timer became invalidated, check whether it's recreated properly after that. ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { - if (valueName == "ResourceTrickle/Interval") + if (valueName == "Upkeep/Interval") return currentValue - 100; - if (valueName == "ResourceTrickle/Rates/food") + if (valueName == "Upkeep/Rates/food") return currentValue + 1; return currentValue; }; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_EQUALS(cmpResourceTrickle.GetInterval(), 100); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 100); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 305, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 295, "metal": 300 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 307, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 293, "metal": 300 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 309, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 291, "metal": 300 }); // Value is now invalid, timer should be cancelled. ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { - if (valueName == "ResourceTrickle/Interval") + if (valueName == "Upkeep/Interval") return currentValue - 201; - if (valueName == "ResourceTrickle/Rates/food") + if (valueName == "Upkeep/Rates/food") return currentValue + 1; return currentValue; }; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_EQUALS(cmpResourceTrickle.GetInterval(), -1); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), -1); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 309, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 291, "metal": 300 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 309, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 291, "metal": 300 }); // Timer became invalidated, check whether it's recreated properly after that. ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { - if (valueName == "ResourceTrickle/Rates/food") + if (valueName == "Upkeep/Rates/food") + return currentValue + 1; + + return currentValue; +}; +Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 200); +cmpTimer.OnUpdate({ "turnLength": turnLength }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 290, "metal": 300 }); +cmpTimer.OnUpdate({ "turnLength": turnLength }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 289, "metal": 300 }); +cmpTimer.OnUpdate({ "turnLength": turnLength }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 288, "metal": 300 }); + +// Test multiple upkeep resources. +ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { + if (valueName == "Upkeep/Rates/food") return currentValue + 1; + if (valueName == "Upkeep/Rates/metal") + return currentValue + 2; return currentValue; }; Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); -cmpResourceTrickle.OnValueModification({ "component": "ResourceTrickle" }); -TS_ASSERT_EQUALS(cmpResourceTrickle.GetInterval(), 200); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 200); +cmpTimer.OnUpdate({ "turnLength": turnLength }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 287, "metal": 298 }); +cmpTimer.OnUpdate({ "turnLength": turnLength }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 286, "metal": 296 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 310, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 285, "metal": 294 }); + +// Test we don't go into negative resources. +let cmpGUI = AddMock(SYSTEM_ENTITY, IID_GuiInterface, { + "PushNotification": () => {} +}); +let notificationSpy = new Spy(cmpGUI, "PushNotification"); +ApplyValueModificationsToEntity = (valueName, currentValue, entity) => { + if (valueName == "Upkeep/Rates/food") + return currentValue + 1; + + return currentValue; +}; +Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity); +cmpUpkeep.OnValueModification({ "component": "Upkeep" }); +TS_ASSERT_EQUALS(cmpUpkeep.GetInterval(), 200); +cmpTimer.OnUpdate({ "turnLength": turnLength * 285 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 0, "metal": 294 }); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 311, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 0, "metal": 294 }); +TS_ASSERT_EQUALS(notificationSpy._called, 1); cmpTimer.OnUpdate({ "turnLength": turnLength }); -TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 312, "metal": 300 }); +TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetResourceCounts(), { "food": 0, "metal": 294 }); +TS_ASSERT_EQUALS(notificationSpy._called, 2); Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -135,6 +135,12 @@ 140 10000 + + + 10.0 + + 1000 + 90