Index: binaries/data/mods/public/simulation/components/Health.js =================================================================== --- binaries/data/mods/public/simulation/components/Health.js +++ binaries/data/mods/public/simulation/components/Health.js @@ -287,7 +287,15 @@ let templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity); let corpse; if (leaveResources) - corpse = Engine.AddEntity("resource|" + templateName); + { + let cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply); + if (cmpResourceSupply) + { + corpse = Engine.AddEntity("resource|" + templateName); + let cmpCorpseResourceSupply = Engine.QueryInterface(corpse, IID_ResourceSupply); + cmpCorpseResourceSupply.SetAmount(cmpResourceSupply.GetCurrentAmount()); + } + } else corpse = Engine.AddLocalEntity("corpse|" + templateName); Index: binaries/data/mods/public/simulation/components/ResourceSupply.js =================================================================== --- binaries/data/mods/public/simulation/components/ResourceSupply.js +++ binaries/data/mods/public/simulation/components/ResourceSupply.js @@ -4,10 +4,23 @@ "Provides a supply of one particular type of resource." + "" + "1000" + + "1500" + "food.meat" + "false" + "25" + "0.8" + + "" + + "" + + "Alive" + + "2" + + "1000" + + "" + + "" + + "Dead" + + "-1" + + "1000" + + "" + + "" + "" + "" + "" + @@ -15,6 +28,11 @@ "" + "Infinity" + "" + + "" + + "" + + "" + + "" + + "" + "" + Resources.BuildChoicesSchema(true, true) + "" + @@ -25,21 +43,54 @@ "" + "" + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "Alive" + + "Dead" + + "Both" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + ""; ResourceSupply.prototype.Init = function() { // Current resource amount (non-negative) - this.amount = this.GetMaxAmount(); + this.amount = +this.template.Amount; + this.maxAmount = this.amount; + if (this.template.MaxAmount && +this.template.MaxAmount > this.amount) + this.maxAmount = +this.template.MaxAmount; + this.infinite = !isFinite(+this.template.Amount); + + if (this.template.RegenBonus && !this.IsInfinite()) + for (let key in this.template.RegenBonus) + this.AddRegenTimer(this.template.RegenBonus[key], key); + // List of IDs for each player this.gatherers = []; - let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers() + let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); for (let i = 0; i < numPlayers; ++i) this.gatherers.push([]); - this.infinite = !isFinite(+this.template.Amount); - let [type, subtype] = this.template.Type.split('.'); this.cachedType = { "generic": type, "specific": subtype }; }; @@ -56,7 +107,7 @@ ResourceSupply.prototype.GetMaxAmount = function() { - return +this.template.Amount; + return this.maxAmount; }; ResourceSupply.prototype.GetCurrentAmount = function() @@ -74,6 +125,19 @@ return this.gatherers.reduce((a, b) => a + b.length, 0); }; +ResourceSupply.prototype.OnValueModification = function(msg) +{ + if (msg.component != "ResourceSupply") + return; + + if (!this.template.RegenBonus || this.IsInfinite()) + return; + + for (let bonus in this.template.RegenBonus) + this[bonus.Name + "Rate"] = ApplyValueModificationsToEntity("ResourceSupply/" + bonus + "Rate", +bonus.Rate, this.entity); + +}; + /* The rate of each additionnal gatherer rate follow a geometric sequence, with diminishingReturns as common ratio. */ ResourceSupply.prototype.GetDiminishingReturns = function() { @@ -84,35 +148,33 @@ { let numGatherers = this.GetNumGatherers(); if (numGatherers > 1) - return diminishingReturns == 1 ? 1 : (1. - Math.pow(diminishingReturns, numGatherers)) / (1. - diminishingReturns) / numGatherers; + return diminishingReturns == 1 ? 1 : (1 - Math.pow(diminishingReturns, numGatherers)) / (1 - diminishingReturns) / numGatherers; } } return null; }; -ResourceSupply.prototype.TakeResources = function(rate) +ResourceSupply.prototype.SetAmount = function(newAmount) { + let oldAmount = this.GetCurrentAmount(); + this.amount = Math.min(Math.max(newAmount, 0), this.GetMaxAmount()); + this.UpdateSupplyStatus(oldAmount); +}; + +ResourceSupply.prototype.TakeResources = function(amount) +{ // Before changing the amount, activate Fogging if necessary to hide changes let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging); if (cmpFogging) cmpFogging.Activate(); if (this.infinite) - return { "amount": rate, "exhausted": false }; + return { "amount": amount, "exhausted": false }; - // 'rate' should be a non-negative integer + let oldAmount = this.GetCurrentAmount(); + this.SetAmount(oldAmount - amount); - var old = this.amount; - this.amount = Math.max(0, old - rate); - var change = old - this.amount; - - // Remove entities that have been exhausted - if (this.amount === 0) - Engine.DestroyEntity(this.entity); - - Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": old, "to": this.amount }); - - return { "amount": change, "exhausted": (this.amount === 0) }; + return { "amount": oldAmount - this.GetCurrentAmount(), "exhausted": this.GetCurrentAmount() == 0 }; }; ResourceSupply.prototype.GetType = function() @@ -138,29 +200,96 @@ Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); } + if (!this.template.RegenBonus || this.IsInfinite()) + return true; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + for (let key in this.template.RegenBonus) + { + if (!this[key + "Timer"]) + continue; + + cmpTimer.CancelTimer(this[key + "Timer"]); + this[key + "Timer"] = undefined; + } + return true; }; // should this return false if the gatherer didn't gather from said resource? ResourceSupply.prototype.RemoveGatherer = function(gathererID, player) { - // this can happen if the unit is dead - if (player == undefined || player == INVALID_PLAYER) + if (player != undefined && player != INVALID_PLAYER) { - for (var i = 0; i < this.gatherers.length; ++i) - this.RemoveGatherer(gathererID, i); - } - else - { - var index = this.gatherers[player].indexOf(gathererID); + let index = this.gatherers[player].indexOf(gathererID); if (index !== -1) { - this.gatherers[player].splice(index,1); + this.gatherers[player].splice(index, 1); // broadcast message, mainly useful for the AIs. Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); - return; } } + // This can happen if the unit is dead + else + for (let i = 0; i < this.gatherers.length; ++i) + this.RemoveGatherer(gathererID, i); + + let hasGatherers = this.gatherers.some(p => p.length); + if (hasGatherers) + return; + + if (!this.template.RegenBonus || this.IsInfinite()) + return; + + for (let key in this.template.RegenBonus) + this.AddRegenTimer(this.template.RegenBonus[key], key); }; +ResourceSupply.prototype.AddRegenTimer = function(bonus, key) +{ + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + if (this[key + "Timer"] === undefined) + this[key + "Timer"] = cmpTimer.SetInterval(this.entity, IID_ResourceSupply, "RegenSupply", +bonus.Interval, +bonus.Interval, bonus); +}; + +ResourceSupply.prototype.RegenSupply = function(bonus) +{ + let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); + if(bonus.Constraint) + { + switch (bonus.Constraint) + { + case "Alive": + if (!cmpHealth) + return; + break; + case "Dead": + if (cmpHealth) + return; + break; + case "Both": + break; + default: + warn("Unrecognized constraint '" + bonus.Constraint + "', valid values are 'Alive', 'Dead' or 'Both'."); + return; + } + } + + this.SetAmount(this.GetCurrentAmount() + ApplyValueModificationsToEntity("ResourceSupply/" + bonus + "Rate", +bonus.Rate, this.entity)); +}; + +ResourceSupply.prototype.UpdateSupplyStatus = function(oldAmount) +{ + // Remove entities that have been exhausted. + if (this.GetCurrentAmount() == 0) + Engine.DestroyEntity(this.entity); + + // Do not send messages if the resource count didn't change. + if (oldAmount == this.GetCurrentAmount()) + return; + + Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": oldAmount, "to": this.GetCurrentAmount() }); +}; + + Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply); Index: binaries/data/mods/public/simulation/templates/gaia/fauna_sheep.xml =================================================================== --- binaries/data/mods/public/simulation/templates/gaia/fauna_sheep.xml +++ binaries/data/mods/public/simulation/templates/gaia/fauna_sheep.xml @@ -12,6 +12,21 @@ pitch + + 200 + + + Alive + 2 + 1000 + + + Dead + -1 + 1000 + + + Index: binaries/data/mods/public/simulation/templates/gaia/flora_tree_dead.xml =================================================================== --- binaries/data/mods/public/simulation/templates/gaia/flora_tree_dead.xml +++ binaries/data/mods/public/simulation/templates/gaia/flora_tree_dead.xml @@ -3,6 +3,14 @@ Dead Tree + + + + -0.5 + 1000 + + + flora/trees/tree_dead.xml Index: binaries/data/mods/public/simulation/templates/special/filter/resource.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special/filter/resource.xml +++ binaries/data/mods/public/simulation/templates/special/filter/resource.xml @@ -9,6 +9,7 @@ Instead, create a static, unblocking (see #3530 for why) static obstruction. TODO: this should probably be generalized as a parameter on entity death or something. --> + true false Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt.xml +++ binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt.xml @@ -12,5 +12,12 @@ 100 food.meat 8 + + + Dead + -1 + 1000 + +