Ticket #252: t252_secondattack_1.diff

File t252_secondattack_1.diff, 27.0 KB (added by bb, 8 years ago)

Patch

  • binaries/data/config/default.cfg

     
    272272[hotkey.session]
    273273kill = Delete                ; Destroy selected units
    274274stop = "H"                   ; Stop the current action
    275 attack = Ctrl                ; Modifier to attack instead of another action (eg capture)
    276 attackmove = Ctrl            ; Modifier to attackmove when clicking on a point
    277 attackmoveUnit = "Ctrl+Q"    ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)
     275attack = Ctrl                ; Modifier to primary attack instead of another action (eg capture)
     276attackmove = Ctrl            ; Modifier to primary attackmove when clicking on a point
     277attackmoveUnit = "Ctrl+Q"    ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys)
    278278garrison = Ctrl              ; Modifier to garrison when clicking on building
    279279autorallypoint = Ctrl        ; Modifier to set the rally point on the building itself
     280secondattack = Alt           ; Modifier to secondary attack instead of another action
     281secondattackmove = Alt       ; Modifier to secondary attackmove when clicking on a point
     282secondattackmoveUnit = "Alt+Q"; Modifier to  secondary attackmove targeting only units when clicking on a point
    280283guard = "G"                  ; Modifier to escort/guard when clicking on unit/building
    281284queue = Shift                ; Modifier to queue unit orders instead of replacing
    282285batchtrain = Shift           ; Modifier to train units in batches
  • binaries/data/mods/public/art/actors/units/persians/champion_unit_1.xml

     
    1414        <animation event="0.5" file="infantry/sword/attack/isw_s_def_06.psa" name="attack_melee" speed="80"/>
    1515        <animation event="0.5" file="infantry/sword/attack/isw_s_def_06.psa" name="attack_melee" speed="80"/>
    1616        <animation event="0.5" file="infantry/sword/attack/isw_s_off_05.psa" name="attack_melee" speed="80"/>
     17        <animation event="0.84" file="biped/inf_arch_atk_a.psa" load="0.16" name="attack_ranged" speed="90"/>
    1718        <animation file="infantry/sword/move/run/isw_s_off_01.psa" name="Run" speed="25"/>
    1819        <animation file="infantry/sword/move/run/isw_s_def_02.psa" name="Run" speed="30"/>
    1920        <animation file="infantry/sword/move/run/isw_s_em_03.psa" name="Run" speed="30"/>
     
    3233        <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/>
    3334        <prop actor="props/units/pers_quiver_back.xml" attachpoint="back"/>
    3435      </props>
     36      <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures>
    3537    </variant>
    3638  </group>
    3739  <group>
     
    3840    <variant frequency="10" name="Armor Iron Scales">
    3941      <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures>
    4042    </variant>
     43    <variant name="attack_ranged">
     44      <props>
     45        <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/>
     46        <prop actor="props/units/weapons/arrow_back.xml" attachpoint="loaded-r_hand"/>
     47        <prop actor="props/units/weapons/arrow_front.xml" attachpoint="projectile"/>
     48      </props>
     49    </variant>
    4150  </group>
    4251  <material>player_trans.xml</material>
    4352</actor>
  • binaries/data/mods/public/gui/session/input.js

     
    205205                data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
    206206                cursor = "action-attack-move";
    207207            }
     208
     209            if (Engine.HotkeyIsPressed("session.secondattackmove"))
     210            {
     211                data.command = "attack-walk";
     212                data.targetClasses = Engine.HotkeyIsPressed("session.secondattackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
     213                cursor = "action-attack-move"; //TODO another cursor
     214            }
    208215            return { "possible": true, "data": data, "cursor": cursor };
    209216        }
    210217
  • binaries/data/mods/public/gui/session/unit_actions.js

     
    8787        "specificness": 30,
    8888    },
    8989
     90    "second-attack-move": // TODO is this one needed?
     91    {
     92        "execute": function(target, action, selection, queued)
     93        {
     94            if (Engine.HotkeyIsPressed("session.secondattackmoveUnit"))
     95                var targetClasses = { "secondattack": ["Unit"] };
     96            else
     97                var targetClasses = { "secondattack": ["Unit", "Structure"] };
     98
     99            Engine.PostNetworkCommand({"type": "second-attack-walk", "entities": selection, "x": target.x, "z": target.z, "targetClasses": targetClasses, "queued": queued});
     100            Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
     101            return true;
     102        },
     103        "getActionInfo": function(entState, targetState)
     104        {
     105            if (!entState.attack || !targetState.hitpoints)
     106                return false;
     107            return {"possible": Engine.GuiInterfaceCall("CanSecondAttack", {"entity": entState.id, "target": targetState.id})};
     108        },
     109        "hotkeyActionCheck": function(target, selection)
     110        {
     111            // Work out whether at least part of the selection have UnitAI
     112            var haveUnitAI = selection.some(function(ent) {
     113                var entState = GetEntityState(ent);
     114                return entState && entState.unitAI;
     115            });
     116            if (haveUnitAI && Engine.HotkeyIsPressed("session.secondattackmove") && getActionInfo("second-attack-move", target).possible)
     117                return {"type": "second-attack-move", "cursor": "action-attack-move"}; // TODO another cursor
     118            return false;
     119        },
     120        "specificness": 32,
     121    },
     122
    90123    "capture":
    91124    {
    92125        "execute": function(target, action, selection, queued)
     
    139172        "specificness": 10,
    140173    },
    141174
     175    "second-attack":
     176    {
     177        "execute": function(target, action, selection, queued)
     178        {
     179            Engine.PostNetworkCommand({"type": "second-attack", "entities": selection, "target": action.target, "queued": queued, "allowCapture": false});
     180            Engine.GuiInterfaceCall("PlaySound", { "name": "order_secondattack", "entity": selection[0] });
     181            return true;
     182        },
     183        "getActionInfo": function(entState, targetState)
     184        {
     185            if (!entState.attack || !targetState.hitpoints)
     186                return false;
     187            return {"possible": Engine.GuiInterfaceCall("CanSecondAttack", {"entity": entState.id, "target": targetState.id})};
     188        },
     189        "hotkeyActionCheck": function(target)
     190        {
     191            if (Engine.HotkeyIsPressed("session.secondattack") && getActionInfo("second-attack", target).possible)
     192                return {"type": "second-attack", "cursor": "action-attack", "target": target}; // TODO another cursor
     193            return false;
     194        },
     195        "actionCheck": function(target)
     196        {
     197            if (getActionInfo("second-attack", target).possible)
     198                return {"type": "second-attack", "cursor": "action-attack", "target": target}; // TODO another cursor
     199            return false;
     200        },
     201        "specificness": 15,
     202    },
     203
    142204    "heal":
    143205    {
    144206        "execute": function(target, action, selection, queued)
     
    499561                data.targetClasses = targetClasses;
    500562                cursor = "action-attack-move";
    501563            }
     564            if (Engine.HotkeyIsPressed("session.secondattackmove"))
     565            {
     566                if (Engine.HotkeyIsPressed("session.secondattackmoveUnit"))
     567                    var targetClasses = { "second-attack": ["Unit"] };
     568                else
     569                    var targetClasses = { "second-attack": ["Unit", "Structure"] };
     570                data.command = "second-attack-walk";
     571                data.targetClasses = targetClasses;
     572                cursor = "action-attack-move"; //TODO another cursor
     573            }
    502574
    503575            if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
    504576            {
  • binaries/data/mods/public/simulation/components/Attack.js

     
    4242    "<a:help>Controls the attack abilities and strengths of the unit.</a:help>" +
    4343    "<a:example>" +
    4444        "<Melee>" +
     45            "<AttackOrder>primary</AttackOrder>" +
    4546            "<Hack>10.0</Hack>" +
    4647            "<Pierce>0.0</Pierce>" +
    4748            "<Crush>5.0</Crush>" +
     
    6263            "<PreferredClasses datatype=\"tokens\">Cavalry Infantry</PreferredClasses>" +
    6364        "</Melee>" +
    6465        "<Ranged>" +
     66            "<AttackOrder>secondary</AttackOrder>" +
    6567            "<Hack>0.0</Hack>" +
    6668            "<Pierce>10.0</Pierce>" +
    6769            "<Crush>0.0</Crush>" +
     
    104106    "</a:example>" +
    105107    "<optional>" +
    106108        "<element name='Melee'>" +
     109            "<optional>" +
     110                "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" +
     111            "</optional>" +
    107112            "<interleave>" +
    108113                "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
    109114                "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
     
    120125    "</optional>" +
    121126    "<optional>" +
    122127        "<element name='Ranged'>" +
     128            "<optional>" +
     129                "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" +
     130            "</optional>" +
    123131            "<interleave>" +
    124132                "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
    125133                "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
     
    218226
    219227Attack.prototype.GetPreferredClasses = function(type)
    220228{
    221     if (this.template[type] && this.template[type].PreferredClasses &&
    222         this.template[type].PreferredClasses._string)
    223     {
     229    if (this.template[type] && this.template[type].PreferredClasses && this.template[type].PreferredClasses._string)
    224230        return this.template[type].PreferredClasses._string.split(/\s+/);
    225     }
    226231    return [];
    227232};
    228233
    229234Attack.prototype.GetRestrictedClasses = function(type)
    230235{
    231     if (this.template[type] && this.template[type].RestrictedClasses &&
    232         this.template[type].RestrictedClasses._string)
    233     {
     236    if (this.template[type] && this.template[type].RestrictedClasses && this.template[type].RestrictedClasses._string)
    234237        return this.template[type].RestrictedClasses._string.split(/\s+/);
    235     }
    236238    return [];
    237239};
    238240
     
    288290    return false;
    289291};
    290292
     293Attack.prototype.CanSecondAttack = function(target)
     294{
     295    for (let type of this.GetAttackTypes())
     296        if (this.template[type].AttackOrder && this.template[type].AttackOrder == "secondary")
     297            return this.CanAttack(target);
     298    return false;
     299};
    291300/**
    292301 * Returns null if we have no preference or the lowest index of a preferred class.
    293302 */
     
    327336        if (type == "Slaughter")
    328337            continue;
    329338        let range = this.GetRange(type);
    330         if (range.min < ret.min)
    331             ret.min = range.min;
    332         if (range.max > ret.max)
    333             ret.max = range.max;
     339        ret.min = Math.min(ret.min, Range.min)
     340        ret.max = Math.max(ret.max, Range.max)
    334341    }
    335342    return ret;
    336343};
    337344
    338 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
     345Attack.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType)
    339346{
    340347    let cmpFormation = Engine.QueryInterface(target, IID_Formation);
    341348    if (cmpFormation)
     
    365372
    366373    let types = this.GetAttackTypes().filter(isAllowed);
    367374
     375    if (prefType)
     376    {
     377        if (types[prefType])
     378            return preftype
     379        prefType = this.GetPrefAttack(types, prefType);
     380        if (types.indexOf(prefType))
     381            return prefType;
     382    }
     383
    368384    // check if the target is capturable
    369385    let captureIndex = types.indexOf("Capture");
    370386    if (captureIndex != -1)
     
    378394        types.splice(captureIndex, 1);
    379395    }
    380396
    381     let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); };
    382     let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); };
     397    // only ranged and/or melee attack left
     398    // if one attacktype left choose this one
     399    if (types.indexOf("Melee") == -1 || types.indexOf("Ranged") == -1)
     400        return types[0];
    383401
    384     return types.sort(byPreference).pop();
     402    if (this.HasPreferredClasses(types))
     403    {
     404        let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); };
     405        let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); };
     406
     407        return types.sort(byPreference).pop();
     408    }
     409        // assume ranged and melee attack
     410        // TODO stop assuming that
     411    let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     412    if (!cmpPosition || !cmpPosition.IsInWorld())
     413        return undefined;
     414    let selfPosition = cmpPosition.GetPosition();
     415    let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
     416    if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
     417        return undefined;
     418    let targetPosition = cmpTargetPosition.GetPosition();
     419    let horizDistance = targetPosition.horizDistanceTo(selfPosition);
     420    if (horizDistance <= 2 * this.GetRange("Melee").max)
     421        return "Melee";
     422    return "Ranged"
     423
    385424};
    386425
    387426Attack.prototype.CompareEntitiesByPreference = function(a, b)
     
    478517    return attackBonus;
    479518};
    480519
     520// Returns preferred attack type if exists
     521Attack.prototype.GetPrefAttack = function(types, pref)
     522{
     523    for (let type of types)
     524        if (this.template[type].AttackOrder && pref == this.template[type].AttackOrder)
     525            return type;
     526    return types[0];
     527};
     528
     529Attack.prototype.HasPreferredClasses = function(types)
     530{
     531    for (let type of types)
     532        if (this.template[type].PreferredClasses)
     533            return true;
     534    return false
     535};
    481536// Returns a 2d random distribution scaled for a spread of scale 1.
    482537// The current implementation is a 2d gaussian with sigma = 1
    483538Attack.prototype.GetNormalDistribution = function(){
     
    486541    let a = Math.random();
    487542    let b = Math.random();
    488543
    489     let c = Math.sqrt(-2*Math.log(a)) * Math.cos(2*Math.PI*b);
    490     let d = Math.sqrt(-2*Math.log(a)) * Math.sin(2*Math.PI*b);
     544    let c = Math.sqrt(-2 * Math.log(a)) * Math.cos(2 * Math.PI * b);
     545    let d = Math.sqrt(-2 * Math.log(a)) * Math.sin(2 * Math.PI * b);
    491546
    492547    return [c, d];
    493548};
     
    503558    if (type == "Ranged")
    504559    {
    505560        let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    506         let turnLength = cmpTimer.GetLatestTurnLength()/1000;
     561        let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
    507562        // In the future this could be extended:
    508563        //  * Obstacles like trees could reduce the probability of the target being hit
    509564        //  * Obstacles like walls should block projectiles entirely
     
    620675Attack.prototype.InterpolatedLocation = function(ent, lateness)
    621676{
    622677    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    623     let turnLength = cmpTimer.GetLatestTurnLength()/1000;
     678    let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
    624679    let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
    625680    if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
    626681        return undefined;
     
    658713        let d = Vector3D.sub(point, targetPosition);
    659714        d = Vector2D.from3D(d).rotate(-angle);
    660715
    661         return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);
     716        return d.x < Math.abs(targetShape.width / 2) && d.y < Math.abs(targetShape.depth / 2);
    662717    }
    663718};
    664719
     
    740795        return;
    741796
    742797    for (let type of this.GetAttackTypes())
    743         if (msg.valueNames.indexOf("Attack/"+type+"/MaxRange") !== -1)
     798        if (msg.valueNames.indexOf("Attack/" + type + "/MaxRange") !== -1)
    744799        {
    745800            cmpUnitAI.UpdateRangeQueries();
    746801            return;
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    17261726    return false;
    17271727};
    17281728
     1729GuiInterface.prototype.CanSecondAttack = function(player, data)
     1730{
     1731    let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
     1732    if (!cmpAttack)
     1733        return false;
     1734
     1735    return cmpAttack.CanSecondAttack(data.target)
     1736};
     1737
    17291738/*
    17301739 * Returns batch build time.
    17311740 */
     
    18691878    "GetTradingDetails": 1,
    18701879    "CanCapture": 1,
    18711880    "CanAttack": 1,
     1881    "CanSecondAttack": 1,
    18721882    "GetBatchTime": 1,
    18731883
    18741884    "IsMapRevealed": 1,
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    416416        }
    417417
    418418        // Work out how to attack the given target
    419         var type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture);
     419        let type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture, this.order.data.prefType);
    420420        if (!type)
    421421        {
    422422            // Oops, we can't attack at all
     
    583583                return;
    584584            }
    585585
    586             this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false });
     586            this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false, "prefType": undefined });
    587587            return;
    588588        }
    589589
     
    842842        },
    843843
    844844        "Order.Attack": function(msg) {
    845             var target = msg.data.target;
    846             var allowCapture = msg.data.allowCapture;
    847             var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
     845            let target = msg.data.target;
     846            let allowCapture = msg.data.allowCapture;
     847            let prefType = msg.data.prefType;
     848            let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
    848849            if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
    849850                target = cmpTargetUnitAI.GetFormationController();
    850851
     
    863864                this.FinishOrder();
    864865                return;
    865866            }
    866             this.CallMemberFunction("Attack", [target, false, allowCapture]);
     867            this.CallMemberFunction("Attack", [target, false, allowCapture, prefType]);
    867868            if (cmpAttack.CanAttackAsFormation())
    868869                this.SetNextState("COMBAT.ATTACKING");
    869870            else
     
    918919                    return;
    919920                }
    920921
    921                 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });
     922                this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false, "prefType": undefined });
    922923                return;
    923924            }
    924925
     
    11551156
    11561157                "MoveCompleted": function(msg) {
    11571158                    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    1158                     this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture]);
     1159                    this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture, this.order.data.prefType]);
    11591160                    if (cmpAttack.CanAttackAsFormation())
    11601161                        this.SetNextState("COMBAT.ATTACKING");
    11611162                    else
     
    11661167            "ATTACKING": {
    11671168                // Wait for individual members to finish
    11681169                "enter": function(msg) {
    1169                     var target = this.order.data.target;
    1170                     var allowCapture = this.order.data.allowCapture;
     1170                    let target = this.order.data.target;
     1171                    let allowCapture = this.order.data.allowCapture;
     1172                    let prefType = this.order.data.prefType;
    11711173                    // Check if we are already in range, otherwise walk there
    11721174                    if (!this.CheckTargetAttackRange(target, target))
    11731175                    {
     
    11741176                        if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
    11751177                        {
    11761178                            this.FinishOrder();
    1177                             this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
     1179                            this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType });
    11781180                            return true;
    11791181                        }
    11801182                        this.FinishOrder();
     
    11901192                },
    11911193
    11921194                "Timer": function(msg) {
    1193                     var target = this.order.data.target;
    1194                     var allowCapture = this.order.data.allowCapture;
     1195                    let target = this.order.data.target;
     1196                    let allowCapture = this.order.data.allowCapture;
     1197                    let prefType = this.order.data.prefType;
    11951198                    // Check if we are already in range, otherwise walk there
    11961199                    if (!this.CheckTargetAttackRange(target, target))
    11971200                    {
     
    11981201                        if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
    11991202                        {
    12001203                            this.FinishOrder();
    1201                             this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
     1204                            this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType });
    12021205                            return;
    12031206                        }
    12041207                        this.FinishOrder();
     
    14101413
    14111414            // target the unit
    14121415            if (this.CheckTargetVisible(msg.data.attacker))
    1413                 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true });
     1416                this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true, "prefType": undefined });
    14141417            else
    14151418            {
    14161419                var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
     
    45234526    return distance < range;
    45244527};
    45254528
    4526 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
     4529UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType)
    45274530{
    45284531    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    45294532    if (!cmpAttack)
    45304533        return undefined;
    4531     return cmpAttack.GetBestAttackAgainst(target, allowCapture);
     4534    return cmpAttack.GetBestAttackAgainst(target, allowCapture, prefType);
    45324535};
    45334536
    45344537UnitAI.prototype.GetAttackBonus = function(type, target)
     
    45504553    if (!target)
    45514554        return false;
    45524555
    4553     this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
     4556    this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined });
    45544557    return true;
    45554558};
    45564559
     
    45634566{
    45644567    var target = ents.find(target =>
    45654568        this.CanAttack(target, forceResponse)
    4566         && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true))
     4569        && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true, undefined))
    45674570        && (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target))
    45684571    );
    45694572    if (!target)
    45704573        return false;
    45714574
    4572     this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
     4575    this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined });
    45734576    return true;
    45744577};
    45754578
     
    49894992/**
    49904993 * Adds attack order to the queue, forced by the player.
    49914994 */
    4992 UnitAI.prototype.Attack = function(target, queued, allowCapture)
     4995UnitAI.prototype.Attack = function(target, queued, allowCapture, prefType)
    49934996{
    49944997    if (!this.CanAttack(target))
    49954998    {
     
    50015004            this.WalkToTarget(target, queued);
    50025005        return;
    50035006    }
    5004     this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
     5007    this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture, "prefType": prefType }, queued);
    50055008};
    50065009
    50075010/**
     
    54115414                    if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
    54125415                        continue;
    54135416                }
    5414                 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
     5417                this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined });
    54155418                return true;
    54165419            }
    54175420        }
     
    54375440            if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
    54385441                continue;
    54395442        }
    5440         this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
     5443        this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined });
    54415444        return true;
    54425445    }
    54435446    return false;
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    141141        });
    142142    },
    143143
     144    "second-attack-walk": function(player, cmd, data)
     145    {
     146        GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
     147            cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
     148        });
     149    },
     150
    144151    "attack": function(player, cmd, data)
    145152    {
    146153        if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
     
    149156            warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
    150157        }
    151158
     159        let prefType = "primary"
    152160        let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
     161        if (allowCapture)
     162            prefType = "Capture";
    153163        // See UnitAI.CanAttack for target checks
    154164        GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
    155             cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture);
     165            cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType);
    156166        });
    157167    },
    158168
     169    "second-attack": function(player, cmd, data)
     170    {
     171        if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
     172        {
     173            // This check is for debugging only!
     174            warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
     175        }
     176
     177        let prefType = "secondary"
     178        let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
     179        // See UnitAI.CanAttack for target checks
     180        GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
     181            cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType);
     182        });
     183    },
     184
    159185    "heal": function(player, cmd, data)
    160186    {
    161187        if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target)))
  • binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml

     
    66  </Armour>
    77  <Attack>
    88    <Melee>
     9      <AttackOrder>primary</AttackOrder>
    910      <Hack>6.0</Hack>
    1011      <Pierce>5.0</Pierce>
    1112      <Crush>0.0</Crush>
     
    1819          </BonusCavMelee>
    1920      </Bonuses>
    2021    </Melee>
     22    <Ranged>
     23      <AttackOrder>secondary</AttackOrder>
     24      <Hack>0</Hack>
     25      <Pierce>6.0</Pierce>
     26      <Crush>0</Crush>
     27      <MaxRange>72.0</MaxRange>
     28      <MinRange>0.0</MinRange>
     29      <ProjectileSpeed>120.0</ProjectileSpeed>
     30      <PrepareTime>1000</PrepareTime>
     31      <RepeatTime>1000</RepeatTime>
     32      <Spread>2.0</Spread>
     33    </Ranged>
    2134    <Charge>
    2235      <Hack>15.0</Hack>
    2336      <Pierce>40.0</Pierce>