Ticket #1496: Damagev3.diff
File Damagev3.diff, 14.1 KB (added by , 11 years ago) |
---|
-
0ad/binaries/data/mods/public/simulation/helpers/Damage.js
1 //Create global Damage object. 2 var Damage = new Object() 3 4 /**************************************** 5 * Damages units around a given origin. 6 * data.attacker = <entity id> 7 * data.origin = {'x':<int>, 'z':<int>} 8 * data.radius = <int> 9 * data.shape = <string> 10 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>} 11 * ***Optional Variables*** 12 * data.direction = <unit vector> 13 * data.playersToDamage = <array of player ids> 14 */ 15 Damage.CauseSplashDamage = function(data) 16 { 17 // Get nearby entities and define variables 18 var nearEnts = Damage.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage); 19 var damageMultiplier = 1; 20 // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin. 21 for each (var entity in nearEnts) 22 { 23 var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition(); 24 if(data.shape=='Circular') // circular effect with quadratic falloff in every direction 25 { 26 var squaredDistanceFromOrigin = Damage.VectorDistanceSquared(data.origin, entityPosition); 27 damageMultiplier == 1 - ((squaredDistanceFromOrigin) / (data.radius * data.radius)); 28 } 29 else if(data.shape=='Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) 30 { 31 // Get position of entity relative to splash origin. 32 var relativePos = {"x": entityPosition.x - data.origin.x, "z": entityPosition.z - data.origin.z}; 33 34 // The width of linear splash is one fifth of the normal splash radius. 35 var width = data.radius/5; 36 37 // Effectivly rotate the axis to align with the missile direction. 38 var parallelDist = Damage.VectorDot(relativePos, data.direction);//x axis 39 var perpDist = Math.abs(Damage.VectorCross(relativePos, data.direction));//y axis 40 41 // Check that the unit is within the distance at which it will get damaged. 42 if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions 43 multiplier = (data.radius*data.radius - parallelDist*parallelDist) / (data.radius*data.radius) 44 * (width*width - perpDist*perpDist) / (width*width); 45 else 46 multiplier = 0; 47 } 48 else // In case someone calls this function with an invalid shape. 49 { 50 warn("The "+data.shape+" splash damage shape is not implemented!"); 51 } 52 // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... 53 Damage.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":"Splash"}) 54 } 55 }; 56 57 /**************************************** 58 * Causes damage on a given unit 59 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>} 60 * data.target = <entity id> 61 * data.attacker = <entity id> 62 * data.multiplier = <float between 1 and 0> 63 * data.type = <string> 64 */ 65 Damage.CauseDamage = function(data) 66 { 67 // Check the target can be damaged otherwise don't do anything. 68 var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); 69 if (!cmpDamageReceiver) 70 return; 71 72 // Damage the target 73 var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier); 74 75 // If the target was killed run some cleanup 76 if (targetState.killed == true) 77 Damage.TargetKilled(data.attacker, data.target); 78 79 // Post the network command (make it work in multiplayer) 80 Engine.PostMessage(data.target, MT_Attacked,{ "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change }); 81 82 // Play attacking sounds 83 PlaySound("attack_impact", data.attacker); 84 }; 85 86 /**************************************** 87 * Gets entities near a give point for given players. 88 * origin = {'x':<int>, 'z':<int>} 89 * radius = <int> 90 * players = <array> 91 * If players is not included, entities from all players are used. 92 */ 93 Damage.EntitiesNearPoint = function(origin, radius, players) 94 { 95 // If there is insufficient data return an empty array. 96 if(!origin || !radius) 97 return []; 98 //Create the dummy entity used for range calculations if it doesn't exist. 99 if(!Damage.dummyTargetEntity) 100 Damage.dummyTargetEntity = Engine.AddEntity('special/dummy'); 101 // Move the dummy entity to the origin of the query. 102 var cmpDummyPosition = Engine.QueryInterface(Damage.dummyTargetEntity, IID_Position); 103 cmpDummyPosition.JumpTo(origin.x, origin.z); 104 105 // If the players parameter is not specified use all players. 106 if(!players) 107 players = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers(); 108 109 // Call RangeManager with dummy entity and return the result. 110 var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 111 var rangeQuery = rangeManager.ExecuteQuery(Damage.dummyTargetEntity, 0, radius, players, IID_DamageReceiver); 112 return rangeQuery; 113 }; 114 115 /**************************************** 116 * Called when some units kills something (another unit, building, animal etc) 117 * killerEntity = <entity id> 118 * targetEntity = <entity id> 119 */ 120 Damage.TargetKilled = function(killerEntity, targetEntity) 121 { 122 // Add to killer statistics. 123 var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker); 124 if (cmpKillerPlayerStatisticsTracker) 125 cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity); 126 // Add to loser statistics. 127 var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker); 128 if (cmpTargetPlayerStatisticsTracker) 129 cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity); 130 131 // If killer can collect loot, lets try to collect it. 132 var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter); 133 if (cmpLooter) 134 cmpLooter.Collect(targetEntity); 135 }; 136 137 // Gets the straight line distance between p1 and p2 138 Damage.VectorDistanceSquared = function(p1, p2) 139 { 140 return ((p1.x - p2.x)*(p1.x - p2.x) + (p1.z - p2.z)*(p1.z - p2.z)); 141 }; 142 143 // Gets the dot product of two vectors. 144 Damage.VectorDot = function(p1, p2) 145 { 146 return (p1.x * p2.x + p1.z * p2.z); 147 }; 148 149 // Gets the 2D interpreted version of the cross product of two vectors. 150 Damage.VectorCross = function(p1, p2) 151 { 152 return (p1.x * p2.z - p1.z * p2.x); 153 }; 154 155 Engine.RegisterGlobal("Damage", Damage); -
0ad/binaries/data/mods/public/simulation/components/Attack.js
488 488 else 489 489 { 490 490 // Melee attack - hurt the target immediately 491 this.CauseDamage({"type": type, "target": target});491 Damage.CauseDamage({"strengths":this.GetAttackStrengths(type), "target":target, "attacker":this.entity, "multiplier":this.GetAttackBonus(type, target), "type":type}); 492 492 } 493 493 // TODO: charge attacks (need to design how they work) 494 494 }; 495 495 496 /**497 * Called when some units kills something (another unit, building, animal etc)498 */499 Attack.prototype.TargetKilled = function(killerEntity, targetEntity)500 {501 var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);502 if (cmpKillerPlayerStatisticsTracker) cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);503 var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);504 if (cmpTargetPlayerStatisticsTracker) cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);505 506 // if unit can collect loot, lets try to collect it507 var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);508 if (cmpLooter)509 {510 cmpLooter.Collect(targetEntity);511 }512 };513 514 496 Attack.prototype.InterpolatedLocation = function(ent, lateness) 515 497 { 516 498 var cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); … … 533 515 return (p1.x * p2.x + p1.z * p2.z); 534 516 }; 535 517 536 Attack.prototype.VectorCross = function(p1, p2)537 {538 return (p1.x * p2.z - p1.z * p2.x);539 };540 541 518 Attack.prototype.VectorLength = function(p) 542 519 { 543 520 return Math.sqrt(p.x*p.x + p.z*p.z); … … 586 563 var friendlyFire = this.template.Ranged.Splash.FriendlyFire; 587 564 var splashRadius = this.template.Ranged.Splash.Range; 588 565 var splashShape = this.template.Ranged.Splash.Shape; 589 590 var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2 + splashRadius, friendlyFire); 591 ents.push(data.target); // Add the original unit to the list of splash damage targets 592 593 for (var i = 0; i < ents.length; i++) 594 { 595 var entityPosition = this.InterpolatedLocation(ents[i], lateness); 596 var radius = this.VectorDistance(data.position, entityPosition); 597 598 if (radius < splashRadius) 599 { 600 var multiplier = 1; 601 if (splashShape == "Circular") // quadratic falloff 602 { 603 multiplier *= 1 - ((radius * radius) / (splashRadius * splashRadius)); 604 } 605 else if (splashShape == "Linear") 606 { 607 // position of entity relative to where the missile hit 608 var relPos = {"x": entityPosition.x - data.position.x, "z": entityPosition.z - data.position.z}; 609 610 var splashWidth = splashRadius / 5; 611 var parallelDist = this.VectorDot(relPos, data.direction); 612 var perpDist = Math.abs(this.VectorCross(relPos, data.direction)); 613 614 // Check that the unit is within the distance splashWidth of the line starting at the missile's 615 // landing point which extends in the direction of the missile for length splashRadius. 616 if (parallelDist > -splashWidth && perpDist < splashWidth) 617 { 618 // Use a quadratic falloff in both directions 619 multiplier = (splashRadius*splashRadius - parallelDist*parallelDist) / (splashRadius*splashRadius) 620 * (splashWidth*splashWidth - perpDist*perpDist) / (splashWidth*splashWidth); 621 } 622 else 623 { 624 multiplier = 0; 625 } 626 } 627 var newData = {"type": data.type + ".Splash", "target": ents[i], "damageMultiplier": multiplier}; 628 this.CauseDamage(newData); 629 } 630 } 566 Damage.CauseSplashDamage({"attacker":this.entity, "origin":data.position, "radius":splashRadius, "shape":splashShape, "strengths":this.GetAttackStrengths(data.type), "direction":data.direction, "playersToDamage":undefined}); 631 567 } 632 568 633 569 if (this.testCollision(data.target, data.position, lateness)) 634 570 { 571 data.attacker=this.entity 572 data.multiplier=this.GetAttackBonus(data.type, data.target) 573 data.strengths=this.GetAttackStrengths(data.type) 635 574 // Hit the primary target 636 this.CauseDamage(data);575 Damage.CauseDamage(data); 637 576 638 577 // Remove the projectile 639 578 var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); … … 642 581 else 643 582 { 644 583 // If we didn't hit the main target look for nearby units 645 var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2);584 var ents = Damage.EntitiesNearPoint(data.position, this.VectorDistance(data.position, targetPosition) * 2); 646 585 647 586 for (var i = 0; i < ents.length; i++) 648 587 { 649 588 if (this.testCollision(ents[i], data.position, lateness)) 650 589 { 651 var newData = {" type": data.type, "target": ents[i]};652 this.CauseDamage(newData);590 var newData = {"strengths":this.GetAttackStrengths(data.type), "target":ents[i], "attacker":this.entity, "multiplier":this.GetAttackBonus(data.type, ents[i]), "type":data.type}; 591 Damage.CauseDamage(newData); 653 592 654 593 // Remove the projectile 655 594 var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); … … 659 598 } 660 599 }; 661 600 662 Attack.prototype.GetNearbyEntities = function(startEnt, range, friendlyFire)663 {664 var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);665 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);666 var owner = cmpOwnership.GetOwner();667 var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player);668 var numPlayers = cmpPlayerManager.GetNumPlayers();669 var players = [];670 671 for (var i = 1; i < numPlayers; ++i)672 {673 // Only target enemies unless friendly fire is on674 if (cmpPlayer.IsEnemy(i) || friendlyFire)675 players.push(i);676 }677 678 var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);679 return rangeManager.ExecuteQuery(startEnt, 0, range, players, IID_DamageReceiver);680 }681 682 /**683 * Inflict damage on the target684 */685 Attack.prototype.CauseDamage = function(data)686 {687 var strengths = this.GetAttackStrengths(data.type);688 689 var damageMultiplier = this.GetAttackBonus(data.type, data.target);690 if (data.damageMultiplier !== undefined)691 damageMultiplier *= data.damageMultiplier;692 693 var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);694 if (!cmpDamageReceiver)695 return;696 var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * damageMultiplier, strengths.pierce * damageMultiplier, strengths.crush * damageMultiplier);697 // if target killed pick up loot and credit experience698 if (targetState.killed == true)699 {700 this.TargetKilled(this.entity, data.target);701 }702 703 Engine.PostMessage(data.target, MT_Attacked,704 { "attacker": this.entity, "target": data.target, "type": data.type, "damage": -targetState.change });705 706 PlaySound("attack_impact", this.entity);707 };708 709 601 Attack.prototype.OnUpdate = function(msg) 710 602 { 711 603 this.turnLength = msg.turnLength; -
0ad/binaries/data/mods/public/simulation/components/PlayerManager.js
46 46 this.playerEntities = []; 47 47 }; 48 48 49 PlayerManager.prototype.GetAllPlayers = function() 50 { 51 return this.playerEntities; 52 }; 49 53 Engine.RegisterComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);