Ticket #3610: DamageAfterDeathOfAtacker_v7.patch
File DamageAfterDeathOfAtacker_v7.patch, 20.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 if (!cmpOwnership || cmpOwnership.GetOwner() == -1) 465 return; 466 let attackerOwner = cmpOwnership.GetOwner(); 467 468 let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage); 463 469 // If this is a ranged attack, then launch a projectile 464 470 if (type == "Ranged") 465 471 { … … 527 533 let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); 528 534 let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); 529 535 530 let playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();531 536 cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 532 cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget * 1000,{537 let data = { 533 538 "type": type, 539 "attacker": this.entity, 534 540 "target": target, 541 "strengths": this.GetAttackStrengths(type), 535 542 "position": realTargetPosition, 536 543 "direction": missileDirection, 537 544 "projectileId": id, 538 "playerId":playerId 539 }); 545 "multiplier": this.GetAttackBonus(type, target), 546 "isSplash": false, 547 "attackerOwner": attackerOwner 548 }; 549 if (this.template.Ranged.Splash) 550 { 551 data.friendlyFire = this.template.Ranged.Splash.FriendlyFire; 552 data.radius = +this.template.Ranged.Splash.Range; 553 data.shape = this.template.Ranged.Splash.Shape; 554 data.isSplash = true; 555 } 556 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data); 540 557 } 541 558 else if (type == "Capture") 542 559 { … … 546 563 return; 547 564 multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints()); 548 565 549 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);550 if (!cmpOwnership || cmpOwnership.GetOwner() == -1)551 return;552 553 let owner = cmpOwnership.GetOwner();554 566 let cmpCapturable = Engine.QueryInterface(target, IID_Capturable); 555 if (!cmpCapturable || !cmpCapturable.CanCapture( owner))567 if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner)) 556 568 return; 557 569 558 570 let strength = this.GetAttackStrengths("Capture").value * multiplier; 559 if (cmpCapturable.Reduce(strength, owner))571 if (cmpCapturable.Reduce(strength, attackerOwner)) 560 572 Engine.PostMessage(target, MT_Attacked, { 561 573 "attacker": this.entity, 562 574 "target": target, 563 575 "type": type, 564 "damage": strength 576 "damage": strength, 577 "attackerOwner": attackerOwner 565 578 }); 566 579 } 567 580 else 568 581 { 569 582 // Melee attack - hurt the target immediately 570 Damage.CauseDamage({583 cmpDamage.CauseDamage({ 571 584 "strengths": this.GetAttackStrengths(type), 572 585 "target": target, 573 586 "attacker": this.entity, 574 587 "multiplier": this.GetAttackBonus(type, target), 575 "type":type 588 "type":type, 589 "attackerOwner": attackerOwner 576 590 }); 577 591 } 578 592 }; 579 593 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 594 Attack.prototype.OnValueModification = function(msg) 693 595 { 694 596 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 cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); 61 if (!cmpPlayer) 62 return; 63 let playerID = cmpPlayer.GetPlayerID() 64 let cmpTargetPlayer = QueryOwnerInterface(target); 62 65 // Don't register attacks dealt against other players 63 if ( cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID())66 if (!cmpTargetPlayer || cmpTargetPlayer != cmpPlayer) 64 67 return; 65 var cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); 68 66 69 // Don't register attacks dealt by myself 67 if ( cmpAttackerOwnership.GetOwner() == cmpPlayer.GetPlayerID())70 if (attackerOwner == playerID) 68 71 return; 69 72 70 73 // Since livestock can be attacked/gathered by other players 71 74 // and generally are not so valuable as other units/buildings, 72 75 // we have a lower priority notification for it, which can be … … 117 120 if (!isPriorityIncreased) 118 121 this.AddSuppression(event); 119 122 120 Engine.PostMessage(this.entity, MT_AttackDetected, { "player": cmpPlayer.GetPlayerID(), "event": event });123 Engine.PostMessage(this.entity, MT_AttackDetected, { "player": playerID, "event": event }); 121 124 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({ 122 125 "type": "attack", 123 126 "target": target, 124 "players": [ cmpPlayer.GetPlayerID()],125 "attacker": cmpAttackerOwnership.GetOwner(),127 "players": [playerID], 128 "attacker": attackerOwner, 126 129 "targetIsDomesticAnimal": targetIsDomesticAnimal 127 130 }); 128 131 PlaySound("attacked", target); -
binaries/data/mods/public/simulation/components/interfaces/Attack.js
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/tests/test_Damage.js
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 IsInWorld: () => true, 60 }); 61 62 AddMock(target, IID_Health, {}); 63 64 AddMock(target, IID_DamageReceiver, { 65 TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; }, 66 }); 67 68 Engine.PostMessage = function(ent, iid, message) 69 { 70 TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": data.type, "damage": damage, "attackerOwner": attackerOwner }, message); 71 }; 72 73 AddMock(target, IID_Footprint, { 74 GetShape: () => ({ "type": "circle", "radius": 20 }), 75 }); 76 77 function TestDamage() 78 { 79 cmpTimer.OnUpdate({ turnLength: 1 }); 80 TS_ASSERT(damageTaken); 81 damageTaken = false; 82 } 83 84 cmpDamage.CauseDamage(data); 85 TestDamage(); 86 87 data.type = "Ranged"; 88 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data); 89 TestDamage(); 90 91 data.friendlyFire = false; 92 data.range = 10; 93 data.shape = "Circular"; 94 data.isSplash = true; 95 cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data); 96 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