0ad/binaries/data/mods/public/simulation/components/Repairable.js
Freagarach 88dc6d8e1e Do not let every entity with UnitAI listen to "OnGlobalConstructionFinished".
All entities with UnitAI wastefully listened to global
ConstructionFinished messages. Now the message is only sent to assigned
builders.

Differential Revision: D2697
Reviewed by: @wraitii.
This was SVN commit r23924.
2020-08-03 10:13:54 +00:00

154 lines
4.9 KiB
JavaScript

function Repairable() {}
Repairable.prototype.Schema =
"<a:help>Deals with repairable structures and units.</a:help>" +
"<a:example>" +
"<RepairTimeRatio>2.0</RepairTimeRatio>" +
"</a:example>" +
"<element name='RepairTimeRatio' a:help='repair time ratio relative to building (or production) time.'>" +
"<ref name='positiveDecimal'/>" +
"</element>";
Repairable.prototype.Init = function()
{
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;
};
/**
* Returns the current builders.
*
* @return {number[]} - An array containing the entity IDs of assigned builders.
*/
Repairable.prototype.GetBuilders = function()
{
return Array.from(this.builders.keys());
};
Repairable.prototype.GetNumBuilders = function()
{
return this.builders.size;
};
/**
* Adds an array of builders.
*
* @param {number[]} - An array containing the entity IDs of builders to assign.
*/
Repairable.prototype.AddBuilders = function(builders)
{
for (let builder of builders)
this.AddBuilder(builder);
}
Repairable.prototype.AddBuilder = function(builderEnt)
{
if (this.builders.has(builderEnt))
return;
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.has(builderEnt))
return;
this.totalBuilderRate -= this.builders.get(builderEnt);
this.builders.delete(builderEnt);
this.SetBuildMultiplier();
};
/**
* 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)
{
// Avoid division by zero, in particular 0/0 = NaN which isn't reliably serialized
return num < 2 ? 1 : Math.pow(num, this.buildTimePenalty) / num;
};
Repairable.prototype.SetBuildMultiplier = function()
{
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 {
// Avoid division by zero, in particular 0/0 = NaN which isn't reliably serialized
"timeRemaining": rate ? timeLeft / rate : 0,
"timeRemainingNew": timeLeft / rateNew
};
};
// TODO: should we have resource costs?
Repairable.prototype.Repair = function(builderEnt, rate)
{
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
if (!cmpHealth || !cmpCost)
return;
let damage = cmpHealth.GetMaxHitpoints() - cmpHealth.GetHitpoints();
if (damage <= 0)
return;
// Calculate the amount of hitpoints that will be added (using diminishing rate when several builders)
let work = rate * this.buildMultiplier * this.GetRepairRate();
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 });
// Inform the builders that repairing has finished.
// This not done by listening to a global message due to performance.
for (let builder of this.GetBuilders())
{
let cmpUnitAIBuilder = Engine.QueryInterface(builder, IID_UnitAI);
if (cmpUnitAIBuilder)
cmpUnitAIBuilder.ConstructionFinished({ "entity": this.entity, "newentity": this.entity });
}
}
};
Repairable.prototype.GetRepairRate = function()
{
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
let repairTime = this.repairTimeRatio * cmpCost.GetBuildTime();
return repairTime ? cmpHealth.GetMaxHitpoints() / repairTime : 1;
};
Engine.RegisterComponentType(IID_Repairable, "Repairable", Repairable);