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);