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

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

  • binaries/data/mods/public/globalscripts/Templates.js

    Property changes on: binaries/data
    ___________________________________________________________________
    Modified: svn:ignore
    ## -1,3 +1,4 ##
    -*.xmb
     *.db
     *.jbf
    +*.xmb
    +cache
     
    138138                    // true if undefined
    139139                    "friendlyFire": template.Attack[type].Splash.FriendlyFire != "false"
    140140                };
     141
     142            if (type == "Death")
     143                ret.attack.Death = {
     144                    "hack": getAttackStat("Hack"),
     145                    "pierce": getAttackStat("Pierce"),
     146                    "crush": getAttackStat("Crush"),
     147                    // true if undefined
     148                    "friendlyFire": template.Attack.Death.FriendlyFire != "false"
     149                };
    141150        }
    142151    }
    143152
  • binaries/data/mods/public/gui/common/tooltips.js

     
    88const g_AttackTypes = {
    99    "Melee": translate("Melee Attack:"),
    1010    "Ranged": translate("Ranged Attack:"),
    11     "Capture": translate("Capture Attack:")
     11    "Capture": translate("Capture Attack:"),
     12    "Death": translate("Damage on Destroy:")
    1213};
    1314
    1415const g_DamageTypes = {
     
    141142    let attacks = [];
    142143    for (let type in template.attack)
    143144    {
     145        if (type == "Death") {
     146            attacks.push(sprintf(translate("%(label)s %(details)s Friendly Fire: %(enabled)s"), {
     147                "label": headerFont(g_AttackTypes[type]),
     148                "details": damageTypesToText(template.attack.Death),
     149                "enabled": template.attack.Death.friendlyFire ? translate("Yes") : translate("No")
     150            }));
     151            continue;
     152        }
     153
    144154        if (type == "Slaughter")
    145155            continue; // Slaughter is used to kill animals, so do not show it.
    146156
  • binaries/data/mods/public/simulation/components/Attack.js

     
    3737            "<text/>" +
    3838        "</element>" +
    3939    "</optional>";
     40   
     41Attack.prototype.splashSchema =
     42    "<interleave>" +
     43        "<element name='Shape' a:help='Shape of the splash damage, can be circular or linear'><text/></element>" +
     44        "<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" +
     45        "<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" +
     46        "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
     47        "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
     48        "<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
     49        Attack.prototype.bonusesSchema +
     50    "</interleave>";
    4051
    4152Attack.prototype.Schema =
    4253    "<a:help>Controls the attack abilities and strengths of the unit.</a:help>" +
     
    94105            "<Crush>0.0</Crush>" +
    95106            "<MaxRange>4.0</MaxRange>" +
    96107        "</Slaughter>" +
     108        "<Death>" +
     109            "<Shape>Circular</Shape>" +
     110            "<Range>20</Range>" +
     111            "<FriendlyFire>false</FriendlyFire>" +
     112            "<Hack>0.0</Hack>" +
     113            "<Pierce>10.0</Pierce>" +
     114            "<Crush>50.0</Crush>" +
     115        "</Death>" +
    97116    "</a:example>" +
    98117    "<optional>" +
    99118        "<element name='Melee'>" +
     
    138157                "<optional>" +
    139158                    "<element name='Splash'>" +
    140159                        "<interleave>" +
    141                             "<element name='Shape' a:help='Shape of the splash damage, can be circular or linear'><text/></element>" +
    142                             "<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" +
    143                             "<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" +
    144                             "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
    145                             "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
    146                             "<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
    147                             Attack.prototype.bonusesSchema +
     160                            Attack.prototype.splashSchema +
    148161                        "</interleave>" +
    149162                    "</element>" +
    150163                "</optional>" +
     
    177190                Attack.prototype.restrictedClassesSchema +
    178191            "</interleave>" +
    179192        "</element>" +
     193    "</optional>" +
     194    "<optional>" +
     195        "<element name='Death' a:help='When a unit or building is destroyed then it inflicts damage to nearby units.'>" +
     196            Attack.prototype.splashSchema +
     197        "</element>" +
    180198    "</optional>";
    181199
    182200Attack.prototype.Init = function()
     
    605623        cmpUnitAI.UpdateRangeQueries();
    606624};
    607625
     626Attack.prototype.CauseDeathDamage = function()
     627{
     628    if (!this.template.Death)
     629        return;
     630
     631    let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     632    if (!cmpPosition || !cmpPosition.IsInWorld())
     633        return;
     634
     635    let pos = cmpPosition.GetPosition();
     636    let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
     637    let owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     638    let playersToDamage = cmpDamage.GetPlayersToDamage(this.template.Death.FriendlyFire, owner);
     639
     640    cmpDamage.CauseSplashDamage({
     641        "attacker": this.entity,
     642        "origin": Vector2D.from3D(pos),
     643        "radius": this.template.Death.Range,
     644        "shape": this.template.Death.Shape,
     645        "strengths": this.GetAttackStrengths("Death"),
     646        "direction": 0,
     647        "playersToDamage": playersToDamage,
     648        "type": this.template.Type,
     649        "attackerOwner": owner
     650    });
     651};
     652
    608653Engine.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 * @return {list of numbers} - the ids of players need to be damaged
     72 */
     73Damage.prototype.GetPlayersToDamage = function(friendlyFire, attackerOwner)
     74{
     75    let player = QueryPlayerIDInterface(attackerOwner);
     76    let nonAllies = player.GetEnemies().concat(player.GetNeutrals());
     77       
     78    return friendlyFire ? nonAllies.concat(player.GetAllies()) : nonAllies;
     79}
     80
     81/**
    6882 * Handles hit logic after the projectile travel time has passed.
    6983 * @param {Object}   data - the data sent by the caller.
    7084 * @param {number}   data.attacker - the entity id of the attacker.
     
    90104    // Do this first in case the direct hit kills the target
    91105    if (data.isSplash)
    92106    {
    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         }
    102 
    103107        this.CauseSplashDamage({
    104108            "attacker": data.attacker,
    105109            "origin": Vector2D.from3D(data.position),
     
    107111            "shape": data.shape,
    108112            "strengths": data.splashStrengths,
    109113            "direction": data.direction,
    110             "playersToDamage": playersToDamage,
     114            "playersToDamage": this.GetPlayersToDamage(data.friendlyFire, data.attackerOwner),
    111115            "type": data.type,
    112116            "attackerOwner": data.attackerOwner
    113117        });
  • 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();
    247251        }
    248252
    249253    }
  • binaries/data/mods/public/simulation/components/Player.js

     
    646646    return this.GetPlayersByDiplomacy("IsEnemy");
    647647};
    648648
     649Player.prototype.GetNeutrals = function()
     650{
     651    return this.GetPlayersByDiplomacy("IsNeutral");
     652};
     653
    649654Player.prototype.SetNeutral = function(id)
    650655{
    651656    this.SetDiplomacyIndex(id, 0);
  • 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": 1000.0,
     101            "Pierce": 1000.0,
     102            "Crush": 500.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    let 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": 1000, "pierce": 1000, "crush": 500 },
     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

     
    4949
    5050AddMock(atkPlayerEntity, IID_Player, {
    5151    GetEnemies: () => [targetOwner],
     52    GetNeutrals: () => []
    5253});
    5354
    5455AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
    5556    GetPlayerByID: (id) => atkPlayerEntity,
     57    GetNumPlayers: () => 5
    5658});
    5759
    5860AddMock(SYSTEM_ENTITY, IID_RangeManager, {
     
    122124cmpAttack.PerformAttack("Ranged", target);
    123125Engine.DestroyEntity(attacker);
    124126TestDamage();
     127
     128atkPlayerEntity = 1;
     129AddMock(atkPlayerEntity, IID_Player, {
     130    GetEnemies: () => [2, 3],
     131    GetNeutrals: () => [4],
     132    GetAllies: () => [0, 1]
     133});
     134TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(true, atkPlayerEntity), [2, 3, 4, 0, 1]);
     135TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(false, atkPlayerEntity), [2, 3, 4]);
  • binaries/data/mods/public/simulation/components/tests/test_Player.js

     
    1616cmpPlayer.SetDiplomacy([-1, 1, 0, 1, -1]);
    1717TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetAllies(), [1, 3]);
    1818TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetEnemies(), [0, 4]);
     19TS_ASSERT_UNEVAL_EQUALS(cmpPlayer.GetNeutrals(), [2]);
    1920
    2021var diplo = cmpPlayer.GetDiplomacy();
    2122diplo[0] = 1;
  • binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

     
    2626      <Spread>1.5</Spread>
    2727      <PreferredClasses datatype="tokens">Human</PreferredClasses>
    2828    </Ranged>
     29    <Death>
     30      <Shape>Circular</Shape>
     31      <Range>23</Range>
     32      <FriendlyFire>true</FriendlyFire>
     33      <Hack>10.0</Hack>
     34      <Pierce>10.0</Pierce>
     35      <Crush>20.0</Crush>
     36    </Death>
    2937  </Attack>
    3038  <BuildingAI>
    3139    <DefaultArrowCount>3</DefaultArrowCount>
  • binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml

     
    1818      <Spread>1.5</Spread>
    1919      <PreferredClasses datatype="tokens">Human</PreferredClasses>
    2020    </Ranged>
     21    <Death>
     22      <Shape>Circular</Shape>
     23      <Range>23</Range>
     24      <FriendlyFire>true</FriendlyFire>
     25      <Hack>10.0</Hack>
     26      <Pierce>10.0</Pierce>
     27      <Crush>20.0</Crush>
     28    </Death>
    2129  </Attack>
    2230  <BuildingAI>
    2331    <DefaultArrowCount>3</DefaultArrowCount>