Ticket #3610: DamageAfterDeathOfAtacker_v6.patch
File DamageAfterDeathOfAtacker_v6.patch, 31.5 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 cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 485 let attackerOwner; 486 if (!cmpOwnership) 487 error("PerformAttack called with an invalid ownership component"); 488 else 489 attackerOwner = cmpOwnership.GetOwner(); 490 let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage); 484 491 // If this is a ranged attack, then launch a projectile 485 492 if (type == "Ranged") 486 493 { … … 548 555 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 549 556 let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); 550 557 551 let playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();552 558 cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 553 cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget * 1000,{559 let data = { 554 560 "type": type, 561 "attacker": this.entity, 555 562 "target": target, 563 "strengths": this.GetAttackStrengths(type), 556 564 "position": realTargetPosition, 557 565 "direction": missileDirection, 558 566 "projectileId": id, 559 "playerId":playerId 560 }); 567 "multiplier": this.GetAttackBonus(type, target), 568 "isSplash": false, 569 "attackerOwner": attackerOwner 570 }; 571 if (this.template.Ranged.Splash) 572 { 573 data.friendlyFire = this.template.Ranged.Splash.FriendlyFire; 574 data.radius = +this.template.Ranged.Splash.Range; 575 data.shape = this.template.Ranged.Splash.Shape; 576 data.isSplash = true; 577 } 578 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data); 561 579 } 562 580 else if (type == "Capture") 563 581 { … … 582 600 "attacker": this.entity, 583 601 "target": target, 584 602 "type": type, 585 "damage": strength 603 "damage": strength, 604 "attackerOwner": attackerOwner 586 605 }); 587 606 } 588 607 else 589 608 { 590 609 // Melee attack - hurt the target immediately 591 Damage.CauseDamage({610 cmpDamage.CauseDamage({ 592 611 "strengths": this.GetAttackStrengths(type), 593 612 "target": target, 594 613 "attacker": this.entity, 595 614 "multiplier": this.GetAttackBonus(type, target), 596 "type":type 615 "type":type, 616 "attackerOwner": attackerOwner 597 617 }); 598 618 } 599 619 // TODO: charge attacks (need to design how they work) 600 620 }; 601 621 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 622 Attack.prototype.OnValueModification = function(msg) 715 623 { 716 624 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 var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);60 let playerID = Engine.QueryInterface(this.entity, IID_Player).GetPlayerID(); 61 61 var cmpTargetOwnership = Engine.QueryInterface(target, IID_Ownership); 62 if (!cmpTargetOwnership) 63 error("PerformAttack called with an invalid ownership component"); 62 64 // Don't register attacks dealt against other players 63 if (cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID())65 if (cmpTargetOwnership.GetOwner() != playerID) 64 66 return; 65 var cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); 67 66 68 // Don't register attacks dealt by myself 67 if ( cmpAttackerOwnership.GetOwner() == cmpPlayer.GetPlayerID())69 if (attackerOwner == playerID) 68 70 return; 69 71 70 72 // Since livestock can be attacked/gathered by other players … … 117 119 if (!isPriorityIncreased) 118 120 this.AddSuppression(event); 119 121 120 Engine.PostMessage(this.entity, MT_AttackDetected, { "player": cmpPlayer.GetPlayerID(), "event": event });122 Engine.PostMessage(this.entity, MT_AttackDetected, { "player": playerID, "event": event }); 121 123 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({ 122 124 "type": "attack", 123 125 "target": target, 124 "players": [ cmpPlayer.GetPlayerID()],125 "attacker": cmpAttackerOwnership.GetOwner(),126 "players": [playerID], 127 "attacker": attackerOwner, 126 128 "targetIsDomesticAnimal": targetIsDomesticAnimal 127 129 }); 128 130 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 /** 9 * Gives the position of the given entity, taking the lateness into account. 10 * @param {number} ent - entity id of the entity we are finding the location for. 11 * @param {number} lateness - the time passed since the expected time to fire the function. 12 * @return {Vector3D} - the location of the entity. 13 */ 14 Damage.prototype.InterpolatedLocation = function(ent, lateness) 15 { 16 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 17 let turnLength = cmpTimer.GetLatestTurnLength() / 1000; 18 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); 19 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly 20 return undefined; 21 let curPos = cmpTargetPosition.GetPosition(); 22 let prevPos = cmpTargetPosition.GetPreviousPosition(); 23 lateness /= 1000; 24 return new Vector3D( 25 (curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength, 26 0, 27 (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength); 28 }; 29 30 /** 31 * Test if a point is inside of an entity's footprint. 32 * @param {int} ent - id of the entity we are checking with. 33 * @param {Vector3D} point - the point we are checking with. 34 * @param {int} lateness - the time passed since the expected time to fire the function. 35 * @return {boolean} - true if the point is inside of the entity's footprint. 36 */ 37 Damage.prototype.TestCollision = function(ent, point, lateness) 38 { 39 let targetPosition = this.InterpolatedLocation(ent, lateness); 40 if (!targetPosition) 41 return false; 42 43 let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint); 44 if (!cmpFootprint) 45 return false; 46 47 let targetShape = cmpFootprint.GetShape(); 48 49 if (!targetShape || !targetPosition) 50 return false; 51 52 if (targetShape.type === 'circle') 53 { 54 // Use VectorDistanceSquared and square targetShape.radius to avoid square roots. 55 return targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius); 56 } 57 else 58 { 59 let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y; 60 61 let distance = Vector3D.sub(point, targetPosition); 62 distance = Vector2D.from3D(d).rotate(-angle); 63 64 return distance.x < Math.abs(targetShape.width/2) && distance.y < Math.abs(targetShape.depth/2); 65 } 66 }; 67 68 /** 69 * Handles hit logic after the projectile travel time has passed. 70 * @param {Object} data - the data sent by the caller. 71 * @param {number} data.attacker - the entity id of the attacker. 72 * @param {number} data.target - the entity id of the target. 73 * @param {Vector2D} data.origin - the origin of the projectile hit. 74 * @param {Object} data.strengths - data of the form { 'hack': <float>, 'pierce': <float>, 'crush': <float> }. 75 * @param {string} data.type - the type of damage. 76 * @param {number} data.attackerOwner - the player id of the owner of the attacker. 77 * @param {boolean} data.isSplash - a flag indicating if it's splash damage. 78 * @param {Vector3D} data.position - the expected position of the target. 79 * @param {number} data.projectileId - the id of the projectile. 80 * @param {Vector3D} data.direction - The unit vector defining the direction 81 * ***When splash damage*** 82 * @param {boolean} data.friendlyFire - a flag indicating if allied entities are also damaged. 83 * @param {number} data.radius - the radius of the splash damage. 84 * @param {string} data.shape - the shape of the splash range. 85 */ 86 Damage.prototype.MissileHit = function(data, lateness) 87 { 88 let targetPosition = this.InterpolatedLocation(data.target, lateness); 89 if (!targetPosition || !data.position) 90 return; 91 92 // Do this first in case the direct hit kills the target 93 if (data.isSplash) 94 { 95 let playersToDamage; 96 97 if (!data.friendlyFire) 98 { 99 let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner); 100 playersToDamage = cmpPlayer.GetEnemies(); 101 } 102 103 this.CauseSplashDamage({ 104 "attacker": data.attacker, 105 "origin": Vector2D.from3D(data.position), 106 "radius": data.radius, 107 "shape": data.shape, 108 "strengths": data.strengths, 109 "direction": data.direction, 110 "playersToDamage": playersToDamage, 111 "type": data.type, 112 "attackerOwner": data.attackerOwner 113 }); 114 } 115 116 if (this.TestCollision(data.target, data.position, lateness)) 117 { 118 this.CauseDamage(data); 119 120 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 121 cmpProjectileManager.RemoveProjectile(data.projectileId); 122 } 123 else 124 { 125 // If we didn't hit the main target look for nearby units 126 let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner); 127 let ents = this.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies()); 128 129 for (let ent of ents) 130 { 131 if (!this.TestCollision(ent, data.position, lateness)) 132 continue; 133 134 this.CauseDamage({ 135 "strengths": data.strengths, 136 "target": ent, 137 "attacker": data.attacker, 138 "multiplier": data.multiplier, 139 "type": data.type, 140 "attackerOwner": data.attackerOwner 141 }); 142 143 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 144 cmpProjectileManager.RemoveProjectile(data.projectileId); 145 } 146 } 147 }; 148 149 /** 150 * Damages units around a given origin. 151 * @param {Object} data - the data sent by the caller. 152 * @param {number} data.attacker - the entity id of the attacker. 153 * @param {Vector2D} data.origin - the origin of the projectile hit. 154 * @param {number} data.radius - the radius of the splash damage. 155 * @param {string} data.shape - the shape of the radius. 156 * @param {Object} data.strengths - data of the form { 'hack': <float>, 'pierce': <float>, 'crush': <float> }. 157 * @param {string} data.type - the type of damage. 158 * @param {number} data.attackerOwner - the player id of the attacker. 159 * @param {Vector3D} data.direction - the unit vector defining the direction. 160 * @param {number[]} [data.playersToDamage] - the array of player id's to damage. 161 */ 162 Damage.prototype.CauseSplashDamage = function(data) 163 { 164 // Get nearby entities and define variables 165 let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage); 166 let damageMultiplier = 1; 167 // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin. 168 for (let ent of nearEnts) 169 { 170 let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D(); 171 if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction 172 damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius); 173 else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) 174 { 175 // Get position of entity relative to splash origin. 176 let relativePos = entityPosition.sub(data.origin); 177 178 // The width of linear splash is one fifth of the normal splash radius. 179 let width = data.radius/5; 180 181 // Effectivly rotate the axis to align with the missile direction. 182 let parallelDist = relativePos.dot(data.direction); // z axis 183 let perpDist = Math.abs(relativePos.cross(data.direction)); // y axis 184 185 // Check that the unit is within the distance at which it will get damaged. 186 if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions 187 damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius) * 188 (width * width - perpDist * perpDist) / (width * width); 189 else 190 damageMultiplier = 0; 191 } 192 else // In case someone calls this function with an invalid shape. 193 { 194 warn("The " + data.shape + " splash damage shape is not implemented!"); 195 } 196 // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... 197 this.CauseDamage({ 198 "strengths":data.strengths, 199 "target":ent, 200 "attacker":data.attacker, 201 "multiplier":damageMultiplier, 202 "type":data.type + ".Splash", 203 "attackerOwner": data.attackerOwner 204 }); 205 } 206 }; 207 208 /** 209 * Causes damage on a given unit. 210 * @param {Object} data - the data passed by the caller. 211 * @param {Object} data.strengths - data in the form of { 'hack': <float>, 'pierce': <float>, 'crush': <float> }. 212 * @param {number} data.target - the entity id of the target. 213 * @param {number} data.attacker - the entity id og the attacker. 214 * @param {float} data.multiplier - the damage multiplier (between 0 and 1). 215 * @param {string} data.type - the type of damage. 216 * @param {number} data.attackerOwner - the player id of the attacker. 217 */ 218 Damage.prototype.CauseDamage = function(data) 219 { 220 let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); 221 let cmpHealth = Engine.QueryInterface(data.target, IID_Health); 222 if (!cmpDamageReceiver || !cmpHealth) 223 return; 224 225 let targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier); 226 227 let cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion); 228 let cmpLoot = Engine.QueryInterface(data.target, IID_Loot); 229 if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0) 230 cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints()); 231 232 if (targetState.killed) 233 this.TargetKilled(data.attacker, data.target, data.attackerOwner); 234 Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner }); 235 236 PlaySound("attack_impact", data.attacker); 237 }; 238 239 /** 240 * Gets entities near a give point for given players. 241 * @param {Vector2D} origin - the point to check around. 242 * @param {number} radius - the radius around the point to check. 243 * @param {number[]} [players] - the players of which we need to check entities. If players is not included, entities from all players are used. 244 * @return {number[]} - the id's of the entities in range of the given point. 245 */ 246 Damage.prototype.EntitiesNearPoint = function(origin, radius, players) 247 { 248 // If there is insufficient data return an empty array. 249 if (!origin || !radius) 250 return []; 251 252 // If the players parameter is not specified use all players. 253 if (!players) 254 { 255 let playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); 256 players = playerEntities.map((ent) => Engine.QueryInterface(ent, IID_Player).GetPlayerID()); 257 } 258 259 // Call RangeManager with dummy entity and return the result. 260 let rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 261 let rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver); 262 return rangeQuery; 263 }; 264 265 /** 266 * Called when a unit kills something (another unit, building, animal etc) 267 * @param {number} killerEntity - the entity id of the killer. 268 * @param {number} targetEntity - the entity id of the target. 269 * @param {number} attackerOwner - the player id of the attacker. 270 */ 271 Damage.prototype.TargetKilled = function(killerEntity, targetEntity, attackerOwner) 272 { 273 // Add to killer statistics. 274 let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(attackerOwner, IID_StatisticsTracker); 275 if (cmpKillerPlayerStatisticsTracker) 276 cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity); 277 // Add to loser statistics. 278 let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker); 279 if (cmpTargetPlayerStatisticsTracker) 280 cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity); 281 282 // If killer can collect loot, let's try to collect it. 283 let cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter); 284 if (cmpLooter) 285 cmpLooter.Collect(targetEntity); 286 }; 287 288 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.LoadComponentScript("interfaces/Damage.js"); 4 Engine.LoadComponentScript("interfaces/Timer.js"); 5 Engine.LoadComponentScript("interfaces/Health.js"); 6 Engine.LoadComponentScript("interfaces/DamageReceiver.js"); 7 Engine.LoadComponentScript("interfaces/Promotion.js"); 8 Engine.LoadComponentScript("interfaces/Loot.js"); 9 Engine.LoadComponentScript("interfaces/Sound.js"); 10 Engine.LoadComponentScript("interfaces/AttackDetection.js"); 11 Engine.LoadComponentScript("interfaces/Player.js"); 12 Engine.LoadComponentScript("interfaces/Attack.js"); 13 Engine.LoadComponentScript("Damage.js"); 14 Engine.LoadComponentScript("Timer.js"); 15 Engine.LoadComponentScript("PlayerManager.js") 16 17 let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage"); 18 let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer"); 19 cmpTimer.OnUpdate({ turnLength: 1 }); 20 let attacker = 11; 21 let atkPlayerEntity = 1; 22 let attackerOwner = 6; 23 let damage = 5; 24 let target = 21; 25 let targetOwner = 7; 26 let targetPos = new Vector3D(3, 0, 3); 27 28 let damageTaken = false; 29 30 let data = { 31 "attacker": attacker, 32 "target": target, 33 "type": "Melee", 34 "strengths": { "hack": 0, "pierce": 0, "crush": damage }, 35 "multiplier": 1.0, 36 "attackerOwner": attackerOwner, 37 "position": targetPos, 38 "isSplash": false, 39 "projectileId": 9 40 }; 41 42 AddMock(atkPlayerEntity, IID_Player, { 43 GetEnemies: () => [targetOwner], 44 }); 45 46 AddMock(SYSTEM_ENTITY, IID_PlayerManager, { 47 GetPlayerByID: (id) => atkPlayerEntity, 48 GetAllPlayerEntities: () => [attackerOwner, targetOwner], 49 }); 50 51 AddMock(SYSTEM_ENTITY, IID_ProjectileManager, { 52 RemoveProjectile: () => {}, 53 }); 54 55 AddMock(target, IID_Position, { 56 GetPosition: () => targetPos, 57 GetPreviousPosition: () => targetPos, 58 GetPosition2D: () => new Vector2D(3, 3), 59 }); 60 61 AddMock(target, IID_Health, {}); 62 63 AddMock(target, IID_DamageReceiver, { 64 TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; }, 65 }); 66 67 Engine.PostMessage = function(ent, iid, message) 68 { 69 TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": data.type, "damage": damage, "attackerOwner": attackerOwner }, message); 70 }; 71 72 AddMock(target, IID_Footprint, { 73 GetShape: () => ({ "type": "circle", "radius": 20 }), 74 }); 75 76 function TestDamage() 77 { 78 cmpTimer.OnUpdate({ turnLength: 1 }); 79 TS_ASSERT(damageTaken); 80 damageTaken = false; 81 } 82 83 cmpDamage.CauseDamage(data); 84 TestDamage(); 85 86 data.type = "Ranged"; 87 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data); 88 TestDamage(); 89 90 data.friendlyFire = false; 91 data.range = 10; 92 data.shape = "Circular"; 93 data.isSplash = true; 94 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data); 95 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