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