Ticket #3610: DamageAfterDeathOfAtacker.patch
File DamageAfterDeathOfAtacker.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 // Store the owner in the damage component in case the attacker dies before the projectile hits its target. 580 cmpDamage.entityOwners.set(this.entity, Engine.QueryInterface(this.entity, IID_Ownership).GetOwner()); 581 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data); 567 582 } 568 583 else if (type == "Capture") 569 584 { … … 594 609 else 595 610 { 596 611 // Melee attack - hurt the target immediately 597 Damage.CauseDamage({612 cmpDamage.CauseDamage({ 598 613 "strengths": this.GetAttackStrengths(type), 599 614 "target": target, 600 615 "attacker": this.entity, … … 605 620 // TODO: charge attacks (need to design how they work) 606 621 }; 607 622 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 623 Attack.prototype.OnValueModification = function(msg) 724 624 { 725 625 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 var 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 // Tests whether it point is inside of ent's footprint 28 Damage.prototype.testCollision = function(ent, point, lateness) 29 { 30 let targetPosition = this.InterpolatedLocation(ent, lateness); 31 if (!targetPosition) 32 return false; 33 let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint); 34 if (!cmpFootprint) 35 return false; 36 let targetShape = cmpFootprint.GetShape(); 37 38 if (!targetShape || !targetPosition) 39 return false; 40 41 if (targetShape.type === 'circle') 42 { 43 // Use VectorDistanceSquared and square targetShape.radius to avoid square roots. 44 return (targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius)); 45 } 46 else 47 { 48 let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y; 49 50 let d = Vector3D.sub(point, targetPosition); 51 d = Vector2D.from3D(d).rotate(-angle); 52 53 return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2); 54 } 55 }; 56 57 Damage.prototype.MissileHit = function(data, lateness) 58 { 59 let targetPosition = this.InterpolatedLocation(data.target, lateness); 60 if (!targetPosition) 61 return; 62 63 // Do this first in case the direct hit kills the target 64 if (data.isSplash) 65 { 66 let playersToDamage; 67 68 if (!data.friendlyFire) 69 { 70 let cmpPlayer = QueryPlayerIDInterface(data.playerId); 71 playersToDamage = cmpPlayer.GetEnemies(); 72 } 73 74 this.CauseSplashDamage({ 75 "attacker": data.attacker, 76 "origin": Vector2D.from3D(data.position), 77 "radius": data.radius, 78 "shape": data.shape, 79 "strengths": data.strengths, 80 "direction": data.direction, 81 "playersToDamage": playersToDamage, 82 "type": data.type 83 }); 84 } 85 86 if (this.testCollision(data.target, data.position, lateness)) 87 { 88 // Hit the primary target 89 this.CauseDamage(data); 90 91 // Remove the projectile 92 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 93 cmpProjectileManager.RemoveProjectile(data.projectileId); 94 } 95 else 96 { 97 // If we didn't hit the main target look for nearby units 98 let cmpPlayer = QueryPlayerIDInterface(data.playerId); 99 let ents = this.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies()); 100 101 for (let i = 0; i < ents.length; ++i) 102 { 103 if (!this.testCollision(ents[i], data.position, lateness)) 104 continue; 105 106 this.CauseDamage({ 107 "strengths": data.strengths, 108 "target": ents[i], 109 "attacker": data.attacker, 110 "multiplier": data.multiplier, 111 "type": data.type 112 }); 113 114 // Remove the projectile 115 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 116 cmpProjectileManager.RemoveProjectile(data.projectileId); 117 } 118 } 119 let cmpHealth = Engine.QueryInterface(data.attacker, IID_Health); 120 if (cmpHealth && cmpHealth.GetHitpoints() != 0) // Remove killer data if it's still alive. 121 { 122 this.entityOwners.delete(data.attacker); 123 } 124 }; 125 126 /** 127 * Damages units around a given origin. 128 * data.attacker = <entity id> 129 * data.origin = <Vector2D> 130 * data.radius = <int> 131 * data.shape = <string> 132 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>} 133 * data.type = <string> 134 * ***Optional Variables*** 135 * data.direction = <unit vector> 136 * data.playersToDamage = <array of player ids> 137 */ 138 Damage.prototype.CauseSplashDamage = function(data) 139 { 140 // Get nearby entities and define variables 141 var nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage); 142 var damageMultiplier = 1; 143 // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin. 144 for each (var entity in nearEnts) 145 { 146 var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition2D(); 147 if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction 148 { 149 var squaredDistanceFromOrigin = data.origin.distanceToSquared(entityPosition); 150 damageMultiplier = 1 - squaredDistanceFromOrigin / (data.radius * data.radius); 151 } 152 else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) 153 { 154 // Get position of entity relative to splash origin. 155 var relativePos = entityPosition.sub(data.origin); 156 157 // The width of linear splash is one fifth of the normal splash radius. 158 var width = data.radius/5; 159 160 // Effectivly rotate the axis to align with the missile direction. 161 var parallelDist = relativePos.dot(data.direction); // z axis 162 var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis 163 164 // Check that the unit is within the distance at which it will get damaged. 165 if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions 166 damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius) 167 * (width * width - perpDist * perpDist) / (width * width); 168 else 169 damageMultiplier = 0; 170 } 171 else // In case someone calls this function with an invalid shape. 172 { 173 warn("The " + data.shape + " splash damage shape is not implemented!"); 174 } 175 // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... 176 this.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":data.type + ".Splash"}); 177 } 178 }; 179 180 /** 181 * Causes damage on a given unit 182 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>} 183 * data.target = <entity id> 184 * data.attacker = <entity id> 185 * data.multiplier = <float between 1 and 0> 186 * data.type = <string> 187 */ 188 Damage.prototype.CauseDamage = function(data) 189 { 190 // Check the target can be damaged otherwise don't do anything. 191 var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); 192 var cmpHealth = Engine.QueryInterface(data.target, IID_Health); 193 if (!cmpDamageReceiver || !cmpHealth) 194 return; 195 196 // Damage the target 197 var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier); 198 199 var cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion); 200 var cmpLoot = Engine.QueryInterface(data.target, IID_Loot); 201 if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0) 202 cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints()); 203 204 // If the target was killed run some cleanup 205 if (targetState.killed) 206 this.TargetKilled(data.attacker, data.target); 207 208 // Post the network command (make it work in multiplayer) 209 Engine.PostMessage(data.target, MT_Attacked, {"attacker":data.attacker, "target":data.target, "type":data.type, "damage":-targetState.change}); 210 211 // Play attacking sounds 212 PlaySound("attack_impact", data.attacker); 213 }; 214 215 /** 216 * Gets entities near a give point for given players. 217 * origin = <Vector2D> 218 * radius = <int> 219 * players = <array> 220 * If players is not included, entities from all players are used. 221 */ 222 Damage.prototype.EntitiesNearPoint = function(origin, radius, players) 223 { 224 // If there is insufficient data return an empty array. 225 if (!origin || !radius) 226 return []; 227 228 // If the players parameter is not specified use all players. 229 if (!players) 230 { 231 var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); 232 players = []; 233 for each (var entity in playerEntities) 234 players.push(Engine.QueryInterface(entity, IID_Player).GetPlayerID()); 235 } 236 237 // Call RangeManager with dummy entity and return the result. 238 var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 239 var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver); 240 return rangeQuery; 241 }; 242 243 /** 244 * Called when some units kills something (another unit, building, animal etc) 245 * killerEntity = <entity id> 246 * targetEntity = <entity id> 247 */ 248 Damage.prototype.TargetKilled = function(killerEntity, targetEntity) 249 { 250 // Add to killer statistics. 251 var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker); 252 if (cmpKillerPlayerStatisticsTracker) 253 cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity); 254 // Add to loser statistics. 255 var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker); 256 if (cmpTargetPlayerStatisticsTracker) 257 cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity); 258 259 // If killer can collect loot, let's try to collect it. 260 var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter); 261 if (cmpLooter) 262 cmpLooter.Collect(targetEntity); 263 }; 264 265 266 Damage.prototype.GetDeadEntityOwner = function(entityId) 267 { 268 var cmpOwner = this.entityOwners.get(entityId); 269 this.entityOwners.delete(entityId); 270 return cmpOwner; 271 }; 272 273 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