Ticket #3610: DamageAfterDeathOfAtacker_v13.patch
File DamageAfterDeathOfAtacker_v13.patch, 34.2 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
460 460 */ 461 461 Attack.prototype.PerformAttack = function(type, target) 462 462 { 463 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 464 let attackerOwner = cmpOwnership.GetOwner(); 465 466 let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage); 463 467 // If this is a ranged attack, then launch a projectile 464 468 if (type == "Ranged") 465 469 { … … 527 531 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 528 532 let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); 529 533 530 let playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();531 534 cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 532 cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget * 1000,{535 let data = { 533 536 "type": type, 537 "attacker": this.entity, 534 538 "target": target, 539 "strengths": this.GetAttackStrengths(type), 535 540 "position": realTargetPosition, 536 541 "direction": missileDirection, 537 542 "projectileId": id, 538 "playerId":playerId 539 }); 543 "multiplier": this.GetAttackBonus(type, target), 544 "isSplash": false, 545 "attackerOwner": attackerOwner 546 }; 547 if (this.template.Ranged.Splash) 548 { 549 data.friendlyFire = this.template.Ranged.Splash.FriendlyFire; 550 data.radius = +this.template.Ranged.Splash.Range; 551 data.shape = this.template.Ranged.Splash.Shape; 552 data.isSplash = true; 553 } 554 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data); 540 555 } 541 556 else if (type == "Capture") 542 557 { 558 if (attackerOwner == -1) 559 return; 560 543 561 let multiplier = this.GetAttackBonus(type, target); 544 562 let cmpHealth = Engine.QueryInterface(target, IID_Health); 545 563 if (!cmpHealth || cmpHealth.GetHitpoints() == 0) … … 546 564 return; 547 565 multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints()); 548 566 549 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);550 if (!cmpOwnership || cmpOwnership.GetOwner() == -1)551 return;552 553 let owner = cmpOwnership.GetOwner();554 567 let cmpCapturable = Engine.QueryInterface(target, IID_Capturable); 555 if (!cmpCapturable || !cmpCapturable.CanCapture( owner))568 if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner)) 556 569 return; 557 570 558 571 let strength = this.GetAttackStrengths("Capture").value * multiplier; 559 if (cmpCapturable.Reduce(strength, owner))572 if (cmpCapturable.Reduce(strength, attackerOwner)) 560 573 Engine.PostMessage(target, MT_Attacked, { 561 574 "attacker": this.entity, 562 575 "target": target, 563 576 "type": type, 564 "damage": strength 577 "damage": strength, 578 "attackerOwner": attackerOwner 565 579 }); 566 580 } 567 581 else 568 582 { 569 583 // Melee attack - hurt the target immediately 570 Damage.CauseDamage({584 cmpDamage.CauseDamage({ 571 585 "strengths": this.GetAttackStrengths(type), 572 586 "target": target, 573 587 "attacker": this.entity, 574 588 "multiplier": this.GetAttackBonus(type, target), 575 "type":type 589 "type": type, 590 "attackerOwner": attackerOwner 576 591 }); 577 592 } 578 593 }; 579 594 580 Attack.prototype.InterpolatedLocation = function(ent, lateness)581 {582 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);583 let turnLength = cmpTimer.GetLatestTurnLength()/1000;584 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);585 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly586 return undefined;587 let curPos = cmpTargetPosition.GetPosition();588 let prevPos = cmpTargetPosition.GetPreviousPosition();589 lateness /= 1000;590 return new Vector3D((curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,591 0,592 (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength);593 };594 595 // Tests whether it point is inside of ent's footprint596 Attack.prototype.testCollision = function(ent, point, lateness)597 {598 let targetPosition = this.InterpolatedLocation(ent, lateness);599 if (!targetPosition)600 return false;601 let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);602 if (!cmpFootprint)603 return false;604 let targetShape = cmpFootprint.GetShape();605 606 if (!targetShape || !targetPosition)607 return false;608 609 if (targetShape.type === 'circle')610 {611 // Use VectorDistanceSquared and square targetShape.radius to avoid square roots.612 return (targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius));613 }614 else615 {616 let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;617 618 let d = Vector3D.sub(point, targetPosition);619 d = Vector2D.from3D(d).rotate(-angle);620 621 return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);622 }623 };624 625 Attack.prototype.MissileHit = function(data, lateness)626 {627 let targetPosition = this.InterpolatedLocation(data.target, lateness);628 if (!targetPosition)629 return;630 631 // Do this first in case the direct hit kills the target632 if (this.template.Ranged.Splash)633 {634 let playersToDamage;635 636 if (this.template.Ranged.Splash.FriendlyFire == "false")637 {638 let cmpPlayer = QueryPlayerIDInterface(data.playerId);639 playersToDamage = cmpPlayer.GetEnemies();640 }641 642 Damage.CauseSplashDamage({643 "attacker": this.entity,644 "origin": Vector2D.from3D(data.position),645 "radius": this.template.Ranged.Splash.Range,646 "shape": this.template.Ranged.Splash.Shape,647 "strengths": this.GetAttackStrengths(data.type),648 "direction": data.direction,649 "playersToDamage": playersToDamage,650 "type": data.type651 });652 }653 654 if (this.testCollision(data.target, data.position, lateness))655 {656 data.attacker = this.entity;657 data.multiplier = this.GetAttackBonus(data.type, data.target);658 data.strengths = this.GetAttackStrengths(data.type);659 // Hit the primary target660 Damage.CauseDamage(data);661 662 // Remove the projectile663 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);664 cmpProjectileManager.RemoveProjectile(data.projectileId);665 }666 else667 {668 // If we didn't hit the main target look for nearby units669 let cmpPlayer = QueryPlayerIDInterface(data.playerId);670 let ents = Damage.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());671 672 for (let i = 0; i < ents.length; ++i)673 {674 if (!this.testCollision(ents[i], data.position, lateness))675 continue;676 677 Damage.CauseDamage({678 "strengths": this.GetAttackStrengths(data.type),679 "target": ents[i],680 "attacker": this.entity,681 "multiplier": this.GetAttackBonus(data.type, ents[i]),682 "type": data.type683 });684 685 // Remove the projectile686 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);687 cmpProjectileManager.RemoveProjectile(data.projectileId);688 }689 }690 };691 692 595 Attack.prototype.OnValueModification = function(msg) 693 596 { 694 597 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);61 var cmpTargetOwnership = Engine.QueryInterface(target, IID_Ownership);60 let playerID = Engine.QueryInterface(this.entity, IID_Player).GetPlayerID(); 61 62 62 // Don't register attacks dealt against other players 63 if ( cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID())63 if (Engine.QueryInterface(target, IID_Ownership).GetOwner() != playerID) 64 64 return; 65 var cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); 65 66 let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); 67 let atkOwner = cmpAttackerOwnership && cmpAttackerOwnership.GetOwner() != -1 ? cmpAttackerOwnership.GetOwner() : attackerOwner; 66 68 // Don't register attacks dealt by myself 67 if ( cmpAttackerOwnership.GetOwner() == cmpPlayer.GetPlayerID())69 if (atkOwner == playerID) 68 70 return; 69 71 70 72 // Since livestock can be attacked/gathered by other players 71 73 // and generally are not so valuable as other units/buildings, 72 74 // we have a lower priority notification for it, which can be … … 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": atkOwner, 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 10 /** 11 * Gives the position of the given entity, taking the lateness into account. 12 * @param {number} ent - entity id of the entity we are finding the location for. 13 * @param {number} lateness - the time passed since the expected time to fire the function. 14 * @return {Vector3D} - the location of the entity. 15 */ 16 Damage.prototype.InterpolatedLocation = function(ent, lateness) 17 { 18 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 19 let turnLength = cmpTimer.GetLatestTurnLength() / 1000; 20 let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); 21 if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly 22 return undefined; 23 let curPos = cmpTargetPosition.GetPosition(); 24 let prevPos = cmpTargetPosition.GetPreviousPosition(); 25 lateness /= 1000; 26 return new Vector3D( 27 (curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength, 28 0, 29 (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength); 30 }; 31 32 /** 33 * Test if a point is inside of an entity's footprint. 34 * @param {number} ent - id of the entity we are checking with. 35 * @param {Vector3D} point - the point we are checking with. 36 * @param {number} lateness - the time passed since the expected time to fire the function. 37 * @return {boolean} - true if the point is inside of the entity's footprint. 38 */ 39 Damage.prototype.TestCollision = function(ent, point, lateness) 40 { 41 let targetPosition = this.InterpolatedLocation(ent, lateness); 42 if (!targetPosition) 43 return false; 44 45 let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint); 46 if (!cmpFootprint) 47 return false; 48 49 let targetShape = cmpFootprint.GetShape(); 50 51 if (!targetShape) 52 return false; 53 54 if (targetShape.type === 'circle') 55 // Use VectorDistanceSquared and square targetShape.radius to avoid square roots. 56 return targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius); 57 58 let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y; 59 let distance = Vector2D.from3D(Vector3D.sub(point, targetPosition)).rotate(-angle); 60 61 return distance.x < Math.abs(targetShape.width/2) && distance.y < Math.abs(targetShape.depth/2); 62 }; 63 64 /** 65 * Handles hit logic after the projectile travel time has passed. 66 * @param {Object} data - the data sent by the caller. 67 * @param {number} data.attacker - the entity id of the attacker. 68 * @param {number} data.target - the entity id of the target. 69 * @param {Vector2D} data.origin - the origin of the projectile hit. 70 * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }. 71 * @param {string} data.type - the type of damage. 72 * @param {number} data.attackerOwner - the player id of the owner of the attacker. 73 * @param {boolean} data.isSplash - a flag indicating if it's splash damage. 74 * @param {Vector3D} data.position - the expected position of the target. 75 * @param {number} data.projectileId - the id of the projectile. 76 * @param {Vector3D} data.direction - The unit vector defining the direction 77 * ***When splash damage*** 78 * @param {boolean} data.friendlyFire - a flag indicating if allied entities are also damaged. 79 * @param {number} data.radius - the radius of the splash damage. 80 * @param {string} data.shape - the shape of the splash range. 81 */ 82 Damage.prototype.MissileHit = function(data, lateness) 83 { 84 let targetPosition = this.InterpolatedLocation(data.target, lateness); 85 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 86 cmpProjectileManager.RemoveProjectile(data.projectileId); 87 88 if (!data.position) 89 return; 90 91 // Do this first in case the direct hit kills the target 92 if (data.isSplash) 93 { 94 let playersToDamage = !data.friendlyFire ? QueryPlayerIDInterface(data.attackerOwner).GetEnemies() : null; 95 96 this.CauseSplashDamage({ 97 "attacker": data.attacker, 98 "origin": Vector2D.from3D(data.position), 99 "radius": data.radius, 100 "shape": data.shape, 101 "strengths": data.strengths, 102 "direction": data.direction, 103 "playersToDamage": playersToDamage, 104 "type": data.type, 105 "attackerOwner": data.attackerOwner 106 }); 107 } 108 109 // Deal direct damage if we hit the main target 110 if (this.TestCollision(data.target, data.position, lateness)) 111 { 112 this.CauseDamage(data); 113 return 114 } 115 116 if (!targetPosition) 117 return; 118 119 // If we didn't hit the main target look for nearby units 120 let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner); 121 let ents = this.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies()); 122 123 for (let ent of ents) 124 { 125 if (!this.TestCollision(ent, data.position, lateness)) 126 continue; 127 128 this.CauseDamage({ 129 "strengths": data.strengths, 130 "target": ent, 131 "attacker": data.attacker, 132 "multiplier": data.multiplier, 133 "type": data.type, 134 "attackerOwner": data.attackerOwner 135 }); 136 } 137 }; 138 139 /** 140 * Damages units around a given origin. 141 * @param {Object} data - the data sent by the caller. 142 * @param {number} data.attacker - the entity id of the attacker. 143 * @param {Vector2D} data.origin - the origin of the projectile hit. 144 * @param {number} data.radius - the radius of the splash damage. 145 * @param {string} data.shape - the shape of the radius. 146 * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }. 147 * @param {string} data.type - the type of damage. 148 * @param {number} data.attackerOwner - the player id of the attacker. 149 * @param {Vector3D} data.direction - the unit vector defining the direction. 150 * @param {number[]} [data.playersToDamage] - the array of player id's to damage. 151 */ 152 Damage.prototype.CauseSplashDamage = function(data) 153 { 154 // Get nearby entities and define variables 155 let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage); 156 let damageMultiplier = 1; 157 // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin. 158 for (let ent of nearEnts) 159 { 160 let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D(); 161 if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction 162 damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius); 163 else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) 164 { 165 // Get position of entity relative to splash origin. 166 let relativePos = entityPosition.sub(data.origin); 167 168 // The width of linear splash is one fifth of the normal splash radius. 169 let width = data.radius / 5; 170 171 // Effectivly rotate the axis to align with the missile direction. 172 let parallelDist = relativePos.dot(data.direction); // z axis 173 let perpDist = Math.abs(relativePos.cross(data.direction)); // y axis 174 175 // Check that the unit is within the distance at which it will get damaged. 176 if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions 177 damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius) * 178 (width * width - perpDist * perpDist) / (width * width); 179 else 180 damageMultiplier = 0; 181 } 182 else // In case someone calls this function with an invalid shape. 183 { 184 warn("The " + data.shape + " splash damage shape is not implemented!"); 185 } 186 // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... 187 this.CauseDamage({ 188 "strengths": data.strengths, 189 "target": ent, 190 "attacker": data.attacker, 191 "multiplier": damageMultiplier, 192 "type": data.type + ".Splash", 193 "attackerOwner": data.attackerOwner 194 }); 195 } 196 }; 197 198 /** 199 * Causes damage on a given unit. 200 * @param {Object} data - the data passed by the caller. 201 * @param {Object} data.strengths - data in the form of { 'hack': number, 'pierce': number, 'crush': number }. 202 * @param {number} data.target - the entity id of the target. 203 * @param {number} data.attacker - the entity id og the attacker. 204 * @param {number} data.multiplier - the damage multiplier (between 0 and 1). 205 * @param {string} data.type - the type of damage. 206 * @param {number} data.attackerOwner - the player id of the attacker. 207 */ 208 Damage.prototype.CauseDamage = function(data) 209 { 210 let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); 211 if (!cmpDamageReceiver) 212 return; 213 214 let targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier); 215 216 let cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion); 217 let cmpLoot = Engine.QueryInterface(data.target, IID_Loot); 218 let cmpHealth = Engine.QueryInterface(data.target, IID_Health); 219 if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0) 220 cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints()); 221 222 if (targetState.killed) 223 this.TargetKilled(data.attacker, data.target, data.attackerOwner); 224 225 Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner }); 226 227 PlaySound("attack_impact", data.attacker); 228 }; 229 230 /** 231 * Gets entities near a give point for given players. 232 * @param {Vector2D} origin - the point to check around. 233 * @param {number} radius - the radius around the point to check. 234 * @param {number[]} [players] - the players of which we need to check entities. If players is not included, entities from all players are used. 235 * @return {number[]} - the id's of the entities in range of the given point. 236 */ 237 Damage.prototype.EntitiesNearPoint = function(origin, radius, players) 238 { 239 // If there is insufficient data return an empty array. 240 if (!origin || !radius) 241 return []; 242 243 // If the players parameter is not specified use all players. 244 if (!players) 245 { 246 let playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); 247 players = playerEntities.map((ent) => Engine.QueryInterface(ent, IID_Player).GetPlayerID()); 248 } 249 250 return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver); 251 }; 252 253 /** 254 * Called when a unit kills something (another unit, building, animal etc) 255 * @param {number} attacker - the entity id of the killer. 256 * @param {number} target - the entity id of the target. 257 * @param {number} attackerOwner - the player id of the attacker. 258 */ 259 Damage.prototype.TargetKilled = function(attacker, target, attackerOwner) 260 { 261 let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); 262 let atkOwner = cmpAttackerOwnership && cmpAttackerOwnership.GetOwner() != -1 ? cmpAttackerOwnership.GetOwner() : attackerOwner; 263 264 // Add to killer statistics. 265 let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(atkOwner, IID_StatisticsTracker); 266 if (cmpKillerPlayerStatisticsTracker) 267 cmpKillerPlayerStatisticsTracker.KilledEntity(target); 268 // Add to loser statistics. 269 let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(target, IID_StatisticsTracker); 270 if (cmpTargetPlayerStatisticsTracker) 271 cmpTargetPlayerStatisticsTracker.LostEntity(target); 272 273 // If killer can collect loot, let's try to collect it. 274 let cmpLooter = Engine.QueryInterface(attacker, IID_Looter); 275 if (cmpLooter) 276 cmpLooter.Collect(target); 277 }; 278 279 Engine.RegisterSystemComponentType(IID_Damage, "Damage", Damage); -
binaries/data/mods/public/simulation/components/interfaces/Attack.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 1 Engine.RegisterInterface("Attack"); 2 2 3 3 /** 4 * Message of the form { "attacker": number, "target": number, "type": string, "damage": number }5 * sent from Attack component and by Damage helperto the target entity, each time the target is attacked or damaged.4 * Message of the form { "attacker": number, "target": number, "type": string, "damage": number, "attackerOwner": number } 5 * sent from Attack component and by Damage component to the target entity, each time the target is attacked or damaged. 6 6 */ 7 7 Engine.RegisterMessageType("Attacked"); -
binaries/data/mods/public/simulation/components/interfaces/Damage.js
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/DamageReceiver.js"); 8 Engine.LoadComponentScript("interfaces/Promotion.js"); 9 Engine.LoadComponentScript("interfaces/Loot.js"); 10 Engine.LoadComponentScript("interfaces/Sound.js"); 11 Engine.LoadComponentScript("interfaces/AttackDetection.js"); 12 Engine.LoadComponentScript("interfaces/Player.js"); 13 Engine.LoadComponentScript("interfaces/Attack.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 damage = 5; 29 let target = 21; 30 let targetOwner = 7; 31 let targetPos = new Vector3D(3, 0, 3); 32 33 let type = "Melee"; 34 let damageTaken = false; 35 36 cmpAttack.GetAttackStrengths = (type) => ({ "hack": 0, "pierce": 0, "crush": damage }); 37 cmpAttack.GetAttackBonus = (type, target) => 1.0; 38 39 let data = { 40 "attacker": attacker, 41 "target": target, 42 "type": "Melee", 43 "strengths": { "hack": 0, "pierce": 0, "crush": damage }, 44 "multiplier": 1.0, 45 "attackerOwner": attackerOwner, 46 "position": targetPos, 47 "isSplash": false, 48 "projectileId": 9 49 }; 50 51 AddMock(atkPlayerEntity, IID_Player, { 52 GetEnemies: () => [targetOwner], 53 }); 54 55 AddMock(SYSTEM_ENTITY, IID_PlayerManager, { 56 GetPlayerByID: (id) => atkPlayerEntity, 57 GetAllPlayerEntities: () => [attackerOwner, targetOwner], 58 }); 59 60 AddMock(SYSTEM_ENTITY, IID_RangeManager, { 61 ExecuteQueryAroundPos: () => [target], 62 GetElevationAdaptedRange: (pos, rot, max, bonus, a) => max, 63 }); 64 65 AddMock(SYSTEM_ENTITY, IID_ProjectileManager, { 66 RemoveProjectile: () => {}, 67 LaunchProjectileAtPoint: (ent, pos, speed, gravity) => {}, 68 }); 69 70 AddMock(target, IID_Position, { 71 GetPosition: () => targetPos, 72 GetPreviousPosition: () => targetPos, 73 GetPosition2D: () => new Vector2D(3, 3), 74 IsInWorld: () => true, 75 }); 76 77 AddMock(target, IID_Health, {}); 78 79 AddMock(target, IID_DamageReceiver, { 80 TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; }, 81 }); 82 83 Engine.PostMessage = function(ent, iid, message) 84 { 85 TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": type, "damage": damage, "attackerOwner": attackerOwner }, message); 86 }; 87 88 AddMock(target, IID_Footprint, { 89 GetShape: () => ({ "type": "circle", "radius": 20 }), 90 }); 91 92 AddMock(attacker, IID_Ownership, { 93 GetOwner: () => attackerOwner, 94 }); 95 96 AddMock(attacker, IID_Position, { 97 GetPosition: () => new Vector3D(2, 0, 3), 98 GetRotation: () => new Vector3D(1, 2, 3), 99 IsInWorld: () => true, 100 }); 101 102 function TestDamage() 103 { 104 cmpTimer.OnUpdate({ turnLength: 1 }); 105 TS_ASSERT(damageTaken); 106 damageTaken = false; 107 } 108 109 cmpDamage.CauseDamage(data); 110 TestDamage(); 111 112 type = data.type = "Ranged"; 113 cmpDamage.CauseDamage(data); 114 TestDamage(); 115 116 data.friendlyFire = false; 117 data.range = 10; 118 data.shape = "Circular"; 119 data.isSplash = true; 120 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data); 121 TestDamage(); 122 123 cmpAttack.PerformAttack("Ranged", target); 124 Engine.DestroyEntity(attacker); 125 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