Ticket #1910: 1910-death-damage-2.patch

File 1910-death-damage-2.patch, 9.7 KB (added by Mate-86, 8 years ago)
  • binaries/data

  • binaries/data/mods/public/simulation/components/Attack.js

    Property changes on: binaries/data
    ___________________________________________________________________
    Modified: svn:ignore
    ## -1,3 +1,4 ##
    -*.xmb
     *.db
     *.jbf
    +*.xmb
    +cache
     
    9494            "<Crush>0.0</Crush>" +
    9595            "<MaxRange>4.0</MaxRange>" +
    9696        "</Slaughter>" +
     97        "<Death>" +
     98            "<Shape>Circular</Shape>" +
     99            "<Range>20</Range>" +
     100            "<FriendlyFire>false</FriendlyFire>" +
     101            "<Hack>0.0</Hack>" +
     102            "<Pierce>10.0</Pierce>" +
     103            "<Crush>50.0</Crush>" +
     104        "</Death>" +
    97105    "</a:example>" +
    98106    "<optional>" +
    99107        "<element name='Melee'>" +
     
    177185                Attack.prototype.restrictedClassesSchema +
    178186            "</interleave>" +
    179187        "</element>" +
     188    "</optional>" +
     189    "<optional>" +
     190        "<element name='Death' a:help='When a unit dies it inflicts damage on nearby units.'>" +
     191            "<interleave>" +
     192                "<element name='Shape' a:help='Shape of the splash damage, can be circular or linear'><text/></element>" +
     193                "<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" +
     194                "<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" +
     195                "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
     196                "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
     197                "<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
     198                Attack.prototype.bonusesSchema +
     199                Attack.prototype.preferredClassesSchema +
     200                Attack.prototype.restrictedClassesSchema +
     201            "</interleave>" +
     202        "</element>" +
    180203    "</optional>";
    181204
    182205Attack.prototype.Init = function()
     
    606629        cmpUnitAI.UpdateRangeQueries();
    607630};
    608631
     632Attack.prototype.CauseDeathDamage = function()
     633{
     634    if (!this.template.Death)
     635        return;
     636
     637    let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     638    if (!cmpPosition.IsInWorld())
     639        return;
     640
     641    let pos = cmpPosition.GetPosition();
     642    let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
     643    let owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     644    let playersToDamage = cmpDamage.GetPlayersToDamage(this.template.Death.FriendlyFire, owner);
     645
     646    cmpDamage.CauseSplashDamage({
     647        "attacker": this.entity,
     648        "origin": Vector2D.from3D(pos),
     649        "radius": this.template.Death.Range,
     650        "shape": this.template.Death.Shape,
     651        "strengths": this.GetAttackStrengths("Death"),
     652        "direction": 0, //does directed death damage make sense?
     653        "playersToDamage": playersToDamage,
     654        "type": this.template.Type,
     655        "attackerOwner": owner
     656    });
     657
     658};
     659
    609660Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
  • binaries/data/mods/public/simulation/components/Damage.js

     
    6565};
    6666
    6767/**
     68 * Get the list of players affected by the damage.
     69 * @param {boolean} friendlyFire - a flag indicating if allied entities are also damaged.
     70 * @param {number}  attackerOwner - the player id of the attacker.
     71 */
     72Damage.prototype.GetPlayersToDamage = function(friendlyFire, attackerOwner)
     73{
     74    var playersToDamage = [];
     75    if (!friendlyFire)
     76        playersToDamage = QueryPlayerIDInterface(attackerOwner).GetEnemies();
     77    else
     78    {
     79        let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     80        for (let i = 0; i < numPlayers; ++i)
     81            playersToDamage.push(i);
     82    }
     83    return playersToDamage;
     84}
     85
     86/**
    6887 * Handles hit logic after the projectile travel time has passed.
    6988 * @param {Object}   data - the data sent by the caller.
    7089 * @param {number}   data.attacker - the entity id of the attacker.
     
    90109    // Do this first in case the direct hit kills the target
    91110    if (data.isSplash)
    92111    {
    93         let playersToDamage = [];
    94         if (!data.friendlyFire)
    95             playersToDamage = QueryPlayerIDInterface(data.attackerOwner).GetEnemies();
    96         else
    97         {
    98             let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
    99             for (let i = 0; i < numPlayers; ++i)
    100                 playersToDamage.push(i);
    101         }
     112        let playersToDamage = this.GetPlayersToDamage(data.friendlyFire, data.attackerOwner);
    102113
    103114        this.CauseSplashDamage({
    104115            "attacker": data.attacker,
  • binaries/data/mods/public/simulation/components/Health.js

     
    244244
    245245            this.hitpoints = 0;
    246246            this.RegisterHealthChanged(oldHitpoints);
     247
     248            let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     249            if (cmpAttack)
     250                cmpAttack.CauseDeathDamage();
     251
    247252        }
    248253
    249254    }
  • binaries/data/mods/public/simulation/components/tests/test_Attack.js

     
    66Engine.LoadComponentScript("interfaces/TechnologyManager.js");
    77Engine.LoadComponentScript("interfaces/Formation.js");
    88Engine.LoadComponentScript("interfaces/Attack.js");
     9Engine.LoadComponentScript("interfaces/Damage.js");
     10Engine.LoadComponentScript("interfaces/Timer.js");
    911Engine.LoadComponentScript("Attack.js");
     12Engine.LoadComponentScript("Timer.js");
    1013
    1114let entityID = 903;
     15let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
    1216
    1317function attackComponentTest(defenderClass, test_function)
    1418{
     
    3034
    3135    AddMock(attacker, IID_Position, {
    3236        "IsInWorld": () => true,
    33         "GetHeightOffset": () => 5
     37        "GetHeightOffset": () => 5,
     38        "GetPosition": () => new Vector3D(1, 2, 3)
    3439    });
    3540
    3641    AddMock(attacker, IID_Ownership, {
     
    6671            "MaxRange": 80,
    6772            "PrepareTime": 300,
    6873            "RepeatTime": 500,
     74            "ProjectileSpeed": 50,
     75            "Spread": 2.5,
    6976            "PreferredClasses": {
    7077                "_string": "Archer"
    7178            },
    7279            "RestrictedClasses": {
    7380                "_string": "Elephant"
     81            },
     82            "Splash" : {
     83                "Shape": "Circular",
     84                "Range": 10,
     85                "FriendlyFire": "false",
     86                "Hack": 0.0,
     87                "Pierce": 15.0,
     88                "Crush": 35.0
    7489            }
    7590        },
    7691        "Capture" : {
     
    7792            "Value": 8,
    7893            "MaxRange": 10,
    7994        },
    80         "Slaughter": {}
     95        "Slaughter": {},
     96        "Death": {
     97            "Shape": "Circular",
     98            "Range": 20,
     99            "FriendlyFire": false,
     100            "Hack": 0.0,
     101            "Pierce": 10.0,
     102            "Crush": 50.0
     103        }
    81104    });
    82105
    83106    let defender = ++entityID;
     
    119142        "prepare": 0,
    120143        "repeat": 1000
    121144    });
     145
     146    TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashDamage("Ranged"), {hack: 0, pierce: 15, crush: 35, friendlyFire: false});
    122147});
    123148
    124149for (let className of ["Infantry", "Cavalry"])
     
    161186testGetBestAttackAgainst("Archer", "Ranged");
    162187testGetBestAttackAgainst("Domestic", "Slaughter");
    163188testGetBestAttackAgainst("Structure", "Capture", true);
     189
     190attackComponentTest(undefined, (attacker, cmpAttack, defender) => {
     191
     192    var causeSplashDamageArg = "";
     193
     194    AddMock(SYSTEM_ENTITY, IID_Damage, {
     195        "CauseDamage": () => false,
     196        "SetTimeout": () => false,
     197        "GetPlayersToDamage": () => [1, 2],
     198        "CauseSplashDamage": function (arg) { causeSplashDamageArg = arg; }
     199    });
     200
     201    cmpAttack.CauseDeathDamage();
     202
     203    TS_ASSERT_UNEVAL_EQUALS(causeSplashDamageArg, {
     204        attacker: attacker,
     205        origin: {x:1, y:3},
     206        radius: 20,
     207        shape: "Circular",
     208        strengths: {hack:0, pierce:10, crush:50},
     209        direction: 0,
     210        playersToDamage: [1, 2],
     211        type: void(0),
     212        attackerOwner: 1
     213    });
     214});
  • binaries/data/mods/public/simulation/components/tests/test_Damage.js

     
    5353
    5454AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
    5555    GetPlayerByID: (id) => atkPlayerEntity,
     56    GetNumPlayers: () => 4
    5657});
    5758
    5859AddMock(SYSTEM_ENTITY, IID_RangeManager, {
     
    122123cmpAttack.PerformAttack("Ranged", target);
    123124Engine.DestroyEntity(attacker);
    124125TestDamage();
     126
     127atkPlayerEntity = 1;
     128AddMock(atkPlayerEntity, IID_Player, {
     129    GetEnemies: () => [2, 3],
     130});
     131TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(true, atkPlayerEntity), [0, 1, 2, 3]);
     132TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(false, atkPlayerEntity), [2, 3]);
  • binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_slinger.xml

     
    1616      <RepeatTime>1000</RepeatTime>
    1717      <Spread>1.4</Spread>
    1818    </Ranged>
     19    <Death>
     20      <Shape>Circular</Shape>
     21      <Range>15</Range>
     22      <FriendlyFire>true</FriendlyFire>
     23      <Hack>50.0</Hack>
     24      <Pierce>60.0</Pierce>
     25      <Crush>70.0</Crush>
     26    </Death>
    1927  </Attack>
    2028  <Cost>
    2129    <Resources>