LCOV - code coverage report
Current view: top level - simulation/components - Repairable.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 56 88 63.6 %
Date: 2023-04-02 12:52:40 Functions: 11 22 50.0 %

          Line data    Source code
       1             : function Repairable() {}
       2             : 
       3           1 : Repairable.prototype.Schema =
       4             :         "<a:help>Deals with repairable structures and units.</a:help>" +
       5             :         "<a:example>" +
       6             :                 "<RepairTimeRatio>2.0</RepairTimeRatio>" +
       7             :         "</a:example>" +
       8             :         "<element name='RepairTimeRatio' a:help='repair time ratio relative to building (or production) time.'>" +
       9             :                 "<ref name='positiveDecimal'/>" +
      10             :         "</element>";
      11             : 
      12           1 : Repairable.prototype.Init = function()
      13             : {
      14           1 :         this.builders = new Map(); // Map of builder entities to their work per second
      15           1 :         this.totalBuilderRate = 0; // Total amount of work the builders do each second
      16           1 :         this.buildMultiplier = 1; // Multiplier for the amount of work builders do
      17           1 :         this.buildTimePenalty = 0.7; // Penalty for having multiple builders
      18           1 :         this.repairTimeRatio = +this.template.RepairTimeRatio;
      19             : };
      20             : 
      21             : /**
      22             :  * Returns the current build progress in a [0,1] range.
      23             :  */
      24           1 : Repairable.prototype.GetBuildProgress = function()
      25             : {
      26           0 :         var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
      27           0 :         if (!cmpHealth)
      28           0 :                 return 0;
      29             : 
      30           0 :         var hitpoints = cmpHealth.GetHitpoints();
      31           0 :         var maxHitpoints = cmpHealth.GetMaxHitpoints();
      32             : 
      33           0 :         return hitpoints / maxHitpoints;
      34             : };
      35             : 
      36             : /**
      37             :  * @return whether this entity can be repaired (this does not account for health).
      38             :  */
      39           1 : Repairable.prototype.IsRepairable = function()
      40             : {
      41           7 :         return !this.unrepairable;
      42             : };
      43             : 
      44           1 : Repairable.prototype.SetRepairability = function(repairable)
      45             : {
      46           2 :         this.unrepairable = !repairable;
      47             : };
      48             : 
      49             : /**
      50             :  * Returns the current builders.
      51             :  *
      52             :  * @return {number[]} - An array containing the entity IDs of assigned builders.
      53             :  */
      54           1 : Repairable.prototype.GetBuilders = function()
      55             : {
      56           0 :         return Array.from(this.builders.keys());
      57             : };
      58             : 
      59           1 : Repairable.prototype.GetNumBuilders = function()
      60             : {
      61           3 :         return this.builders.size;
      62             : };
      63             : 
      64             : /**
      65             :  * Adds an array of builders.
      66             :  *
      67             :  * @param {number[]} - An array containing the entity IDs of builders to assign.
      68             :  */
      69           1 : Repairable.prototype.AddBuilders = function(builders)
      70             : {
      71           0 :         for (let builder of builders)
      72           0 :                 this.AddBuilder(builder);
      73             : };
      74             : 
      75           1 : Repairable.prototype.AddBuilder = function(builderEnt)
      76             : {
      77           2 :         if (this.builders.has(builderEnt))
      78           0 :                 return;
      79             : 
      80           2 :         this.builders.set(builderEnt, Engine.QueryInterface(builderEnt, IID_Builder).GetRate());
      81           2 :         this.totalBuilderRate += this.builders.get(builderEnt);
      82           2 :         this.SetBuildMultiplier();
      83             : };
      84             : 
      85           1 : Repairable.prototype.RemoveBuilder = function(builderEnt)
      86             : {
      87           1 :         if (!this.builders.has(builderEnt))
      88           0 :                 return;
      89             : 
      90           1 :         this.totalBuilderRate -= this.builders.get(builderEnt);
      91           1 :         this.builders.delete(builderEnt);
      92           1 :         this.SetBuildMultiplier();
      93             : };
      94             : 
      95             : /**
      96             :  * The build multiplier is a penalty that is applied to each builder.
      97             :  * For example, ten women build at a combined rate of 10^0.7 = 5.01 instead of 10.
      98             :  */
      99           1 : Repairable.prototype.CalculateBuildMultiplier = function(num)
     100             : {
     101             :         // Avoid division by zero, in particular 0/0 = NaN which isn't reliably serialized
     102           3 :         return num < 2 ? 1 : Math.pow(num, this.buildTimePenalty) / num;
     103             : };
     104             : 
     105           1 : Repairable.prototype.SetBuildMultiplier = function()
     106             : {
     107           3 :         this.buildMultiplier = this.CalculateBuildMultiplier(this.GetNumBuilders());
     108             : };
     109             : 
     110           1 : Repairable.prototype.GetBuildTime = function()
     111             : {
     112           0 :         let timeLeft = (1 - this.GetBuildProgress()) * Engine.QueryInterface(this.entity, IID_Cost).GetBuildTime() * this.repairTimeRatio;
     113           0 :         let rate = this.totalBuilderRate * this.buildMultiplier;
     114             :         // The rate if we add another woman to the repairs
     115           0 :         let rateNew = (this.totalBuilderRate + 1) * this.CalculateBuildMultiplier(this.GetNumBuilders() + 1);
     116           0 :         return {
     117             :                 // Avoid division by zero, in particular 0/0 = NaN which isn't reliably serialized
     118             :                 "timeRemaining": rate ? timeLeft / rate : 0,
     119             :                 "timeRemainingNew": timeLeft / rateNew
     120             :         };
     121             : };
     122             : 
     123             : // TODO: should we have resource costs?
     124           1 : Repairable.prototype.Repair = function(builderEnt, rate)
     125             : {
     126           3 :         let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     127           3 :         let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
     128           3 :         if (!cmpHealth || !cmpCost)
     129           0 :                 return;
     130           3 :         let damage = cmpHealth.GetMaxHitpoints() - cmpHealth.GetHitpoints();
     131           3 :         if (damage <= 0)
     132           0 :                 return;
     133             : 
     134             :         // Calculate the amount of hitpoints that will be added (using diminishing rate when several builders)
     135           3 :         let work = rate * this.buildMultiplier * this.GetRepairRate();
     136           3 :         let amount = Math.min(damage, work);
     137           3 :         cmpHealth.Increase(amount);
     138             : 
     139             :         // Update the total builder rate
     140           3 :         this.totalBuilderRate += rate - this.builders.get(builderEnt);
     141           3 :         this.builders.set(builderEnt, rate);
     142             : 
     143             :         // If we repaired all the damage, send a message to entities to stop repairing this building
     144           3 :         if (amount >= damage)
     145             :         {
     146           0 :                 Engine.PostMessage(this.entity, MT_ConstructionFinished, { "entity": this.entity, "newentity": this.entity });
     147             : 
     148             :                 // Inform the builders that repairing has finished.
     149             :                 // This not done by listening to a global message due to performance.
     150           0 :                 for (let builder of this.GetBuilders())
     151             :                 {
     152           0 :                         let cmpUnitAIBuilder = Engine.QueryInterface(builder, IID_UnitAI);
     153           0 :                         if (cmpUnitAIBuilder)
     154           0 :                                 cmpUnitAIBuilder.ConstructionFinished({ "entity": this.entity, "newentity": this.entity });
     155             :                 }
     156             :         }
     157             : };
     158             : 
     159           1 : Repairable.prototype.GetRepairRate = function()
     160             : {
     161           3 :         let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     162           3 :         let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
     163           3 :         let repairTime = this.repairTimeRatio * cmpCost.GetBuildTime();
     164           3 :         return repairTime ? cmpHealth.GetMaxHitpoints() / repairTime : 1;
     165             : };
     166             : 
     167           1 : Repairable.prototype.OnEntityRenamed = function(msg)
     168             : {
     169           0 :         let cmpRepairableNew = Engine.QueryInterface(msg.newentity, IID_Repairable);
     170           0 :         if (cmpRepairableNew)
     171           0 :                 cmpRepairableNew.AddBuilders(this.GetBuilders());
     172             : };
     173             : 
     174             : function RepairableMirage() {}
     175           1 : RepairableMirage.prototype.Init = function(cmpRepairable)
     176             : {
     177           0 :         this.numBuilders = cmpRepairable.GetNumBuilders();
     178           0 :         this.buildTime = cmpRepairable.GetBuildTime();
     179           0 :         if (cmpRepairable.unrepairable)
     180           0 :                 this.unrepairable = cmpRepairable.unrepairable;
     181             : };
     182             : 
     183           1 : RepairableMirage.prototype.GetNumBuilders = function() { return this.numBuilders; };
     184           1 : RepairableMirage.prototype.GetBuildTime = function() { return this.buildTime; };
     185           1 : RepairableMirage.prototype.IsRepairable = function() { return !this.unrepairable; };
     186             : 
     187           1 : Engine.RegisterGlobal("RepairableMirage", RepairableMirage);
     188             : 
     189           1 : Repairable.prototype.Mirage = function()
     190             : {
     191           0 :         let mirage = new RepairableMirage();
     192           0 :         mirage.Init(this);
     193           0 :         return mirage;
     194             : };
     195             : 
     196           1 : Engine.RegisterComponentType(IID_Repairable, "Repairable", Repairable);

Generated by: LCOV version 1.14