Ticket #3610: DamageAfterDeathOfAtacker_v2.patch
File DamageAfterDeathOfAtacker_v2.patch, 24.7 KB (added by , 8 years ago) |
---|
-
data/mods/public/gui/credits/texts/programming.json
108 108 {"nick": "kingadami", "name": "Adam Winsor"}, 109 109 {"nick": "kingbasil", "name": "Giannis Fafalios"}, 110 110 {"nick": "lafferjm", "name": "Justin Lafferty"}, 111 {"nick": "LeanderH", "name": "Leander Hemelhof"}, 111 112 {"nick": "leper", "name": "Georg Kilzer"}, 112 113 {"nick": "LittleDev"}, 113 114 {"nick": "livingaftermidnight", "name": "Will Dull"}, -
data/mods/public/simulation/components/Attack.js
487 487 */ 488 488 Attack.prototype.PerformAttack = function(type, target) 489 489 { 490 let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage); 490 491 // If this is a ranged attack, then launch a projectile 491 492 if (type == "Ranged") 492 493 { … … 556 557 557 558 let playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(); 558 559 cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 559 cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget * 1000, { 560 "type": type, 561 "target": target, 562 "position": realTargetPosition, 563 "direction": missileDirection, 564 "projectileId": id, 565 "playerId":playerId 566 }); 560 let data = { 561 "type": type, 562 "attacker": this.entity, 563 "target": target, 564 "strengths": this.GetAttackStrengths(type), 565 "position": realTargetPosition, 566 "direction": missileDirection, 567 "projectileId": id, 568 "playerId": playerId, 569 "multiplier": this.GetAttackBonus(type, target), 570 "isSplash": false 571 } 572 if (this.template.Ranged.Splash) 573 { 574 data.friendlyFire = this.template.Ranged.Splash.FriendlyFire; 575 data.range = this.template.Ranged.Splash.Range; 576 data.shape = this.template.Ranged.Splash.Shape; 577 data.isSplash = true; 578 } 579 cmpDamage.entityOwners.set(this.entity, Engine.QueryInterface(this.entity, IID_Ownership).GetOwner()); 580 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data); 567 581 } 568 582 else if (type == "Capture") 569 583 { … … 594 608 else 595 609 { 596 610 // Melee attack - hurt the target immediately 597 Damage.CauseDamage({611 cmpDamage.CauseDamage({ 598 612 "strengths": this.GetAttackStrengths(type), 599 613 "target": target, 600 614 "attacker": this.entity, … … 605 619 // TODO: charge attacks (need to design how they work) 606 620 }; 607 621 608 Attack.prototype.InterpolatedLocation = function(ent, lateness)609 {610 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);611 let turnLength = cmpTimer.GetLatestTurnLength()/1000;612 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);613 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly614 return undefined;615 let curPos = cmpTargetPosition.GetPosition();616 let prevPos = cmpTargetPosition.GetPreviousPosition();617 lateness /= 1000;618 return new Vector3D((curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,619 0,620 (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength);621 };622 623 // Tests whether it point is inside of ent's footprint624 Attack.prototype.testCollision = function(ent, point, lateness)625 {626 let targetPosition = this.InterpolatedLocation(ent, lateness);627 if (!targetPosition)628 return false;629 let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);630 if (!cmpFootprint)631 return false;632 let targetShape = cmpFootprint.GetShape();633 634 if (!targetShape || !targetPosition)635 return false;636 637 if (targetShape.type === 'circle')638 {639 // Use VectorDistanceSquared and square targetShape.radius to avoid square roots.640 return (targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius));641 }642 else643 {644 let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;645 646 let d = Vector3D.sub(point, targetPosition);647 d = Vector2D.from3D(d).rotate(-angle);648 649 return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);650 }651 };652 653 Attack.prototype.MissileHit = function(data, lateness)654 {655 let targetPosition = this.InterpolatedLocation(data.target, lateness);656 if (!targetPosition)657 return;658 659 // Do this first in case the direct hit kills the target660 if (this.template.Ranged.Splash)661 {662 let friendlyFire = this.template.Ranged.Splash.FriendlyFire;663 let splashRadius = this.template.Ranged.Splash.Range;664 let splashShape = this.template.Ranged.Splash.Shape;665 let playersToDamage;666 667 if (friendlyFire == "false")668 {669 let cmpPlayer = QueryPlayerIDInterface(data.playerId);670 playersToDamage = cmpPlayer.GetEnemies();671 }672 673 Damage.CauseSplashDamage({674 "attacker": this.entity,675 "origin": Vector2D.from3D(data.position),676 "radius": splashRadius,677 "shape": splashShape,678 "strengths": this.GetAttackStrengths(data.type),679 "direction": data.direction,680 "playersToDamage": playersToDamage,681 "type": data.type682 });683 }684 685 if (this.testCollision(data.target, data.position, lateness))686 {687 data.attacker = this.entity;688 data.multiplier = this.GetAttackBonus(data.type, data.target);689 data.strengths = this.GetAttackStrengths(data.type);690 // Hit the primary target691 Damage.CauseDamage(data);692 693 // Remove the projectile694 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);695 cmpProjectileManager.RemoveProjectile(data.projectileId);696 }697 else698 {699 // If we didn't hit the main target look for nearby units700 let cmpPlayer = QueryPlayerIDInterface(data.playerId);701 let ents = Damage.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());702 703 for (let i = 0; i < ents.length; ++i)704 {705 if (!this.testCollision(ents[i], data.position, lateness))706 continue;707 708 Damage.CauseDamage({709 "strengths": this.GetAttackStrengths(data.type),710 "target": ents[i],711 "attacker": this.entity,712 "multiplier": this.GetAttackBonus(data.type, ents[i]),713 "type": data.type714 });715 716 // Remove the projectile717 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);718 cmpProjectileManager.RemoveProjectile(data.projectileId);719 }720 }721 };722 723 622 Attack.prototype.OnValueModification = function(msg) 724 623 { 725 624 if (msg.component != "Attack") -
data/mods/public/simulation/components/AttackDetection.js
63 63 if (cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID()) 64 64 return; 65 65 var cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); 66 let attackerOwner; 67 if (cmpAttackerOwnership) 68 attackerOwner = cmpAttackerOwnership.GetOwner(); 69 else // The attacker is dead, so we use the data stored in the Damage component 70 attackerOwner = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage).GetDeadEntityOwner(attacker); 66 71 // Don't register attacks dealt by myself 67 if ( cmpAttackerOwnership.GetOwner()== cmpPlayer.GetPlayerID())72 if (attackerOwner == cmpPlayer.GetPlayerID()) 68 73 return; 69 74 70 75 // Since livestock can be attacked/gathered by other players … … 122 127 "type": "attack", 123 128 "target": target, 124 129 "players": [cmpPlayer.GetPlayerID()], 125 "attacker": cmpAttackerOwnership.GetOwner(),130 "attacker": attackerOwner, 126 131 "targetIsDomesticAnimal": targetIsDomesticAnimal 127 132 }); 128 133 PlaySound("attacked", target); -
data/mods/public/simulation/components/Damage.js
1 function Damage() {} 2 3 Damage.prototype.Schema = 4 "<a:component type='system'/><empty/>"; 5 6 Damage.prototype.Init = function() 7 { 8 }; 9 10 Damage.prototype.entityOwners = new Map(); 11 12 Damage.prototype.InterpolatedLocation = function(ent, lateness) 13 { 14 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 15 let turnLength = cmpTimer.GetLatestTurnLength()/1000; 16 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); 17 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly 18 return undefined; 19 let curPos = cmpTargetPosition.GetPosition(); 20 let prevPos = cmpTargetPosition.GetPreviousPosition(); 21 lateness /= 1000; 22 return new Vector3D((curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength, 23 0, 24 (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength); 25 }; 26 /** 27 * Test if a point is inside of an entities footprint 28 * ent = <entity id> 29 * point = <Vector2D> 30 * lateness = <int> 31 */ 32 Damage.prototype.TestCollision = function(ent, point, lateness) 33 { 34 let targetPosition = this.InterpolatedLocation(ent, lateness); 35 if (!targetPosition) 36 return false; 37 let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint); 38 if (!cmpFootprint) 39 return false; 40 let targetShape = cmpFootprint.GetShape(); 41 42 if (!targetShape || !targetPosition) 43 return false; 44 45 if (targetShape.type === 'circle') 46 { 47 // Use VectorDistanceSquared and square targetShape.radius to avoid square roots. 48 return (targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius)); 49 } 50 else 51 { 52 let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y; 53 54 let d = Vector3D.sub(point, targetPosition); 55 d = Vector2D.from3D(d).rotate(-angle); 56 57 return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2); 58 } 59 }; 60 61 Damage.prototype.MissileHit = function(data, lateness) 62 { 63 let targetPosition = this.InterpolatedLocation(data.target, lateness); 64 if (!targetPosition) 65 return; 66 67 // Do this first in case the direct hit kills the target 68 if (data.isSplash) 69 { 70 let playersToDamage; 71 72 if (!data.friendlyFire) 73 { 74 let cmpPlayer = QueryPlayerIDInterface(data.playerId); 75 playersToDamage = cmpPlayer.GetEnemies(); 76 } 77 78 this.CauseSplashDamage({ 79 "attacker": data.attacker, 80 "origin": Vector2D.from3D(data.position), 81 "radius": data.radius, 82 "shape": data.shape, 83 "strengths": data.strengths, 84 "direction": data.direction, 85 "playersToDamage": playersToDamage, 86 "type": data.type 87 }); 88 } 89 90 if (this.TestCollision(data.target, data.position, lateness)) 91 { 92 // Hit the primary target 93 this.CauseDamage(data); 94 95 // Remove the projectile 96 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 97 cmpProjectileManager.RemoveProjectile(data.projectileId); 98 } 99 else 100 { 101 // If we didn't hit the main target look for nearby units 102 let cmpPlayer = QueryPlayerIDInterface(data.playerId); 103 let ents = this.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies()); 104 105 for (let i = 0; i < ents.length; ++i) 106 { 107 if (!this.TestCollision(ents[i], data.position, lateness)) 108 continue; 109 110 this.CauseDamage({ 111 "strengths": data.strengths, 112 "target": ents[i], 113 "attacker": data.attacker, 114 "multiplier": data.multiplier, 115 "type": data.type 116 }); 117 118 // Remove the projectile 119 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 120 cmpProjectileManager.RemoveProjectile(data.projectileId); 121 } 122 } 123 let cmpHealth = Engine.QueryInterface(data.attacker, IID_Health); 124 if (cmpHealth && cmpHealth.GetHitpoints() != 0) // Remove killer data if it's still alive. 125 { 126 this.entityOwners.delete(data.attacker); 127 } 128 }; 129 130 /** 131 * Damages units around a given origin. 132 * data.attacker = <entity id> 133 * data.origin = <Vector2D> 134 * data.radius = <int> 135 * data.shape = <string> 136 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>} 137 * data.type = <string> 138 * ***Optional Variables*** 139 * data.direction = <unit vector> 140 * data.playersToDamage = <array of player ids> 141 */ 142 Damage.prototype.CauseSplashDamage = function(data) 143 { 144 // Get nearby entities and define variables 145 var nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage); 146 var damageMultiplier = 1; 147 // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin. 148 for each (var entity in nearEnts) 149 { 150 var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition2D(); 151 if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction 152 { 153 var squaredDistanceFromOrigin = data.origin.distanceToSquared(entityPosition); 154 damageMultiplier = 1 - squaredDistanceFromOrigin / (data.radius * data.radius); 155 } 156 else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) 157 { 158 // Get position of entity relative to splash origin. 159 var relativePos = entityPosition.sub(data.origin); 160 161 // The width of linear splash is one fifth of the normal splash radius. 162 var width = data.radius/5; 163 164 // Effectivly rotate the axis to align with the missile direction. 165 var parallelDist = relativePos.dot(data.direction); // z axis 166 var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis 167 168 // Check that the unit is within the distance at which it will get damaged. 169 if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions 170 damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius) 171 * (width * width - perpDist * perpDist) / (width * width); 172 else 173 damageMultiplier = 0; 174 } 175 else // In case someone calls this function with an invalid shape. 176 { 177 warn("The " + data.shape + " splash damage shape is not implemented!"); 178 } 179 // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... 180 this.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":data.type + ".Splash"}); 181 } 182 }; 183 184 /** 185 * Causes damage on a given unit 186 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>} 187 * data.target = <entity id> 188 * data.attacker = <entity id> 189 * data.multiplier = <float between 1 and 0> 190 * data.type = <string> 191 */ 192 Damage.prototype.CauseDamage = function(data) 193 { 194 // Check the target can be damaged otherwise don't do anything. 195 var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); 196 var cmpHealth = Engine.QueryInterface(data.target, IID_Health); 197 if (!cmpDamageReceiver || !cmpHealth) 198 return; 199 200 // Damage the target 201 var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier); 202 203 var cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion); 204 var cmpLoot = Engine.QueryInterface(data.target, IID_Loot); 205 if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0) 206 cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints()); 207 208 // If the target was killed run some cleanup 209 if (targetState.killed) 210 this.TargetKilled(data.attacker, data.target); 211 212 // Post the network command (make it work in multiplayer) 213 Engine.PostMessage(data.target, MT_Attacked, {"attacker":data.attacker, "target":data.target, "type":data.type, "damage":-targetState.change}); 214 215 // Play attacking sounds 216 PlaySound("attack_impact", data.attacker); 217 }; 218 219 /** 220 * Gets entities near a give point for given players. 221 * origin = <Vector2D> 222 * radius = <int> 223 * players = <array> 224 * If players is not included, entities from all players are used. 225 */ 226 Damage.prototype.EntitiesNearPoint = function(origin, radius, players) 227 { 228 // If there is insufficient data return an empty array. 229 if (!origin || !radius) 230 return []; 231 232 // If the players parameter is not specified use all players. 233 if (!players) 234 { 235 var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); 236 players = []; 237 for each (var entity in playerEntities) 238 players.push(Engine.QueryInterface(entity, IID_Player).GetPlayerID()); 239 } 240 241 // Call RangeManager with dummy entity and return the result. 242 var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 243 var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver); 244 return rangeQuery; 245 }; 246 247 /** 248 * Called when some units kills something (another unit, building, animal etc) 249 * killerEntity = <entity id> 250 * targetEntity = <entity id> 251 */ 252 Damage.prototype.TargetKilled = function(killerEntity, targetEntity) 253 { 254 // Add to killer statistics. 255 var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker); 256 if (cmpKillerPlayerStatisticsTracker) 257 cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity); 258 // Add to loser statistics. 259 var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker); 260 if (cmpTargetPlayerStatisticsTracker) 261 cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity); 262 263 // If killer can collect loot, let's try to collect it. 264 var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter); 265 if (cmpLooter) 266 cmpLooter.Collect(targetEntity); 267 }; 268 269 270 Damage.prototype.GetDeadEntityOwner = function(entityId) 271 { 272 var cmpOwner = this.entityOwners.get(entityId); 273 this.entityOwners.delete(entityId); 274 return cmpOwner; 275 }; 276 277 Engine.RegisterSystemComponentType(IID_Damage, "Damage", Damage); -
data/mods/public/simulation/components/interfaces/Damage.js
Property changes on: data/mods/public/simulation/components/Damage.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 Engine.RegisterInterface("Damage"); -
data/mods/public/simulation/helpers/Damage.js
Property changes on: data/mods/public/simulation/components/interfaces/Damage.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 // Create global Damage object.2 var Damage = {};3 4 /**5 * Damages units around a given origin.6 * data.attacker = <entity id>7 * data.origin = <Vector2D>8 * data.radius = <int>9 * data.shape = <string>10 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}11 * data.type = <string>12 * ***Optional Variables***13 * data.direction = <unit vector>14 * data.playersToDamage = <array of player ids>15 */16 Damage.CauseSplashDamage = function(data)17 {18 // Get nearby entities and define variables19 var nearEnts = Damage.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);20 var damageMultiplier = 1;21 // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.22 for each (var entity in nearEnts)23 {24 var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition2D();25 if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction26 {27 var squaredDistanceFromOrigin = data.origin.distanceToSquared(entityPosition);28 damageMultiplier = 1 - squaredDistanceFromOrigin / (data.radius * data.radius);29 }30 else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)31 {32 // Get position of entity relative to splash origin.33 var relativePos = entityPosition.sub(data.origin);34 35 // The width of linear splash is one fifth of the normal splash radius.36 var width = data.radius/5;37 38 // Effectivly rotate the axis to align with the missile direction.39 var parallelDist = relativePos.dot(data.direction); // z axis40 var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis41 42 // Check that the unit is within the distance at which it will get damaged.43 if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions44 damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius)45 * (width * width - perpDist * perpDist) / (width * width);46 else47 damageMultiplier = 0;48 }49 else // In case someone calls this function with an invalid shape.50 {51 warn("The " + data.shape + " splash damage shape is not implemented!");52 }53 // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....54 Damage.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":data.type + ".Splash"});55 }56 };57 58 /**59 * Causes damage on a given unit60 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}61 * data.target = <entity id>62 * data.attacker = <entity id>63 * data.multiplier = <float between 1 and 0>64 * data.type = <string>65 */66 Damage.CauseDamage = function(data)67 {68 // Check the target can be damaged otherwise don't do anything.69 var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);70 var cmpHealth = Engine.QueryInterface(data.target, IID_Health);71 if (!cmpDamageReceiver || !cmpHealth)72 return;73 74 // Damage the target75 var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier);76 77 var cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion);78 var cmpLoot = Engine.QueryInterface(data.target, IID_Loot);79 if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0)80 cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints());81 82 // If the target was killed run some cleanup83 if (targetState.killed)84 Damage.TargetKilled(data.attacker, data.target);85 86 // Post the network command (make it work in multiplayer)87 Engine.PostMessage(data.target, MT_Attacked, {"attacker":data.attacker, "target":data.target, "type":data.type, "damage":-targetState.change});88 89 // Play attacking sounds90 PlaySound("attack_impact", data.attacker);91 };92 93 /**94 * Gets entities near a give point for given players.95 * origin = <Vector2D>96 * radius = <int>97 * players = <array>98 * If players is not included, entities from all players are used.99 */100 Damage.EntitiesNearPoint = function(origin, radius, players)101 {102 // If there is insufficient data return an empty array.103 if (!origin || !radius)104 return [];105 106 // If the players parameter is not specified use all players.107 if (!players)108 {109 var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();110 players = [];111 for each (var entity in playerEntities)112 players.push(Engine.QueryInterface(entity, IID_Player).GetPlayerID());113 }114 115 // Call RangeManager with dummy entity and return the result.116 var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);117 var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);118 return rangeQuery;119 };120 121 /**122 * Called when some units kills something (another unit, building, animal etc)123 * killerEntity = <entity id>124 * targetEntity = <entity id>125 */126 Damage.TargetKilled = function(killerEntity, targetEntity)127 {128 // Add to killer statistics.129 var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);130 if (cmpKillerPlayerStatisticsTracker)131 cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);132 // Add to loser statistics.133 var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);134 if (cmpTargetPlayerStatisticsTracker)135 cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);136 137 // If killer can collect loot, let's try to collect it.138 var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);139 if (cmpLooter)140 cmpLooter.Collect(targetEntity);141 };142 143 Engine.RegisterGlobal("Damage", Damage);144