Ticket #3406: decay_field_v1.patch
File decay_field_v1.patch, 42.4 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'>Militar</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 if (this.buildingType === 'Militar') 41 { 42 this.currentRound = 0; 43 this.archersGarrisoned = 0; 44 this.arrowsLeft = 0; 45 this.targetUnits = []; 46 } 31 47 }; 32 48 33 49 BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) 34 50 { 51 // 52 // If the building is not of militar type, nothing should be done 53 // 54 if(this.buildingType !== 'Militar') 55 return; 56 35 57 let classes = this.template.GarrisonArrowClasses; 36 58 37 59 for (let ent of msg.added) … … 55 77 56 78 BuildingAI.prototype.OnOwnershipChanged = function(msg) 57 79 { 80 // If the building is of civil type, then the moment it is finished 81 // being constructed, start the timer for the decaying process. 82 // 83 // This covers the scenario when the building is built in a queue where the builder 84 // goes for other structures leaving the buildind behind. If they don't 85 // return then the building should decay, otherwise the timer would be 86 // stopped inside the FSM in UnitAI 87 if(msg.from == -1 && this.buildingType === 'Civil') 88 { 89 this.DecayBuilding(); 90 return; 91 } 92 58 93 this.targetUnits = []; 59 94 this.SetupRangeQuery(); 60 95 this.SetupGaiaRangeQuery(); … … 62 97 63 98 BuildingAI.prototype.OnDiplomacyChanged = function(msg) 64 99 { 100 // 101 // If the building is not of militar type, nothing should be done 102 // 103 if(this.buildingType !== 'Militar') 104 return; 105 65 106 if (!IsOwnedByPlayer(msg.player, this.entity)) 66 107 return; 67 108 … … 73 114 74 115 BuildingAI.prototype.OnDestroy = function() 75 116 { 117 // 118 // If the building is not of militar type, nothing should be done 119 // 120 if(this.buildingType !== 'Militar') 121 return; 122 76 123 if (this.timer) 77 124 { 78 125 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); … … 93 140 */ 94 141 BuildingAI.prototype.OnValueModification = function(msg) 95 142 { 143 // 144 // If the building is not of militar type, nothing should be done 145 // 146 if(this.buildingType !== 'Militar') 147 return; 148 96 149 if (msg.component != "Attack") 97 150 return; 98 151 … … 106 159 */ 107 160 BuildingAI.prototype.SetupRangeQuery = function() 108 161 { 109 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);162 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 110 163 if (!cmpAttack) 111 164 return; 112 165 113 varcmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);166 let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 114 167 if (this.enemyUnitsQuery) 115 168 { 116 169 cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); … … 117 170 this.enemyUnitsQuery = undefined; 118 171 } 119 172 120 varcmpPlayer = QueryOwnerInterface(this.entity);173 let cmpPlayer = QueryOwnerInterface(this.entity); 121 174 if (!cmpPlayer) 122 175 return; 123 176 124 varenemies = [];125 varnumPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();177 let enemies = []; 178 let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); 126 179 for (let i = 1; i < numPlayers; ++i) 127 180 if (cmpPlayer.IsEnemy(i)) 128 181 enemies.push(i); … … 130 183 if (!enemies.length) 131 184 return; 132 185 133 varrange = cmpAttack.GetRange(attackType);186 let range = cmpAttack.GetRange(attackType); 134 187 this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( 135 188 this.entity, range.min, range.max, range.elevationBonus, 136 189 enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); … … 142 195 // This should be called whenever our ownership changes. 143 196 BuildingAI.prototype.SetupGaiaRangeQuery = function() 144 197 { 145 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);198 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 146 199 if (!cmpAttack) 147 200 return; 148 201 149 varcmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);202 let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 150 203 if (this.gaiaUnitsQuery) 151 204 { 152 205 cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); … … 153 206 this.gaiaUnitsQuery = undefined; 154 207 } 155 208 156 varcmpPlayer = QueryOwnerInterface(this.entity);209 let cmpPlayer = QueryOwnerInterface(this.entity); 157 210 if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) 158 211 return; 159 212 160 varrange = cmpAttack.GetRange(attackType);213 let range = cmpAttack.GetRange(attackType); 161 214 162 215 // This query is only interested in Gaia entities that can attack. 163 216 this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( … … 172 225 */ 173 226 BuildingAI.prototype.OnRangeUpdate = function(msg) 174 227 { 228 // 229 // If the building is not of militar type, nothing should be done 230 // 231 if(this.buildingType !== 'Militar') 232 return; 175 233 176 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);234 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 177 235 if (!cmpAttack) 178 236 return; 179 237 … … 202 260 } 203 261 204 262 if (this.targetUnits.length) 205 this. StartTimer();263 this.AttackNearEntities(); 206 264 }; 207 265 208 BuildingAI.prototype. StartTimer= function()266 BuildingAI.prototype.AttackNearEntities = function() 209 267 { 210 268 if (this.timer) 211 269 return; 212 270 213 varcmpAttack = Engine.QueryInterface(this.entity, IID_Attack);271 let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 214 272 if (!cmpAttack) 215 273 return; 274 let attackTimers = cmpAttack.GetTimers(attackType); 275 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 216 276 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); 277 this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null); 222 278 }; 223 279 224 280 BuildingAI.prototype.GetDefaultArrowCount = function() 225 281 { 226 vararrowCount = +this.template.DefaultArrowCount;282 let arrowCount = +this.template.DefaultArrowCount; 227 283 return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity); 228 284 }; 229 285 … … 238 294 239 295 BuildingAI.prototype.GetGarrisonArrowMultiplier = function() 240 296 { 241 vararrowMult = +this.template.GarrisonArrowMultiplier;297 let arrowMult = +this.template.GarrisonArrowMultiplier; 242 298 return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); 243 299 }; 244 300 245 301 BuildingAI.prototype.GetGarrisonArrowClasses = function() 246 302 { 247 varstring = this.template.GarrisonArrowClasses;303 let string = this.template.GarrisonArrowClasses; 248 304 if (string) 249 305 return string.split(/\s+/); 250 306 return []; … … 267 323 { 268 324 this.unitAITarget = ent; 269 325 if (ent) 270 this. StartTimer();326 this.AttackNearEntities(); 271 327 }; 272 328 273 329 /** … … 302 358 arrowsToFire = this.arrowsLeft; 303 359 else 304 360 arrowsToFire = Math.min( 305 306 361 Math.round(2 * Math.random() * this.GetArrowCount() / roundCount), 362 this.arrowsLeft 307 363 ); 308 364 309 365 if (arrowsToFire <= 0) … … 364 420 */ 365 421 BuildingAI.prototype.CheckTargetVisible = function(target) 366 422 { 367 varcmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);423 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 368 424 if (!cmpOwnership) 369 425 return false; 370 426 371 427 // Entities that are hidden and miraged are considered visible 372 varcmpFogging = Engine.QueryInterface(target, IID_Fogging);428 let cmpFogging = Engine.QueryInterface(target, IID_Fogging); 373 429 if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) 374 430 return true; 375 431 … … 378 434 return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; 379 435 }; 380 436 437 /** 438 * Some buildings (like fields) are expected to decline their health when no farmer is 439 * working on them. This increases the realism of the game and adds new levels of 440 * complexity into the game. 441 * 442 * The way is expected to work is simple. When the number of workers in a field reaches 443 * zero, a timer is launched with an offset of 25 seconds, and after that the health of 444 * the field starts decaying with the values given by the template element <IdleDecayAmount></IdleDecayAmount> 445 * which is accesed through the IID_Health the field 446 */ 447 BuildingAI.prototype.DecayBuilding = function() 448 { 449 if(this.timer) 450 return; 451 452 let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 453 if(!cmpHealth || cmpHealth.GetIdleDecayRate === 0 || cmpHealth.GetIdleDecayAmount === 0) 454 return; 455 456 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 457 this.timer = cmpTimer.SetInterval(this.entity, IID_Health, "Reduce", 15000, 15000 / cmpHealth.GetIdleDecayRate(), cmpHealth.GetIdleDecayAmount()); 458 } 459 460 /** 461 * Stop the current BuildingAI timer. 462 */ 463 BuildingAI.prototype.StopTimer = function() 464 { 465 if (!this.timer) 466 return; 467 468 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 469 cmpTimer.CancelTimer(this.timer); 470 this.timer = undefined; 471 }; 472 381 473 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 varcmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);2284 let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2264 2285 if (cmpSupply) 2286 { 2265 2287 cmpSupply.RemoveGatherer(this.entity); 2288 2289 // Every time a gather leaves the farmer, check how many are left inside the field 2290 // If the number of gathers is zero, start a timer after certain offset to decay the 2291 // health of the field 2292 let supplyType = cmpSupply.GetType(); 2293 if(supplyType.generic === 'food' && supplyType.specific === 'grain' && cmpSupply.GetNumGatherers() === 0) 2294 { 2295 let cmpBuildingAI = Engine.QueryInterface(this.gatheringTarget, IID_BuildingAI); 2296 cmpBuildingAI.DecayBuilding(); 2297 } 2298 } 2266 2299 delete this.gatheringTarget; 2267 2300 2268 2301 // Show the carried resource, if we've gathered anything. … … 2270 2303 }, 2271 2304 2272 2305 "Timer": function(msg) { 2273 varresourceTemplate = this.order.data.template;2274 varresourceType = this.order.data.type;2306 let resourceTemplate = this.order.data.template; 2307 let resourceType = this.order.data.type; 2275 2308 2276 varcmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);2309 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 2277 2310 if (!cmpOwnership) 2278 2311 return; 2279 2312 2280 var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2313 let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); 2314 let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); 2281 2315 if (cmpSupply && cmpSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity)) 2282 2316 { 2283 2317 // Check we can still reach and gather from the target … … 2285 2319 { 2286 2320 // Gather the resources: 2287 2321 2288 var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);2289 2290 2322 // Try to gather treasure 2291 2323 if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget)) 2292 2324 return; … … 2297 2329 cmpResourceGatherer.DropResources(); 2298 2330 2299 2331 // Collect from the target 2300 varstatus = cmpResourceGatherer.PerformGather(this.gatheringTarget);2332 let status = cmpResourceGatherer.PerformGather(this.gatheringTarget); 2301 2333 2302 2334 // If we've collected as many resources as possible, 2303 2335 // return to the nearest dropsite 2304 2336 if (status.filled) 2305 2337 { 2306 varnearby = this.FindNearestDropsite(resourceType.generic);2338 let nearby = this.FindNearestDropsite(resourceType.generic); 2307 2339 if (nearby) 2308 2340 { 2309 2341 // (Keep this Gather order on the stack so we'll … … 2336 2368 // the old one. So try to get close to the old resource's 2337 2369 // last known position 2338 2370 2339 varmaxRange = 8; // get close but not too close2371 let maxRange = 8; // get close but not too close 2340 2372 if (this.order.data.lastPos && 2341 2373 this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z, 2342 2374 0, maxRange)) … … 2349 2381 2350 2382 // We're already in range, can't get anywhere near it or the target is exhausted. 2351 2383 2352 varherdPos = this.order.data.initPos;2384 let herdPos = this.order.data.initPos; 2353 2385 2354 2386 // Give up on this order and try our next queued order 2355 2387 // 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 2388 if (cmpResourceGatherer.IsCarrying(resourceType.generic) && 2358 2389 this.orderQueue.length > 1 && this.orderQueue[1] !== "ReturnResource" && 2359 2390 (this.orderQueue[1].type !== "Gather" || this.orderQueue[1].data.type.generic !== resourceType.generic)) … … 2369 2400 2370 2401 // Try to find a new resource of the same specific type near our current position: 2371 2402 // Also don't switch to a different type of huntable animal 2372 varnearby = this.FindNearbyResource(function (ent, type, template) {2403 let nearby = this.FindNearbyResource(function (ent, type, template) { 2373 2404 return ( 2374 2405 (type.generic == "treasure" && resourceType.generic == "treasure") 2375 2406 || (type.specific == resourceType.specific … … 2393 2424 // drop it off, and if not then we might as well head to the dropsite 2394 2425 // anyway because that's a nice enough place to congregate and idle 2395 2426 2396 varnearby = this.FindNearestDropsite(resourceType.generic);2427 let nearby = this.FindNearestDropsite(resourceType.generic); 2397 2428 if (nearby) 2398 2429 { 2399 2430 this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); … … 2636 2667 2637 2668 this.order.data.force = false; 2638 2669 2639 this.repairTarget = this.order.data.target; 2670 this.repairTarget = this.order.data.target; // temporary, deleted in "leave". 2640 2671 // Check we can still reach and repair the target 2641 2672 if (!this.CanRepair(this.repairTarget)) 2642 2673 { … … 2724 2755 let dropsiteTypes = cmpResourceDropsite.GetTypes(); 2725 2756 cmpResourceGatherer.CommitResources(dropsiteTypes); 2726 2757 this.SetGathererAnimationOverride(); 2727 } 2758 } 2728 2759 2729 2760 // We finished building it. 2730 2761 // Switch to the next order (if any) … … 2765 2796 var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite); 2766 2797 var types = cmpResourceDropsite.GetTypes(); 2767 2798 // TODO: Slightly undefined behavior here, we don't know what type of resource will be collected, 2768 // 2799 // may cause problems for AIs (especially hunting fast animals), but avoid ugly hacks to fix that! 2769 2800 var nearby = this.FindNearbyResource(function (ent, type, template) { 2770 2801 return (types.indexOf(type.generic) != -1); 2771 2802 }); … … 2799 2830 var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); 2800 2831 if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) 2801 2832 { 2802 this.pickup = this.order.data.target; 2833 this.pickup = this.order.data.target; // temporary, deleted in "leave" 2803 2834 Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); 2804 2835 } 2805 2836 }, … … 3054 3085 "ANIMAL": { 3055 3086 "Attacked": function(msg) { 3056 3087 if (this.template.NaturalBehaviour == "skittish" || 3057 3088 this.template.NaturalBehaviour == "passive") 3058 3089 { 3059 3090 this.Flee(msg.data.attacker, false); 3060 3091 } … … 3436 3467 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 3437 3468 if (this.losRangeQuery) 3438 3469 this.SetupRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losRangeQuery)); 3439 3470 3440 3471 if (this.IsHealer() && this.losHealRangeQuery) 3441 3472 this.SetupHealRangeQuery(cmpRangeManager.IsActiveQueryEnabled(this.losHealRangeQuery)); 3442 3473 }; … … 3554 3585 { 3555 3586 if (!this.orderQueue.length) 3556 3587 { 3557 var stack = new Error().stack.trimRight().replace(/^/mg, ' 3588 var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line 3558 3589 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 3559 3590 var template = cmpTemplateManager.GetCurrentTemplateName(this.entity); 3560 3591 error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); … … 5775 5806 if (cmpOwnership && IsOwnedByPlayer(cmpOwnership.GetOwner(), target)) 5776 5807 return true; 5777 5808 return cmpPlayer && cmpPlayer.HasSharedDropsites() && cmpResourceDropsite.IsShared() && 5778 5809 cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target); 5779 5810 }; 5780 5811 5781 5812 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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</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>Militar</Type> 29 30 <DefaultArrowCount>1</DefaultArrowCount> 30 31 <GarrisonArrowMultiplier>0.5</GarrisonArrowMultiplier> 31 32 <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>