Ticket #3406: decay_field_v2.patch
File decay_field_v2.patch, 42.8 KB (added by , 8 years ago) |
---|
-
binaries/data/mods/public/simulation/components/BuildingAI.js
5 5 function BuildingAI() {} 6 6 7 7 BuildingAI.prototype.Schema = 8 "<element name='DefaultArrowCount'>" + 9 "<data type='nonNegativeInteger'/>" + 8 "<element name='Type'>" + 9 "<choice>" + 10 "<value a:help='Represents buildings with the capability to attack other units'>Military</value>" + 11 "<value a:help='Represents buildings which cannot attack'>Civil</value>" + 12 "</choice>" + 10 13 "</element>" + 14 "<optional>"+ 15 "<element name='DefaultArrowCount'>" + 16 "<data type='nonNegativeInteger'/>" + 17 "</element>" + 18 "</optional>"+ 11 19 "<optional>" + 12 20 "<element name='MaxArrowCount' a:help='Limit the number of arrows to a certain amount'>" + 13 21 "<data type='nonNegativeInteger'/>" + 14 22 "</element>" + 15 23 "</optional>" + 16 "<element name='GarrisonArrowMultiplier'>" + 17 "<ref name='nonNegativeDecimal'/>" + 18 "</element>" + 19 "<element name='GarrisonArrowClasses' a:help='Add extra arrows for this class list'>" + 20 "<text/>" + 21 "</element>"; 24 "<optional>" + 25 "<element name='GarrisonArrowMultiplier'>" + 26 "<ref name='nonNegativeDecimal'/>" + 27 "</element>" + 28 "</optional>" + 29 "<optional>" + 30 "<element name='GarrisonArrowClasses' a:help='Add extra arrows for this class list'>" + 31 "<text/>" + 32 "</element>" + 33 "</optional>"; 22 34 23 35 BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; 24 36 25 37 BuildingAI.prototype.Init = function() 26 38 { 27 this.currentRound = 0; 28 this.archersGarrisoned = 0; 29 this.arrowsLeft = 0; 30 this.targetUnits = []; 39 this.buildingType = this.template.Type; 40 this.currentRound = 0; 41 this.archersGarrisoned = 0; 42 this.arrowsLeft = 0; 43 this.targetUnits = []; 31 44 }; 32 45 33 46 BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) 34 47 { 48 // 49 // If the building is not of militar type, nothing should be done 50 // 51 if(this.buildingType !== 'Military') 52 return; 53 35 54 let classes = this.template.GarrisonArrowClasses; 36 55 37 56 for (let ent of msg.added) … … 55 74 56 75 BuildingAI.prototype.OnOwnershipChanged = function(msg) 57 76 { 77 // If the building is of civil type, then the moment it is finished 78 // being constructed, start the timer for the decaying process. 79 // 80 // This covers the scenario when the building is built in a queue where the builder 81 // goes for other structures leaving the buildind behind. If they don't 82 // return then the building should decay, otherwise the timer would be 83 // stopped inside the FSM in UnitAI 84 if(msg.from == -1 && this.buildingType === 'Civil') 85 { 86 this.DecayBuilding(); 87 return; 88 } 89 58 90 this.targetUnits = []; 59 91 this.SetupRangeQuery(); 60 92 this.SetupGaiaRangeQuery(); … … 62 94 63 95 BuildingAI.prototype.OnDiplomacyChanged = function(msg) 64 96 { 97 // 98 // If the building is not of militar type, nothing should be done 99 // 100 if(this.buildingType !== 'Military') 101 return; 102 65 103 if (!IsOwnedByPlayer(msg.player, this.entity)) 66 104 return; 67 105 … … 73 111 74 112 BuildingAI.prototype.OnDestroy = function() 75 113 { 114 // 115 // If the building is not of militar type, nothing should be done 116 // 117 if(this.buildingType !== 'Military') 118 return; 119 76 120 if (this.timer) 77 121 { 78 122 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); … … 93 137 */ 94 138 BuildingAI.prototype.OnValueModification = function(msg) 95 139 { 140 // 141 // If the building is not of militar type, nothing should be done 142 // 143 if(this.buildingType !== 'Military') 144 return; 145 96 146 if (msg.component != "Attack") 97 147 return; 98 148 … … 106 156 */ 107 157 BuildingAI.prototype.SetupRangeQuery = function() 108 158 { 109 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);159 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 110 160 if (!cmpAttack) 111 161 return; 112 162 113 varcmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);163 let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 114 164 if (this.enemyUnitsQuery) 115 165 { 116 166 cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); … … 117 167 this.enemyUnitsQuery = undefined; 118 168 } 119 169 120 varcmpPlayer = QueryOwnerInterface(this.entity);170 let cmpPlayer = QueryOwnerInterface(this.entity); 121 171 if (!cmpPlayer) 122 172 return; 123 173 124 varenemies = [];125 varnumPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();174 let enemies = []; 175 let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); 126 176 for (let i = 1; i < numPlayers; ++i) 127 177 if (cmpPlayer.IsEnemy(i)) 128 178 enemies.push(i); … … 130 180 if (!enemies.length) 131 181 return; 132 182 133 varrange = cmpAttack.GetRange(attackType);183 let range = cmpAttack.GetRange(attackType); 134 184 this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( 135 185 this.entity, range.min, range.max, range.elevationBonus, 136 186 enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); … … 142 192 // This should be called whenever our ownership changes. 143 193 BuildingAI.prototype.SetupGaiaRangeQuery = function() 144 194 { 145 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);195 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 146 196 if (!cmpAttack) 147 197 return; 148 198 149 varcmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);199 let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 150 200 if (this.gaiaUnitsQuery) 151 201 { 152 202 cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); … … 153 203 this.gaiaUnitsQuery = undefined; 154 204 } 155 205 156 varcmpPlayer = QueryOwnerInterface(this.entity);206 let cmpPlayer = QueryOwnerInterface(this.entity); 157 207 if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) 158 208 return; 159 209 160 varrange = cmpAttack.GetRange(attackType);210 let range = cmpAttack.GetRange(attackType); 161 211 162 212 // This query is only interested in Gaia entities that can attack. 163 213 this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( … … 172 222 */ 173 223 BuildingAI.prototype.OnRangeUpdate = function(msg) 174 224 { 225 // 226 // If the building is not of militar type, nothing should be done 227 // 228 if(this.buildingType !== 'Military') 229 return; 175 230 176 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);231 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 177 232 if (!cmpAttack) 178 233 return; 179 234 … … 202 257 } 203 258 204 259 if (this.targetUnits.length) 205 this. StartTimer();260 this.AttackNearEntities(); 206 261 }; 207 262 208 BuildingAI.prototype. StartTimer= function()263 BuildingAI.prototype.AttackNearEntities = function() 209 264 { 210 265 if (this.timer) 211 266 return; 212 267 213 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);268 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 214 269 if (!cmpAttack) 215 270 return; 271 let attackTimers = cmpAttack.GetTimers(attackType); 272 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 216 273 217 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 218 var attackTimers = cmpAttack.GetTimers(attackType); 219 220 this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", 221 attackTimers.prepare, attackTimers.repeat / roundCount, null); 274 this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null); 222 275 }; 223 276 224 277 BuildingAI.prototype.GetDefaultArrowCount = function() 225 278 { 226 vararrowCount = +this.template.DefaultArrowCount;279 let arrowCount = +this.template.DefaultArrowCount; 227 280 return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity); 228 281 }; 229 282 … … 238 291 239 292 BuildingAI.prototype.GetGarrisonArrowMultiplier = function() 240 293 { 241 vararrowMult = +this.template.GarrisonArrowMultiplier;294 let arrowMult = +this.template.GarrisonArrowMultiplier; 242 295 return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); 243 296 }; 244 297 245 298 BuildingAI.prototype.GetGarrisonArrowClasses = function() 246 299 { 247 varstring = this.template.GarrisonArrowClasses;300 let string = this.template.GarrisonArrowClasses; 248 301 if (string) 249 302 return string.split(/\s+/); 250 303 return []; … … 267 320 { 268 321 this.unitAITarget = ent; 269 322 if (ent) 270 this. StartTimer();323 this.AttackNearEntities(); 271 324 }; 272 325 273 326 /** … … 302 355 arrowsToFire = this.arrowsLeft; 303 356 else 304 357 arrowsToFire = Math.min( 305 306 358 Math.round(2 * Math.random() * this.GetArrowCount() / roundCount), 359 this.arrowsLeft 307 360 ); 308 361 309 362 if (arrowsToFire <= 0) … … 364 417 */ 365 418 BuildingAI.prototype.CheckTargetVisible = function(target) 366 419 { 367 varcmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);420 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 368 421 if (!cmpOwnership) 369 422 return false; 370 423 371 424 // Entities that are hidden and miraged are considered visible 372 varcmpFogging = Engine.QueryInterface(target, IID_Fogging);425 let cmpFogging = Engine.QueryInterface(target, IID_Fogging); 373 426 if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) 374 427 return true; 375 428 … … 378 431 return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; 379 432 }; 380 433 434 /** 435 * Some buildings (like fields) are expected to decline their health when no farmer is 436 * working on them. This increases the realism of the game and adds new levels of 437 * complexity into the game. 438 * 439 * The way is expected to work is simple. When the number of workers in a field reaches 440 * zero, a timer is launched with an offset of 25 seconds, and after that the health of 441 * the field starts decaying with the values given by the template element <IdleDecayAmount></IdleDecayAmount> 442 * which is accesed through the IID_Health the field 443 */ 444 BuildingAI.prototype.DecayBuilding = function() 445 { 446 if(this.timer) 447 return; 448 449 let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 450 if(!cmpHealth || cmpHealth.GetIdleDecayRate === 0 || cmpHealth.GetIdleDecayAmount === 0) 451 return; 452 453 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 454 this.timer = cmpTimer.SetInterval(this.entity, IID_Health, "Reduce", 15000, 25000 / cmpHealth.GetIdleDecayRate(), cmpHealth.GetIdleDecayAmount()); 455 } 456 457 /** 458 * Stop the current BuildingAI timer. 459 */ 460 BuildingAI.prototype.StopTimer = function() 461 { 462 if (!this.timer) 463 return; 464 465 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 466 cmpTimer.CancelTimer(this.timer); 467 this.timer = undefined; 468 }; 469 381 470 Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI); -
binaries/data/mods/public/simulation/components/Health.js
27 27 "<element name='IdleRegenRate' a:help='Hitpoint regeneration rate per second when idle or garrisoned.'>" + 28 28 "<data type='decimal'/>" + 29 29 "</element>" + 30 "<optional>" + 31 "<element name='IdleDecayRate' a:help='Some structures (like fields) are supposed to decay in health when they are not been used, this variable is used to declare the frequency'>" + 32 "<data type='decimal'/>" + 33 "</element>" + 34 "</optional>" + 35 "<optional>" + 36 "<element name='IdleDecayAmount' a:help='Represents the amount of health that would be taken each time the dacying process is called'>" + 37 "<data type='decimal'/>" + 38 "</element>" + 39 "</optional>" + 30 40 "<element name='DeathType' a:help='Behaviour when the unit dies'>" + 31 41 "<choice>" + 32 42 "<value a:help='Disappear instantly'>vanish</value>" + … … 50 60 this.hitpoints = +(this.template.Initial || this.GetMaxHitpoints()); 51 61 this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity); 52 62 this.idleRegenRate = ApplyValueModificationsToEntity("Health/IdleRegenRate", +this.template.IdleRegenRate, this.entity); 63 // Some entities lose points when iddle, but if it is not defined, make it zero 64 this.idleDecayRate = +(this.template.IdleDecayRate || 0); 65 this.idleDecayAmount = +(this.template.IdleDecayAmount || 0); 53 66 this.CheckRegenTimer(); 54 67 }; 55 68 … … 69 82 return this.maxHitpoints; 70 83 }; 71 84 85 /** 86 * Returns the current frecuency at which the health of the unit should 87 * diminish each second when is idle (mainly used by fields) 88 */ 89 Health.prototype.GetIdleDecayRate = function() 90 { 91 return this.idleDecayRate; 92 }; 93 94 /** 95 * Returns the amount of healt points this unit should lost while the 96 * unit is idle (mainly used by fields) 97 */ 98 Health.prototype.GetIdleDecayAmount = function() 99 { 100 return this.idleDecayAmount; 101 }; 102 72 103 Health.prototype.SetHitpoints = function(value) 73 104 { 74 105 // If we're already dead, don't allow resurrection … … 82 113 83 114 var old = this.hitpoints; 84 115 this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value)); 85 116 86 117 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 87 118 if (cmpRangeManager) 88 119 { … … 134 165 135 166 if (regen > 0) 136 167 this.Increase(regen); 137 else 168 else 138 169 this.Reduce(-regen); 139 170 }; 140 171 … … 145 176 { 146 177 // check if we need a timer 147 178 if (this.GetRegenRate() == 0 && this.GetIdleRegenRate() == 0 || 148 149 179 this.GetHitpoints() == this.GetMaxHitpoints() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 || 180 this.GetHitpoints() == 0) 150 181 { 151 182 // we don't need a timer, disable if one exists 152 183 if (this.regenTimer) … … 254 285 255 286 var old = this.hitpoints; 256 287 this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); 257 288 258 289 if (this.hitpoints == this.GetMaxHitpoints()) 259 290 { 260 291 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); … … 353 384 354 385 let oldRegenRate = this.regenRate; 355 386 this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity); 356 387 357 388 let oldIdleRegenRate = this.idleRegenRate; 358 389 this.idleRegenRate = ApplyValueModificationsToEntity("Health/IdleRegenRate", +this.template.IdleRegenRate, this.entity); 359 390 360 391 if (this.regenRate != oldRegenRate || this.idleRegenRate != oldIdleRegenRate) 361 392 this.CheckRegenTimer(); 362 393 }; -
binaries/data/mods/public/simulation/components/UnitAI.js
56 56 57 57 // Unit stances. 58 58 // There some targeting options: 59 // 60 // 61 // 62 // 63 // 59 // targetVisibleEnemies: anything in vision range is a viable target 60 // targetAttackersAlways: anything that hurts us is a viable target, 61 // possibly overriding user orders! 62 // targetAttackersPassive: anything that hurts us is a viable target, 63 // if we're on a passive/unforced order (e.g. gathering/building) 64 64 // There are some response options, triggered when targets are detected: 65 // 66 // 67 // 68 // 69 // 70 // 65 // respondFlee: run away 66 // respondChase: start chasing after the enemy 67 // respondChaseBeyondVision: start chasing, and don't stop even if it's out 68 // of this unit's vision range (though still visible to the player) 69 // respondStandGround: attack enemy but don't move at all 70 // respondHoldGround: attack enemy but don't move far from current position 71 71 // TODO: maybe add targetAggressiveEnemies (don't worry about lone scouts, 72 72 // do worry around armies slaughtering the guy standing next to you), etc. 73 73 var g_Stances = { … … 349 349 return; 350 350 } 351 351 352 // Check if we need to move 352 // Check if we need to move TODO implement a better way to know if we are on the shoreline 353 353 var needToMove = true; 354 354 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 355 355 if (this.lastShorelinePosition && cmpPosition && (this.lastShorelinePosition.x == cmpPosition.GetPosition().x) 356 356 && (this.lastShorelinePosition.z == cmpPosition.GetPosition().z)) 357 357 { 358 358 // we were already on the shoreline, and have not moved since 359 359 if (DistanceBetweenEntities(this.entity, this.order.data.target) < 50) … … 1080 1080 var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); 1081 1081 if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) 1082 1082 { 1083 this.pickup = this.order.data.target; 1083 this.pickup = this.order.data.target; // temporary, deleted in "leave" 1084 1084 Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); 1085 1085 } 1086 1086 }, … … 1387 1387 var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); 1388 1388 var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); 1389 1389 if (cmpIdentity && cmpIdentity.HasClass("Support") && 1390 1390 cmpHealth && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 1391 1391 { 1392 1392 if (this.CanHeal(this.isGuardOf)) 1393 1393 this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); … … 1743 1743 1744 1744 "Attacked": function(msg) { 1745 1745 // If we're attacked by a close enemy, we should try to defend ourself 1746 // 1746 // but only if we're not forced to target something else 1747 1747 if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && !this.order.data.force))) 1748 1748 { 1749 1749 this.RespondToTargetedEntities([msg.data.attacker]); … … 1795 1795 } 1796 1796 // Check the target is still alive and attackable 1797 1797 if (this.TargetIsAlive(target) && 1798 1799 1798 this.CanAttack(target, this.order.data.forceResponse || null) && 1799 !this.CheckTargetAttackRange(target, this.order.data.attackType)) 1800 1800 { 1801 1801 // Can't reach it - try to chase after it 1802 1802 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) … … 1960 1960 if (this.order.data.target != msg.data.attacker) 1961 1961 { 1962 1962 // If we're attacked by a close enemy, stronger than our current target, 1963 // 1963 // we choose to attack it, but only if we're not forced to target something else 1964 1964 if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && !this.order.data.force))) 1965 1965 { 1966 1966 var ents = [this.order.data.target, msg.data.attacker]; … … 2037 2037 var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2038 2038 var cmpMirage = Engine.QueryInterface(this.gatheringTarget, IID_Mirage); 2039 2039 if ((!cmpMirage || !cmpMirage.Mirages(IID_ResourceSupply)) && 2040 2040 (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))) 2041 2041 { 2042 2042 // Save the current order's data in case we need it later 2043 2043 var oldType = this.order.data.type; … … 2201 2201 { 2202 2202 // Check that we can gather from the resource we're supposed to gather from. 2203 2203 // Will only be added if we're not already in. 2204 varcmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);2205 varcmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);2204 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 2205 let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2206 2206 if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity)) 2207 2207 { 2208 2208 this.gatheringTarget = INVALID_ENTITY; … … 2209 2209 this.StartTimer(0); 2210 2210 return false; 2211 2211 } 2212 2213 // If the building is a field and is decaying, stop the timer from BuildingAI 2214 let supplyType = cmpSupply.GetType(); 2215 if(supplyType.generic === 'food' && supplyType.specific === 'grain') 2216 { 2217 let cmpBuildingAI = Engine.QueryInterface(this.gatheringTarget, IID_BuildingAI); 2218 let cmpBuildingHealth = Engine.QueryInterface(this.gatheringTarget, IID_Health); 2219 if(cmpBuildingAI && cmpBuildingHealth) 2220 { 2221 cmpBuildingAI.StopTimer(); 2222 2223 // If the field has been decaying its heatpoints should be less than the maximum 2224 // therefore it should be repared before continue with the gathering 2225 if(cmpBuildingHealth.GetHitpoints() < cmpBuildingHealth.GetMaxHitpoints()) 2226 { 2227 this.PushOrderFront("Repair", { "target": this.gatheringTarget, "force": true }); 2228 this.SetNextState('INDIVIDUAL.REPAIR.REPAIRING'); 2229 return false; 2230 } 2231 } 2232 } 2212 2233 } 2213 2234 2214 2235 // If this order was forced, the player probably gave it, but now we've reached the target … … 2218 2239 2219 2240 // Calculate timing based on gather rates 2220 2241 // This allows the gather rate to control how often we gather, instead of how much. 2221 varcmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);2222 varrate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget);2242 let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); 2243 let rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget); 2223 2244 2224 2245 if (!rate) 2225 2246 { … … 2238 2259 2239 2260 // Scale timing interval based on rate, and start timer 2240 2261 // The offset should be at least as long as the repeat time so we use the same value for both. 2241 varoffset = 1000/rate;2242 varrepeat = offset;2262 let offset = 1000/rate; 2263 let repeat = offset; 2243 2264 this.StartTimer(offset, repeat); 2244 2265 2245 2266 // We want to start the gather animation as soon as possible, … … 2249 2270 // off to a different target.) 2250 2271 if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) 2251 2272 { 2252 vartypename = "gather_" + this.order.data.type.specific;2273 let typename = "gather_" + this.order.data.type.specific; 2253 2274 this.SelectAnimation(typename, false, 1.0, typename); 2254 2275 } 2255 2276 return false; … … 2260 2281 2261 2282 // don't use ownership because this is called after a conversion/resignation 2262 2283 // and the ownership would be invalid then. 2263 var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2264 if (cmpSupply) 2265 cmpSupply.RemoveGatherer(this.entity); 2266 delete this.gatheringTarget; 2284 // 2285 // As javascript manages references instead of copies, when the field 2286 // is repaired, the reference to this.gatherinTarget could be deleted 2287 // creating an error when the engine is queried 2288 if(this.gatheringTarget) 2289 { 2290 let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2291 if (cmpSupply) 2292 { 2293 cmpSupply.RemoveGatherer(this.entity); 2267 2294 2295 // Every time a gather leaves the farmer, check how many are left inside the field 2296 // If the number of gathers is zero, start a timer after certain offset to decay the 2297 // health of the field 2298 let supplyType = cmpSupply.GetType(); 2299 if(supplyType.generic === 'food' && supplyType.specific === 'grain' && cmpSupply.GetNumGatherers() === 0) 2300 { 2301 let cmpBuildingAI = Engine.QueryInterface(this.gatheringTarget, IID_BuildingAI); 2302 cmpBuildingAI.DecayBuilding(); 2303 } 2304 } 2305 delete this.gatheringTarget; 2306 } 2307 2268 2308 // Show the carried resource, if we've gathered anything. 2269 2309 this.SetGathererAnimationOverride(); 2270 2310 }, 2271 2311 2272 2312 "Timer": function(msg) { 2273 varresourceTemplate = this.order.data.template;2274 varresourceType = this.order.data.type;2313 let resourceTemplate = this.order.data.template; 2314 let resourceType = this.order.data.type; 2275 2315 2276 varcmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);2316 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 2277 2317 if (!cmpOwnership) 2278 2318 return; 2279 2319 2280 var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2320 let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2321 let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); 2281 2322 if (cmpSupply && cmpSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity)) 2282 2323 { 2283 2324 // Check we can still reach and gather from the target … … 2285 2326 { 2286 2327 // Gather the resources: 2287 2328 2288 var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);2289 2290 2329 // Try to gather treasure 2291 2330 if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget)) 2292 2331 return; … … 2297 2336 cmpResourceGatherer.DropResources(); 2298 2337 2299 2338 // Collect from the target 2300 varstatus = cmpResourceGatherer.PerformGather(this.gatheringTarget);2339 let status = cmpResourceGatherer.PerformGather(this.gatheringTarget); 2301 2340 2302 2341 // If we've collected as many resources as possible, 2303 2342 // return to the nearest dropsite 2304 2343 if (status.filled) 2305 2344 { 2306 varnearby = this.FindNearestDropsite(resourceType.generic);2345 let nearby = this.FindNearestDropsite(resourceType.generic); 2307 2346 if (nearby) 2308 2347 { 2309 2348 // (Keep this Gather order on the stack so we'll … … 2336 2375 // the old one. So try to get close to the old resource's 2337 2376 // last known position 2338 2377 2339 varmaxRange = 8; // get close but not too close2378 let maxRange = 8; // get close but not too close 2340 2379 if (this.order.data.lastPos && 2341 2380 this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z, 2342 2381 0, maxRange)) … … 2349 2388 2350 2389 // We're already in range, can't get anywhere near it or the target is exhausted. 2351 2390 2352 varherdPos = this.order.data.initPos;2391 let herdPos = this.order.data.initPos; 2353 2392 2354 2393 // Give up on this order and try our next queued order 2355 2394 // but first check what is our next order and, if needed, insert a returnResource order 2356 var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);2357 2395 if (cmpResourceGatherer.IsCarrying(resourceType.generic) && 2358 2396 this.orderQueue.length > 1 && this.orderQueue[1] !== "ReturnResource" && 2359 2397 (this.orderQueue[1].type !== "Gather" || this.orderQueue[1].data.type.generic !== resourceType.generic)) … … 2369 2407 2370 2408 // Try to find a new resource of the same specific type near our current position: 2371 2409 // Also don't switch to a different type of huntable animal 2372 varnearby = this.FindNearbyResource(function (ent, type, template) {2410 let nearby = this.FindNearbyResource(function (ent, type, template) { 2373 2411 return ( 2374 2412 (type.generic == "treasure" && resourceType.generic == "treasure") 2375 2413 || (type.specific == resourceType.specific … … 2393 2431 // drop it off, and if not then we might as well head to the dropsite 2394 2432 // anyway because that's a nice enough place to congregate and idle 2395 2433 2396 varnearby = this.FindNearestDropsite(resourceType.generic);2434 let nearby = this.FindNearestDropsite(resourceType.generic); 2397 2435 if (nearby) 2398 2436 { 2399 2437 this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); … … 2636 2674 2637 2675 this.order.data.force = false; 2638 2676 2639 this.repairTarget = this.order.data.target; 2677 this.repairTarget = this.order.data.target; // temporary, deleted in "leave". 2640 2678 // Check we can still reach and repair the target 2641 2679 if (!this.CanRepair(this.repairTarget)) 2642 2680 { … … 2724 2762 let dropsiteTypes = cmpResourceDropsite.GetTypes(); 2725 2763 cmpResourceGatherer.CommitResources(dropsiteTypes); 2726 2764 this.SetGathererAnimationOverride(); 2727 } 2765 } 2728 2766 2729 2767 // We finished building it. 2730 2768 // Switch to the next order (if any) … … 2765 2803 var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite); 2766 2804 var types = cmpResourceDropsite.GetTypes(); 2767 2805 // TODO: Slightly undefined behavior here, we don't know what type of resource will be collected, 2768 // 2806 // may cause problems for AIs (especially hunting fast animals), but avoid ugly hacks to fix that! 2769 2807 var nearby = this.FindNearbyResource(function (ent, type, template) { 2770 2808 return (types.indexOf(type.generic) != -1); 2771 2809 }); … … 2799 2837 var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); 2800 2838 if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) 2801 2839 { 2802 this.pickup = this.order.data.target; 2840 this.pickup = this.order.data.target; // temporary, deleted in "leave" 2803 2841 Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); 2804 2842 } 2805 2843 }, … … 3054 3092 "ANIMAL": { 3055 3093 "Attacked": function(msg) { 3056 3094 if (this.template.NaturalBehaviour == "skittish" || 3057 3095 this.template.NaturalBehaviour == "passive") 3058 3096 { 3059 3097 this.Flee(msg.data.attacker, false); 3060 3098 } … … 3436 3474 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 3437 3475 if (this.losRangeQuery) 3438 3476 this.SetupRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losRangeQuery)); 3439 3477 3440 3478 if (this.IsHealer() && this.losHealRangeQuery) 3441 3479 this.SetupHealRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losHealRangeQuery)); 3442 3480 }; … … 3554 3592 { 3555 3593 if (!this.orderQueue.length) 3556 3594 { 3557 var stack = new Error().stack.trimRight().replace(/^/mg, ' 3595 var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line 3558 3596 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 3559 3597 var template = cmpTemplateManager.GetCurrentTemplateName(this.entity); 3560 3598 error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); … … 5775 5813 if (cmpOwnership && IsOwnedByPlayer(cmpOwnership.GetOwner(), target)) 5776 5814 return true; 5777 5815 return cmpPlayer && cmpPlayer.HasSharedDropsites() && cmpResourceDropsite.IsShared() && 5778 5816 cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target); 5779 5817 }; 5780 5818 5781 5819 UnitAI.prototype.CanTrade = function(target) -
binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml
13 13 </Ranged> 14 14 </Attack> 15 15 <BuildingAI> 16 <Type>Military</Type> 16 17 <DefaultArrowCount>3</DefaultArrowCount> 17 18 <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier> 18 19 </BuildingAI> -
binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml
13 13 </Ranged> 14 14 </Attack> 15 15 <BuildingAI> 16 <Type>Military</Type> 16 17 <DefaultArrowCount>5</DefaultArrowCount> 17 18 <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier> 18 19 </BuildingAI> -
binaries/data/mods/public/simulation/templates/other/plane.xml
14 14 </Ranged> 15 15 </Attack> 16 16 <BuildingAI> 17 <Type>Military</Type> 17 18 <DefaultArrowCount>3</DefaultArrowCount> 18 19 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 19 20 <GarrisonArrowClasses>Infantry</GarrisonArrowClasses> … … 24 25 <List datatype="tokens">Support Infantry</List> 25 26 <BuffHeal>1</BuffHeal> 26 27 <LoadingRange>5</LoadingRange> 27 <EjectClassesOnDestroy datatype="tokens" /> 28 <EjectClassesOnDestroy datatype="tokens" /> 28 29 </GarrisonHolder> 29 30 <Decay> 30 31 <SinkingAnim>true</SinkingAnim> -
binaries/data/mods/public/simulation/templates/structures/brit_crannog.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <Entity parent="template_structure_civic_civil_centre"> 3 3 <BuildingAI> 4 <Type>Military</Type> 4 5 <DefaultArrowCount>3</DefaultArrowCount> 5 6 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 6 7 </BuildingAI> -
binaries/data/mods/public/simulation/templates/structures/iber_defense_tower.xml
6 6 </Ranged> 7 7 </Attack> 8 8 <BuildingAI> 9 <Type>Military</Type> 9 10 <DefaultArrowCount>2</DefaultArrowCount> 10 11 </BuildingAI> 11 12 <Cost> -
binaries/data/mods/public/simulation/templates/structures/ptol_military_colony.xml
14 14 </Ranged> 15 15 </Attack> 16 16 <BuildingAI> 17 <Type>Military</Type> 17 18 <DefaultArrowCount>1</DefaultArrowCount> 18 19 <MaxArrowCount>15</MaxArrowCount> 19 20 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> -
binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
24 24 </Ranged> 25 25 </Attack> 26 26 <BuildingAI> 27 <Type>Military</Type> 27 28 <DefaultArrowCount>1</DefaultArrowCount> 28 29 <MaxArrowCount>15</MaxArrowCount> 29 30 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> -
binaries/data/mods/public/simulation/templates/structures/sele_military_colony.xml
14 14 </Ranged> 15 15 </Attack> 16 16 <BuildingAI> 17 <Type>Military</Type> 17 18 <DefaultArrowCount>1</DefaultArrowCount> 18 19 <MaxArrowCount>15</MaxArrowCount> 19 20 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> -
binaries/data/mods/public/simulation/templates/template_structure.xml
11 11 </Foundation> 12 12 </Armour> 13 13 <BuildingAI> 14 <Type>Military</Type> 14 15 <DefaultArrowCount>0</DefaultArrowCount> 15 16 <GarrisonArrowMultiplier>0</GarrisonArrowMultiplier> 16 17 <GarrisonArrowClasses>Ranged Infantry</GarrisonArrowClasses> -
binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
24 24 </Ranged> 25 25 </Attack> 26 26 <BuildingAI> 27 <Type>Military</Type> 27 28 <DefaultArrowCount>3</DefaultArrowCount> 28 29 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 29 30 </BuildingAI> -
binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml
16 16 </Ranged> 17 17 </Attack> 18 18 <BuildingAI> 19 <Type>Military</Type> 19 20 <DefaultArrowCount>1</DefaultArrowCount> 20 21 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 21 22 <GarrisonArrowClasses>Infantry</GarrisonArrowClasses> -
binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml
24 24 </Ranged> 25 25 </Attack> 26 26 <BuildingAI> 27 <Type>Military</Type> 27 28 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 28 29 </BuildingAI> 29 30 <BuildRestrictions> -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml
20 20 </Ranged> 21 21 </Attack> 22 22 <BuildingAI> 23 <Type>Military</Type> 23 24 <DefaultArrowCount>0</DefaultArrowCount> 24 25 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 25 26 <GarrisonArrowClasses>Infantry</GarrisonArrowClasses> -
binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
15 15 </Ranged> 16 16 </Attack> 17 17 <BuildingAI> 18 <Type>Military</Type> 18 19 <DefaultArrowCount>3</DefaultArrowCount> 19 20 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> 20 21 <GarrisonArrowClasses>Infantry Ranged</GarrisonArrowClasses> -
binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml
5 5 <Pierce>40</Pierce> 6 6 <Crush>5</Crush> 7 7 </Armour> 8 <BuildingAI> 9 <Type>Civil</Type> 10 </BuildingAI> 8 11 <BuildRestrictions> 9 12 <Category>Field</Category> 10 13 </BuildRestrictions> … … 22 25 <Health> 23 26 <Max>250</Max> 24 27 <SpawnEntityOnDeath>rubble/rubble_field</SpawnEntityOnDeath> 28 <IdleDecayRate>1</IdleDecayRate> 29 <IdleDecayAmount>5</IdleDecayAmount> 25 30 </Health> 26 31 <Identity> 27 32 <GenericName>Field</GenericName> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml
14 14 </Ranged> 15 15 </Attack> 16 16 <BuildingAI> 17 <Type>Military</Type> 17 18 <DefaultArrowCount>2</DefaultArrowCount> 18 19 <MaxArrowCount>10</MaxArrowCount> 19 20 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml
22 22 </Ranged> 23 23 </Attack> 24 24 <BuildingAI> 25 <Type>Military</Type> 25 26 <DefaultArrowCount>1</DefaultArrowCount> 26 27 <MaxArrowCount>10</MaxArrowCount> 27 28 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml
14 14 </Ranged> 15 15 </Attack> 16 16 <BuildingAI> 17 <Type>Military</Type> 17 18 <DefaultArrowCount>3</DefaultArrowCount> 18 19 <MaxArrowCount>13</MaxArrowCount> 19 20 <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml
26 26 </Capture> 27 27 </Attack> 28 28 <BuildingAI> 29 <Type>Military</Type> 29 30 <DefaultArrowCount>1</DefaultArrowCount> 30 31 <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier> 31 32 <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>