Ticket #1496: Damagev2.diff

File Damagev2.diff, 17.6 KB (added by Josh, 11 years ago)

Diff of all changes to hopefully fix the last problem (including test functions)

  • 0ad/binaries/data/mods/public/simulation/components/UnitAI.js

     
    34723472 */
    34733473UnitAI.prototype.RespondToTargetedEntities = function(ents)
    34743474{
     3475    //warn("Respond to Target: "+ents[0]);
    34753476    if (!ents.length)
    34763477        return false;
    34773478
  • 0ad/binaries/data/mods/public/simulation/components/Attack.js

     
    486486    else
    487487    {
    488488        // Melee attack - hurt the target immediately
    489         this.CauseDamage({"type": type, "target": target});
     489        var cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
     490        cmpDamage.CauseDamage({"strengths":this.GetAttackStrengths(type), "target":target, "attacker":this.entity, "multiplier":this.GetAttackBonus(type, target), "type":type});
    490491    }
    491492    // TODO: charge attacks (need to design how they work)
    492493};
    493494
    494 /**
    495  * Called when some units kills something (another unit, building, animal etc)
    496  */
    497 Attack.prototype.TargetKilled = function(killerEntity, targetEntity)
    498 {
    499     var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
    500     if (cmpKillerPlayerStatisticsTracker) cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
    501     var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
    502     if (cmpTargetPlayerStatisticsTracker) cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
    503 
    504     // if unit can collect loot, lets try to collect it
    505     var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
    506     if (cmpLooter)
    507     {
    508         cmpLooter.Collect(targetEntity);
    509     }
    510 };
    511 
    512495Attack.prototype.InterpolatedLocation = function(ent, lateness)
    513496{
    514497    var cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
    (this hunk was shorter than expected)  
    530513{
    531514    return (p1.x * p2.x + p1.z * p2.z);
    532515};
    533 
    534 Attack.prototype.VectorCross = function(p1, p2)
    535 {
    536     return (p1.x * p2.z - p1.z * p2.x);
    537 };
    538 
    539 Attack.prototype.VectorLength = function(p)
    540 {
    541     return Math.sqrt(p.x*p.x + p.z*p.z);
    542 };
    543516
    544517// Tests whether it point is inside of ent's footprint
    545518Attack.prototype.testCollision = function(ent, point, lateness)
     
    578556    var targetPosition = this.InterpolatedLocation(data.target, lateness);
    579557    if (!targetPosition)
    580558        return;
    581    
     559    var cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
    582560    if (this.template.Ranged.Splash) // splash damage, do this first in case the direct hit kills the target
    583561    {
    584562        var friendlyFire = this.template.Ranged.Splash.FriendlyFire;
    585563        var splashRadius = this.template.Ranged.Splash.Range;
    586564        var splashShape = this.template.Ranged.Splash.Shape;
    587        
    588         var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2 + splashRadius, friendlyFire);
    589         ents.push(data.target); // Add the original unit to the list of splash damage targets
    590        
    591         for (var i = 0; i < ents.length; i++)
    592         {
    593             var entityPosition = this.InterpolatedLocation(ents[i], lateness);
    594             var radius = this.VectorDistance(data.position, entityPosition);
    595            
    596             if (radius < splashRadius)
    597             {
    598                 var multiplier = 1;
    599                 if (splashShape == "Circular") // quadratic falloff
    600                 {
    601                     multiplier *= 1 - ((radius * radius) / (splashRadius * splashRadius));
    602                 }
    603                 else if (splashShape == "Linear")
    604                 {
    605                     // position of entity relative to where the missile hit
    606                     var relPos = {"x": entityPosition.x - data.position.x, "z": entityPosition.z - data.position.z};
    607                    
    608                     var splashWidth = splashRadius / 5;
    609                     var parallelDist = this.VectorDot(relPos, data.direction);
    610                     var perpDist = Math.abs(this.VectorCross(relPos, data.direction));
    611                    
    612                     // Check that the unit is within the distance splashWidth of the line starting at the missile's
    613                     // landing point which extends in the direction of the missile for length splashRadius.
    614                     if (parallelDist > -splashWidth && perpDist < splashWidth)
    615                     {
    616                         // Use a quadratic falloff in both directions
    617                         multiplier = (splashRadius*splashRadius - parallelDist*parallelDist) / (splashRadius*splashRadius)
    618                                      * (splashWidth*splashWidth - perpDist*perpDist) / (splashWidth*splashWidth);
    619                     }
    620                     else
    621                     {
    622                         multiplier = 0;
    623                     }
    624                 }
    625                 var newData = {"type": data.type + ".Splash", "target": ents[i], "damageMultiplier": multiplier};
    626                 this.CauseDamage(newData);
    627             }
    628         }
     565        cmpDamage.CauseSplashDamage({"attacker":this.entity, "origin":data.position, "radius":splashRadius, "shape":splashShape, "strengths":this.GetAttackStrengths(data.type), "direction":data.direction, "playersToDamage":undefined});
    629566    }
    630567   
    631568    if (this.testCollision(data.target, data.position, lateness))
    632569    {
     570        data.attacker=this.entity
     571        data.multiplier=this.GetAttackBonus(data.type, data.target)
     572        data.strengths=this.GetAttackStrengths(data.type)
    633573        // Hit the primary target
    634         this.CauseDamage(data);
     574        cmpDamage.CauseDamage(data);
    635575       
    636576        // Remove the projectile
    637577        var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
     
    640580    else
    641581    {
    642582        // If we didn't hit the main target look for nearby units
    643         var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2);
     583        var ents = cmpDamage.EntitiesNearPoint(data.position, this.VectorDistance(data.position, targetPosition) * 2);
    644584       
    645585        for (var i = 0; i < ents.length; i++)
    646586        {
    647587            if (this.testCollision(ents[i], data.position, lateness))
    648588            {
    649                 var newData = {"type": data.type, "target": ents[i]};
    650                 this.CauseDamage(newData);
     589                var newData = {"strengths":this.GetAttackStrengths(data.type), "target":ents[i], "attacker":this.entity, "multiplier":this.GetAttackBonus(data.type, ents[i]), "type":data.type};
    651590               
     591                cmpDamage.CauseDamage(newData);
     592               
    652593                // Remove the projectile
    653594                var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
    654595                cmpProjectileManager.RemoveProjectile(data.projectileId);
     
    657598    }
    658599};
    659600
    660 Attack.prototype.GetNearbyEntities = function(startEnt, range, friendlyFire)
    661 {
    662     var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
    663     var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    664     var owner = cmpOwnership.GetOwner();
    665     var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player);
    666     var numPlayers = cmpPlayerManager.GetNumPlayers();
    667     var players = [];
    668    
    669     for (var i = 1; i < numPlayers; ++i)
    670     {   
    671         // Only target enemies unless friendly fire is on
    672         if (cmpPlayer.IsEnemy(i) || friendlyFire)
    673             players.push(i);
    674     }
    675    
    676     var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    677     return rangeManager.ExecuteQuery(startEnt, 0, range, players, IID_DamageReceiver);
    678 }
    679 
    680 /**
    681  * Inflict damage on the target
    682  */
    683 Attack.prototype.CauseDamage = function(data)
    684 {
    685     var strengths = this.GetAttackStrengths(data.type);
    686    
    687     var damageMultiplier = this.GetAttackBonus(data.type, data.target);
    688     if (data.damageMultiplier !== undefined)
    689         damageMultiplier *= data.damageMultiplier;
    690    
    691     var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
    692     if (!cmpDamageReceiver)
    693         return;
    694     var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * damageMultiplier, strengths.pierce * damageMultiplier, strengths.crush * damageMultiplier);
    695     // if target killed pick up loot and credit experience
    696     if (targetState.killed == true)
    697     {
    698         this.TargetKilled(this.entity, data.target);
    699     }
    700 
    701     Engine.PostMessage(data.target, MT_Attacked,
    702         { "attacker": this.entity, "target": data.target, "type": data.type, "damage": -targetState.change });
    703 
    704     PlaySound("attack_impact", this.entity);
    705 };
    706 
    707601Attack.prototype.OnUpdate = function(msg)
    708602{
    709603    this.turnLength = msg.turnLength;
  • 0ad/binaries/data/mods/public/simulation/components/PlayerManager.js

     
    4646    this.playerEntities = [];
    4747};
    4848
     49PlayerManager.prototype.GetAllPlayers = function()
     50{
     51    return this.playerEntities;
     52};
    4953Engine.RegisterComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);
  • 0ad/binaries/data/mods/public/simulation/components/interfaces/Damage.js

     
     1Engine.RegisterInterface("Damage");
  • 0ad/binaries/data/mods/public/simulation/components/Damage.js

     
     1function Damage() {}
     2
     3Damage.prototype.Schema = "<a:component type='system'/><empty/>";
     4
     5Damage.prototype.Init = function()
     6{
     7    // Create dummy entity for EntitiesNearPoint
     8    this.dummyTargetEntity = Engine.AddEntity('special/dummy');
     9}
     10
     11/****************************************
     12 * Damages units around a given origin.
     13 * data.attacker = <entity id>
     14 * data.origin = {'x':<int>, 'z':<int>}
     15 * data.radius = <int>
     16 * data.shape = <string>
     17 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
     18 * ***Optional Variables***
     19 * data.direction = <unit vector>
     20 * data.playersToDamage = <array of player ids>
     21 */
     22Damage.prototype.CauseSplashDamage = function(data)
     23{
     24    warn("Splash mania! Pos: "+data.origin.x+"x, "+data.origin.z+"z + Radius: "+data.radius)
     25    // Get nearby entities and define variables
     26    var nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
     27    var damageMultiplier = 1;
     28    // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
     29    for each (var entity in nearEnts)
     30    {
     31        var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition();
     32        if(data.shape=='Circular') // circular effect with quadratic falloff in every direction
     33        {
     34            var squaredDistanceFromOrigin = this.VectorDistanceSquared(data.origin, entityPosition);
     35            damageMultiplier == 1 - ((squaredDistanceFromOrigin) / (data.radius * data.radius));
     36        }
     37        else if(data.shape=='Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
     38        {
     39            // Get position of entity relative to splash origin.
     40            var relativePos = {"x": entityPosition.x - data.origin.x, "z": entityPosition.z - data.origin.z};
     41           
     42            // The width of linear splash is one fifth of the normal splash radius.
     43            var width = data.radius/5;
     44           
     45            // Effectivly rotate the axis to align with the missile direction.
     46            var parallelDist = this.VectorDot(relativePos, data.direction);//x axis
     47            var perpDist = Math.abs(this.VectorCross(relativePos, data.direction));//y axis
     48           
     49            // Check that the unit is within the distance at which it will get damaged.
     50            if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions
     51                multiplier = (data.radius*data.radius - parallelDist*parallelDist) / (data.radius*data.radius)
     52                                     * (width*width - perpDist*perpDist) / (width*width);
     53            else
     54                multiplier = 0;
     55        }
     56        else // In case someone calls this function with an invalid shape.
     57        {
     58            warn("The "+data.shape+" splash damage shape is not implemented!");
     59        }
     60        // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
     61        this.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":"Splash"})
     62    }
     63};
     64
     65/****************************************
     66 * Causes damage on a given unit
     67 * data.strengths = {'hack':<float>, 'pierce':<float>, 'crush':<float>}
     68 * data.target = <entity id>
     69 * data.attacker = <entity id>
     70 * data.multiplier = <float between 1 and 0>
     71 * data.type = <string>
     72 */
     73Damage.prototype.CauseDamage = function(data)
     74{
     75    // Check the target can be damaged otherwise don't do anything.
     76    var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
     77    if (!cmpDamageReceiver)
     78        return;
     79
     80    // Damage the target
     81    var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier);
     82
     83    // If the target was killed run some cleanup
     84    if (targetState.killed == true)
     85        this.TargetKilled(data.attacker, data.target);
     86
     87    // Post the network command (make it work in multiplayer)
     88    Engine.PostMessage(data.target, MT_Attacked,{ "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change });
     89
     90    // Play attacking sounds
     91    PlaySound("attack_impact", data.attacker);
     92};
     93
     94/****************************************
     95 * Gets entities near a give point for given players.
     96 * origin = {'x':<int>, 'z':<int>}
     97 * radius = <int>
     98 * players = <array>
     99 * If players is not included, entities from all players are used.
     100 */
     101Damage.prototype.EntitiesNearPoint = function(origin, radius, players)
     102{
     103    // If there is insufficient data return an empty array.
     104    if(!origin || !radius) 
     105        return [];
     106
     107    // Move the dummy entity to the origin of the query.
     108    var cmpDummyPosition = Engine.QueryInterface(this.dummyTargetEntity, IID_Position);
     109    cmpDummyPosition.JumpTo(origin.x, origin.z);
     110   
     111    // If the players parameter is not specified use all players.
     112    if(!players)
     113        players = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers();
     114
     115    // Call RangeManager with dummy entity and return the result.
     116    var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     117    var rangeQuery = rangeManager.ExecuteQuery(this.dummyTargetEntity, 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 */
     126Damage.prototype.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, lets try to collect it.
     138    var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
     139    if (cmpLooter)
     140        cmpLooter.Collect(targetEntity);
     141};
     142
     143// Gets the straight line distance between p1 and p2
     144Damage.prototype.VectorDistanceSquared = function(p1, p2)
     145{
     146    return ((p1.x - p2.x)*(p1.x - p2.x) + (p1.z - p2.z)*(p1.z - p2.z));
     147};
     148
     149// Gets the dot product of two vectors.
     150Damage.prototype.VectorDot = function(p1, p2)
     151{
     152    return (p1.x * p2.x + p1.z * p2.z);
     153};
     154
     155// Gets the 2D interpreted version of the cross product of two vectors.
     156Damage.prototype.VectorCross = function(p1, p2)
     157{
     158    return (p1.x * p2.z - p1.z * p2.x);
     159};
     160
     161Engine.RegisterComponentType(IID_Damage, "Damage", Damage);
  • 0ad/binaries/data/mods/public/simulation/components/GuiInterface.js

     
    17591759    "SetObstructionDebugOverlay": 1,
    17601760    "SetMotionDebugOverlay": 1,
    17611761    "SetRangeDebugOverlay": 1,
     1762    "TestSD": 1,
    17621763};
    17631764
     1765GuiInterface.prototype.TestSD = function(pl, d)
     1766{
     1767    var dm = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
     1768    if (!dm) warn("IID_Damage is returning " + dm);
     1769    else dm.CauseSplashDamage({"origin":{"x":d.x, "z":d.z}, "shape":"circular", "radius":d.r, "strengths":{"hack":20.0, "pierce":20.0, "crush":20.0}, "attacker":SYSTEM_ENTITY});
     1770    return
     1771};
     1772
    17641773GuiInterface.prototype.ScriptCall = function(player, name, args)
    17651774{
    17661775    if (exposedFunctions[name])
  • 0ad/binaries/data/mods/public/gui/session/input.js

     
    117117                "angle": placementSupport.angle,
    118118                "actorSeed": placementSupport.actorSeed
    119119            });
    120 
     120            //warn(placementSupport.position.x + ", " + placementSupport.position.z);
    121121            // Show placement info tooltip if invalid position
    122122            placementSupport.tooltipError = !result.success;
    123123            placementSupport.tooltipMessage = result.success ? "" : result.message;
  • 0ad/source/simulation2/Simulation2.cpp

     
    127127            LOAD_SCRIPTED_COMPONENT("PlayerManager");
    128128            LOAD_SCRIPTED_COMPONENT("TechnologyTemplateManager");
    129129            LOAD_SCRIPTED_COMPONENT("Timer");
     130            LOAD_SCRIPTED_COMPONENT("Damage");
    130131
    131132#undef LOAD_SCRIPTED_COMPONENT