Index: ps/trunk/binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/tooltips.js +++ ps/trunk/binaries/data/mods/public/gui/common/tooltips.js @@ -352,42 +352,48 @@ ].join(commaFont(translate(", "))); } -function getRepairRateTooltip(template) +function getRepairTimeTooltip(entState) { - if (!template.repairRate) - return ""; - - return sprintf(translate("%(repairRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), { - "repairRateLabel": headerFont(translate("Repair Rate:")), - "value": template.repairRate.toFixed(1), - "health": unitFont(translate("Health")), - "second": unitFont(translate("second")), - "worker": unitFont(translate("Worker")) - }); + return sprintf(translate("%(label)s %(details)s"), { + "label": headerFont(translate("Number of repairers:")), + "details": entState.repairable.numBuilders + }) + "\n" + (entState.repairable.numBuilders ? + sprintf(translatePlural( + "Add another worker to speed up the repairs by %(second)s second.", + "Add another worker to speed up the repairs by %(second)s seconds.", + Math.round(entState.repairable.buildTime.timeRemaining - entState.repairable.buildTime.timeRemainingNew)), + { + "second": Math.round(entState.repairable.buildTime.timeRemaining - entState.repairable.buildTime.timeRemainingNew) + }) : + sprintf(translatePlural( + "Add a worker to finish the repairs in %(second)s second.", + "Add a worker to finish the repairs in %(second)s seconds.", + Math.round(entState.repairable.buildTime.timeRemainingNew)), + { + "second": Math.round(entState.repairable.buildTime.timeRemainingNew) + })); } function getBuildTimeTooltip(entState) { - if (!entState.foundation.numBuilders) - return sprintf(translatePlural( - "Add a worker to finish the construction in %(second)s second.", - "Add a worker to finish the construction in %(second)s seconds.", - Math.round(entState.foundation.buildTime.timeRemainingNew)), - { - "second": Math.round(entState.foundation.buildTime.timeRemainingNew) - }); - return sprintf(translate("%(label)s %(details)s"), { "label": headerFont(translate("Number of builders:")), "details": entState.foundation.numBuilders - }) + "\n" + + }) + "\n" + (entState.foundation.numBuilders ? sprintf(translatePlural( "Add another worker to speed up the construction by %(second)s second.", "Add another worker to speed up the construction by %(second)s seconds.", Math.round(entState.foundation.buildTime.timeRemaining - entState.foundation.buildTime.timeRemainingNew)), { "second": Math.round(entState.foundation.buildTime.timeRemaining - entState.foundation.buildTime.timeRemainingNew) - }); + }) : + sprintf(translatePlural( + "Add a worker to finish the construction in %(second)s second.", + "Add a worker to finish the construction in %(second)s seconds.", + Math.round(entState.foundation.buildTime.timeRemainingNew)), + { + "second": Math.round(entState.foundation.buildTime.timeRemainingNew) + })); } /** Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js +++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js @@ -244,13 +244,14 @@ Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders ? Engine.FormatMillisecondsIntoDateStringGMT(entState.foundation.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss")) + " " : ""; } - else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility != "hidden") + else if (entState.repairable && entState.needsRepair) { Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false; Engine.GetGUIObjectByName("resourceCarryingText").hidden = false; Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png"; - Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.repairable.numBuilders + " "; - Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders."); + Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = getRepairTimeTooltip(entState); + Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.repairable.numBuilders ? + Engine.FormatMillisecondsIntoDateStringGMT(entState.repairable.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss")) + " " : ""; } else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints) && entState.visibility == "visible") { @@ -290,7 +291,6 @@ getHealerTooltip, getArmorTooltip, getGatherTooltip, - getRepairRateTooltip, getSpeedTooltip, getGarrisonTooltip, getProjectilesTooltip, Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -315,12 +315,10 @@ let cmpRepairable = QueryMiragedInterface(ent, IID_Repairable); if (cmpRepairable) - { - ret.repairable = { "numBuilders": cmpRepairable.GetNumBuilders() }; - cmpRepairable = Engine.QueryInterface(ent, IID_Repairable); - if (cmpRepairable) - ret.repairRate = cmpRepairable.GetRepairRate(); - } + ret.repairable = { + "numBuilders": cmpRepairable.GetNumBuilders(), + "buildTime": cmpRepairable.GetBuildTime() + }; let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) Index: ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js @@ -84,12 +84,13 @@ Mirage.prototype.GetNumBuilders = function() { return this.numBuilders; }; Mirage.prototype.GetBuildTime = function() { return this.buildTime; }; -// Repairable data (numBuilders shared with foundation as entities can't have both) +// Repairable data (numBuilders and buildTime shared with foundation as entities can't have both) Mirage.prototype.CopyRepairable = function(cmpRepairable) { this.miragedIids.add(IID_Repairable); this.numBuilders = cmpRepairable.GetNumBuilders(); + this.buildTime = cmpRepairable.GetBuildTime(); }; // Health data Index: ps/trunk/binaries/data/mods/public/simulation/components/Repairable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Repairable.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Repairable.js @@ -11,43 +11,77 @@ Repairable.prototype.Init = function() { - this.builders = []; // builder entities - this.buildMultiplier = 1; // Multiplier for the amount of work builders do. + this.builders = new Map(); // Map of builder entities to their work per second + this.totalBuilderRate = 0; // Total amount of work the builders do each second + this.buildMultiplier = 1; // Multiplier for the amount of work builders do + this.buildTimePenalty = 0.7; // Penalty for having multiple builders this.repairTimeRatio = +this.template.RepairTimeRatio; }; +/** + * Returns the current build progress in a [0,1] range. + */ +Repairable.prototype.GetBuildProgress = function() +{ + var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); + if (!cmpHealth) + return 0; + + var hitpoints = cmpHealth.GetHitpoints(); + var maxHitpoints = cmpHealth.GetMaxHitpoints(); + + return hitpoints / maxHitpoints; +}; + Repairable.prototype.GetNumBuilders = function() { - return this.builders.length; + return this.builders.size; }; Repairable.prototype.AddBuilder = function(builderEnt) { - if (this.builders.indexOf(builderEnt) !== -1) + if (this.builders.has(builderEnt)) return; - this.builders.push(builderEnt); + + this.builders.set(builderEnt, Engine.QueryInterface(builderEnt, IID_Builder).GetRate()); + this.totalBuilderRate += this.builders.get(builderEnt); this.SetBuildMultiplier(); }; Repairable.prototype.RemoveBuilder = function(builderEnt) { - if (this.builders.indexOf(builderEnt) === -1) + if (!this.builders.has(builderEnt)) return; - this.builders.splice(this.builders.indexOf(builderEnt), 1); - this.SetBuildMultiplier(); + + this.totalBuilderRate -= this.builders.get(builderEnt); + this.builders.delete(builderEnt); + this.SetBuildMultiplier(); }; /** - * Sets the build rate multiplier, which is applied to all builders. - * Yields a total rate of construction equal to numBuilders^0.7 + * The build multiplier is a penalty that is applied to each builder. + * For example, ten women build at a combined rate of 10^0.7 = 5.01 instead of 10. */ +Repairable.prototype.CalculateBuildMultiplier = function(num) +{ + return num < 2 ? 1 : Math.pow(num, this.buildTimePenalty) / num; +}; + Repairable.prototype.SetBuildMultiplier = function() { - let numBuilders = this.builders.length; - if (numBuilders < 2) - this.buildMultiplier = 1; - else - this.buildMultiplier = Math.pow(numBuilders, 0.7) / numBuilders; + this.buildMultiplier = this.CalculateBuildMultiplier(this.GetNumBuilders()); +}; + +Repairable.prototype.GetBuildTime = function() +{ + let timeLeft = (1 - this.GetBuildProgress()) * Engine.QueryInterface(this.entity, IID_Cost).GetBuildTime() * this.repairTimeRatio; + let rate = this.totalBuilderRate * this.buildMultiplier; + // The rate if we add another woman to the repairs + let rateNew = (this.totalBuilderRate + 1) * this.CalculateBuildMultiplier(this.GetNumBuilders() + 1); + return { + "timeRemaining": timeLeft / rate, + "timeRemainingNew": timeLeft / rateNew + }; }; // TODO: should we have resource costs? @@ -66,6 +100,10 @@ let amount = Math.min(damage, work); cmpHealth.Increase(amount); + // Update the total builder rate + this.totalBuilderRate += rate - this.builders.get(builderEnt); + this.builders.set(builderEnt, rate); + // If we repaired all the damage, send a message to entities to stop repairing this building if (amount >= damage) Engine.PostMessage(this.entity, MT_ConstructionFinished, { "entity": this.entity, "newentity": this.entity });