Ticket #3610: DamageAfterDeathOfAtacker_v11.patch

File DamageAfterDeathOfAtacker_v11.patch, 34.2 KB (added by Leander Hemelhof, 8 years ago)

Some minor tweaks.

  • binaries/data/mods/public/gui/credits/texts/programming.json

     
    110110            {"nick": "kingadami", "name": "Adam Winsor"},
    111111            {"nick": "kingbasil", "name": "Giannis Fafalios"},
    112112            {"nick": "lafferjm", "name": "Justin Lafferty"},
     113            {"nick": "LeanderH", "name": "Leander Hemelhof"},
    113114            {"nick": "leper", "name": "Georg Kilzer"},
    114115            {"nick": "LittleDev"},
    115116            {"nick": "livingaftermidnight", "name": "Will Dull"},
  • binaries/data/mods/public/simulation/components/Attack.js

     
    460460 */
    461461Attack.prototype.PerformAttack = function(type, target)
    462462{
     463    let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     464    let attackerOwner = cmpOwnership.GetOwner();
     465   
     466    let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
    463467    // If this is a ranged attack, then launch a projectile
    464468    if (type == "Ranged")
    465469    {
     
    527531        let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
    528532        let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
    529533
    530         let playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
    531534        cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    532         cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget * 1000, {
     535        let data = {
    533536            "type": type,
     537            "attacker": this.entity,
    534538            "target": target,
     539            "strengths": this.GetAttackStrengths(type),
    535540            "position": realTargetPosition,
    536541            "direction": missileDirection,
    537542            "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);
    540555    }
    541556    else if (type == "Capture")
    542557    {
     558        if (attackerOwner == -1)
     559            return;
     560       
    543561        let multiplier = this.GetAttackBonus(type, target);
    544562        let cmpHealth = Engine.QueryInterface(target, IID_Health);
    545563        if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
     
    546564            return;
    547565        multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
    548566
    549         let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    550         if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
    551             return;
    552 
    553         let owner = cmpOwnership.GetOwner();
    554567        let cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
    555         if (!cmpCapturable || !cmpCapturable.CanCapture(owner))
     568        if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner))
    556569            return;
    557570
    558571        let strength = this.GetAttackStrengths("Capture").value * multiplier;
    559         if (cmpCapturable.Reduce(strength, owner))
     572        if (cmpCapturable.Reduce(strength, attackerOwner))
    560573            Engine.PostMessage(target, MT_Attacked, {
    561574                "attacker": this.entity,
    562575                "target": target,
    563576                "type": type,
    564                 "damage": strength
     577                "damage": strength,
     578                "attackerOwner": attackerOwner
    565579            });
    566580    }
    567581    else
    568582    {
    569583        // Melee attack - hurt the target immediately
    570         Damage.CauseDamage({
     584        cmpDamage.CauseDamage({
    571585            "strengths": this.GetAttackStrengths(type),
    572586            "target": target,
    573587            "attacker": this.entity,
    574588            "multiplier": this.GetAttackBonus(type, target),
    575             "type":type
     589            "type": type,
     590            "attackerOwner": attackerOwner
    576591        });
    577592    }
    578593};
    579594
    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 properly
    586         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 footprint
    596 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     else
    615     {
    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 target
    632     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.type
    651         });
    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 target
    660         Damage.CauseDamage(data);
    661 
    662         // Remove the projectile
    663         let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
    664         cmpProjectileManager.RemoveProjectile(data.projectileId);
    665     }
    666     else
    667     {
    668         // If we didn't hit the main target look for nearby units
    669         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.type
    683             });
    684 
    685             // Remove the projectile
    686             let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
    687             cmpProjectileManager.RemoveProjectile(data.projectileId);
    688         }
    689     }
    690 };
    691 
    692595Attack.prototype.OnValueModification = function(msg)
    693596{
    694597    if (msg.component != "Attack")
  • binaries/data/mods/public/simulation/components/AttackDetection.js

     
    5050
    5151    Engine.PostMessage(msg.target, MT_MinimapPing);
    5252
    53     this.AttackAlert(msg.target, msg.attacker);
     53    this.AttackAlert(msg.target, msg.attacker, msg.attackerOwner);
    5454};
    5555
    5656//// External interface ////
    5757
    58 AttackDetection.prototype.AttackAlert = function(target, attacker)
     58AttackDetection.prototype.AttackAlert = function(target, attacker, attackerOwner)
    5959{
    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   
    6262    // Don't register attacks dealt against other players
    63     if (cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID())
     63    if (Engine.QueryInterface(target, IID_Ownership).GetOwner() != playerID)
    6464        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;
    6668    // Don't register attacks dealt by myself
    67     if (cmpAttackerOwnership.GetOwner() == cmpPlayer.GetPlayerID())
     69    if (atkOwner == playerID)
    6870        return;
    69        
     71
    7072    // Since livestock can be attacked/gathered by other players
    7173    // and generally are not so valuable as other units/buildings,
    7274    // we have a lower priority notification for it, which can be
     
    117119    if (!isPriorityIncreased)
    118120        this.AddSuppression(event);
    119121
    120     Engine.PostMessage(this.entity, MT_AttackDetected, { "player": cmpPlayer.GetPlayerID(), "event": event });
     122    Engine.PostMessage(this.entity, MT_AttackDetected, { "player": playerID, "event": event });
    121123    Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
    122124        "type": "attack",
    123125        "target": target,
    124         "players": [cmpPlayer.GetPlayerID()],
    125         "attacker": cmpAttackerOwnership.GetOwner(),
     126        "players": [playerID],
     127        "attacker": atkOwner,
    126128        "targetIsDomesticAnimal": targetIsDomesticAnimal
    127129    });
    128130    PlaySound("attacked", target);
  • binaries/data/mods/public/simulation/components/Damage.js

     
     1function Damage() {}
     2
     3Damage.prototype.Schema =
     4    "<a:component type='system'/><empty/>";
     5   
     6Damage.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 */
     16Damage.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 {int}      ent - id of the entity we are checking with.
     35 * @param {Vector3D} point - the point we are checking with.
     36 * @param {int}      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 */
     39Damage.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': <float>, 'pierce': <float>, 'crush': <float> }.
     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 */
     82Damage.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': <float>, 'pierce': <float>, 'crush': <float> }.
     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 */
     152Damage.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': <float>, 'pierce': <float>, 'crush': <float> }.
     202 * @param {number} data.target - the entity id of the target.
     203 * @param {number} data.attacker - the entity id og the attacker.
     204 * @param {float}  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 */
     208Damage.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 */
     237Damage.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 */
     259Damage.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   
     279Engine.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
     
    11Engine.RegisterInterface("Attack");
    22
    33/**
    4  * Message of the form { "attacker": number, "target": number, "type": string, "damage": number }
    5  * sent from Attack component and by Damage helper to 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.
    66 */
    77Engine.RegisterMessageType("Attacked");
  • binaries/data/mods/public/simulation/components/interfaces/Damage.js

     
     1Engine.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
     
     1Engine.LoadHelperScript("Player.js");
     2Engine.LoadHelperScript("Sound.js");
     3Engine.LoadHelperScript("ValueModification.js");
     4Engine.LoadComponentScript("interfaces/Damage.js");
     5Engine.LoadComponentScript("interfaces/Timer.js");
     6Engine.LoadComponentScript("interfaces/Health.js");
     7Engine.LoadComponentScript("interfaces/DamageReceiver.js");
     8Engine.LoadComponentScript("interfaces/Promotion.js");
     9Engine.LoadComponentScript("interfaces/Loot.js");
     10Engine.LoadComponentScript("interfaces/Sound.js");
     11Engine.LoadComponentScript("interfaces/AttackDetection.js");
     12Engine.LoadComponentScript("interfaces/Player.js");
     13Engine.LoadComponentScript("interfaces/Attack.js");
     14Engine.LoadComponentScript("interfaces/TechnologyManager.js");
     15Engine.LoadComponentScript("interfaces/AuraManager.js");
     16Engine.LoadComponentScript("Damage.js");
     17Engine.LoadComponentScript("Attack.js");
     18Engine.LoadComponentScript("Timer.js");
     19Engine.LoadComponentScript("PlayerManager.js")
     20
     21let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
     22let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
     23cmpTimer.OnUpdate({ turnLength: 1 });
     24let attacker = 11;
     25let atkPlayerEntity = 1;
     26let attackerOwner = 6;
     27let cmpAttack = ConstructComponent(attacker, "Attack", { Ranged: { ProjectileSpeed: 500, Spread: 0.5, MaxRange: 50, MinRange: 0 } } );
     28let damage = 5;
     29let target = 21;
     30let targetOwner = 7;
     31let targetPos = new Vector3D(3, 0, 3);
     32
     33let type = "Melee";
     34let damageTaken = false;
     35
     36cmpAttack.GetAttackStrengths = (type) => ({ "hack": 0, "pierce": 0, "crush": damage });
     37cmpAttack.GetAttackBonus = (type, target) => 1.0;
     38
     39let 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
     51AddMock(atkPlayerEntity, IID_Player, {
     52    GetEnemies: () => [targetOwner],
     53});
     54
     55AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
     56    GetPlayerByID: (id) => atkPlayerEntity,
     57    GetAllPlayerEntities: () => [attackerOwner, targetOwner],
     58});
     59
     60AddMock(SYSTEM_ENTITY, IID_RangeManager, {
     61    ExecuteQueryAroundPos: () => [target],
     62    GetElevationAdaptedRange: (pos, rot, max, bonus, a) => max,
     63});
     64
     65AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
     66    RemoveProjectile: () => {},
     67    LaunchProjectileAtPoint: (ent, pos, speed, gravity) => {},
     68});
     69
     70AddMock(target, IID_Position, {
     71    GetPosition: () => targetPos,
     72    GetPreviousPosition: () => targetPos,
     73    GetPosition2D: () => new Vector2D(3, 3),
     74    IsInWorld: () => true,
     75});
     76
     77AddMock(target, IID_Health, {});
     78 
     79AddMock(target, IID_DamageReceiver, {
     80    TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; },
     81});
     82
     83Engine.PostMessage = function(ent, iid, message)
     84{
     85    TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": type, "damage": damage, "attackerOwner": attackerOwner }, message);
     86};
     87
     88AddMock(target, IID_Footprint, {
     89    GetShape: () => ({ "type": "circle", "radius": 20 }),
     90});
     91
     92AddMock(attacker, IID_Ownership, {
     93    GetOwner: () => attackerOwner,
     94});
     95
     96AddMock(attacker, IID_Position, {
     97    GetPosition: () => new Vector3D(2, 0, 3),
     98    GetRotation: () => new Vector3D(1, 2, 3),
     99    IsInWorld: () => true,
     100});
     101
     102function TestDamage()
     103{
     104    cmpTimer.OnUpdate({ turnLength: 1 });
     105    TS_ASSERT(damageTaken);
     106    damageTaken = false;
     107}
     108
     109cmpDamage.CauseDamage(data);
     110TestDamage();
     111
     112type = data.type = "Ranged";
     113cmpDamage.CauseDamage(data);
     114TestDamage();
     115
     116data.friendlyFire = false;
     117data.range = 10;
     118data.shape = "Circular";
     119data.isSplash = true;
     120cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data);
     121TestDamage();
     122
     123cmpAttack.PerformAttack("Ranged", target);
     124Engine.DestroyEntity(attacker);
     125TestDamage();
  • 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 variables
    19     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 direction
    26         {
    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 axis
    40             var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis
    41 
    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 directions
    44                 damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius)
    45                                  * (width * width - perpDist * perpDist) / (width * width);
    46             else
    47                 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 unit
    60  * 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 target
    75     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 cleanup
    83     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 sounds
    90     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