Ticket #252: t252_secondattack_2.diff

File t252_secondattack_2.diff, 27.6 KB (added by bb, 8 years ago)

Tweaked some little things, mainly need to be decided which units get secondary attacks

  • binaries/data/config/default.cfg

     
    274274[hotkey.session]
    275275kill = Delete                ; Destroy selected units
    276276stop = "H"                   ; Stop the current action
    277 attack = Ctrl                ; Modifier to attack instead of another action (eg capture)
    278 attackmove = Ctrl            ; Modifier to attackmove when clicking on a point
    279 attackmoveUnit = "Ctrl+Q"    ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)
     277attack = Ctrl                ; Modifier to primary attack instead of another action (eg capture)
     278attackmove = Ctrl            ; Modifier to primary attackmove when clicking on a point
     279attackmoveUnit = "Ctrl+Q"    ; Modifier to primary attackmove targeting only units when clicking on a point (should contain the attackmove keys)
    280280garrison = Ctrl              ; Modifier to garrison when clicking on building
    281281autorallypoint = Ctrl        ; Modifier to set the rally point on the building itself
     282secondattack = Alt           ; Modifier to secondary attack instead of another action
     283secondattackmove = Alt       ; Modifier to secondary attackmove when clicking on a point
     284secondattackmoveUnit = "Alt+Q"; Modifier to  secondary attackmove targeting only units when clicking on a point
    282285guard = "G"                  ; Modifier to escort/guard when clicking on unit/building
    283286queue = Shift                ; Modifier to queue unit orders instead of replacing
    284287batchtrain = 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"/>
     
    2829      <props>
    2930        <prop actor="props/units/heads/head_pers_tiara.xml" attachpoint="head"/>
    3031        <prop actor="props/units/heads/pers_kidaris_tied.xml" attachpoint="helmet"/>
    31         <prop actor="props/units/shields/gerron_b.xml" attachpoint="shield"/>
    32         <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/>
    3332        <prop actor="props/units/pers_quiver_back.xml" attachpoint="back"/>
    3433      </props>
    3534    </variant>
     
    3938      <textures><texture file="skeletal/pers_su1_anusiya_iron.dds" name="baseTex"/></textures>
    4039    </variant>
    4140  </group>
     41  <group>
     42    <variant name="attack_melee">
     43      <props>
     44        <prop actor="props/units/shields/gerron_b.xml" attachpoint="shield"/>
     45        <prop actor="props/units/weapons/spear_ball.xml" attachpoint="r_hand"/>
     46      </props>
     47    </variant>
     48    <variant name="attack_ranged">
     49      <props>
     50        <prop actor="props/units/weapons/bow_recurve.xml" attachpoint="l_hand"/>
     51        <prop actor="props/units/weapons/arrow_back.xml" attachpoint="loaded-r_hand"/>
     52        <prop actor="props/units/weapons/arrow_front.xml" attachpoint="projectile"/>
     53      </props>
     54    </variant>
     55  </group>
    4256  <material>player_trans.xml</material>
    4357</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", "MutualAlly"]))
    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>" +
     
    103105        "</Slaughter>" +
    104106    "</a:example>" +
    105107    "<optional>" +
     108        "<element name='ChangeDistance' a:help='Distance to change between Melee and Ranged attack'>" +
     109            "<ref name='nonNegativeDecimal'/>" +
     110        "</element>" +
     111    "</optional>" +
     112    "<optional>" +
    106113        "<element name='Melee'>" +
     114            "<optional>" +
     115                "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" +
     116            "</optional>" +
    107117            "<interleave>" +
    108118                "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
    109119                "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
     
    120130    "</optional>" +
    121131    "<optional>" +
    122132        "<element name='Ranged'>" +
     133            "<optional>" +
     134                "<element name='AttackOrder' a:help='primary or secondary'><text/></element>" +
     135            "</optional>" +
    123136            "<interleave>" +
    124137                "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
    125138                "<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
     
    218231
    219232Attack.prototype.GetPreferredClasses = function(type)
    220233{
    221     if (this.template[type] && this.template[type].PreferredClasses &&
    222         this.template[type].PreferredClasses._string)
    223     {
     234    if (this.template[type] && this.template[type].PreferredClasses && this.template[type].PreferredClasses._string)
    224235        return this.template[type].PreferredClasses._string.split(/\s+/);
    225     }
    226236    return [];
    227237};
    228238
    229239Attack.prototype.GetRestrictedClasses = function(type)
    230240{
    231     if (this.template[type] && this.template[type].RestrictedClasses &&
    232         this.template[type].RestrictedClasses._string)
    233     {
     241    if (this.template[type] && this.template[type].RestrictedClasses && this.template[type].RestrictedClasses._string)
    234242        return this.template[type].RestrictedClasses._string.split(/\s+/);
    235     }
    236243    return [];
    237244};
    238245
     
    288295    return false;
    289296};
    290297
     298Attack.prototype.CanSecondAttack = function(target)
     299{
     300    for (let type of this.GetAttackTypes())
     301        if (this.template[type].AttackOrder && this.template[type].AttackOrder == "secondary")
     302            return this.CanAttack(target);
     303    return false;
     304};
    291305/**
    292306 * Returns null if we have no preference or the lowest index of a preferred class.
    293307 */
     
    327341        if (type == "Slaughter")
    328342            continue;
    329343        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;
     344        ret.min = Math.min(ret.min, range.min)
     345        ret.max = Math.max(ret.max, range.max)
    334346    }
    335347    return ret;
    336348};
    337349
    338 Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
     350Attack.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType)
    339351{
    340352    let cmpFormation = Engine.QueryInterface(target, IID_Formation);
    341353    if (cmpFormation)
     
    365377
    366378    let types = this.GetAttackTypes().filter(isAllowed);
    367379
     380    if (prefType)
     381    {
     382        if (types[prefType])
     383            return preftype
     384        prefType = this.GetPrefAttack(types, prefType);
     385        if (types.indexOf(prefType))
     386            return prefType;
     387    }
     388
    368389    // check if the target is capturable
    369390    let captureIndex = types.indexOf("Capture");
    370391    if (captureIndex != -1)
     
    378399        types.splice(captureIndex, 1);
    379400    }
    380401
    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) ); };
     402    // only ranged and/or melee attack left
     403    // if one attacktype left choose this one
     404    if (types.indexOf("Melee") == -1 || types.indexOf("Ranged") == -1)
     405        return types[0];
    383406
    384     return types.sort(byPreference).pop();
     407    if (this.HasPreferredClasses(types))
     408    {
     409        let isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); };
     410        let byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); };
     411
     412        return types.sort(byPreference).pop();
     413    }
     414        // assume ranged and melee attack
     415        // TODO stop assuming that?
     416    let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     417    if (!cmpPosition || !cmpPosition.IsInWorld())
     418        return undefined;
     419    let selfPosition = cmpPosition.GetPosition();
     420    let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
     421    if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
     422        return undefined;
     423    let targetPosition = cmpTargetPosition.GetPosition();
     424    let horizDistance = targetPosition.horizDistanceTo(selfPosition);
     425    if (horizDistance <= this.template.ChangeDistance)
     426        return "Melee";
     427    return "Ranged"
     428
    385429};
    386430
    387431Attack.prototype.CompareEntitiesByPreference = function(a, b)
     
    478522    return attackBonus;
    479523};
    480524
     525// Returns preferred attack type if exists
     526Attack.prototype.GetPrefAttack = function(types, pref)
     527{
     528    for (let type of types)
     529        if (this.template[type].AttackOrder && pref == this.template[type].AttackOrder)
     530            return type;
     531    return types[0];
     532};
     533
     534Attack.prototype.HasPreferredClasses = function(types)
     535{
     536    for (let type of types)
     537        if (this.template[type].PreferredClasses)
     538            return true;
     539    return false
     540};
    481541// Returns a 2d random distribution scaled for a spread of scale 1.
    482542// The current implementation is a 2d gaussian with sigma = 1
    483543Attack.prototype.GetNormalDistribution = function(){
     
    486546    let a = Math.random();
    487547    let b = Math.random();
    488548
    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);
     549    let c = Math.sqrt(-2 * Math.log(a)) * Math.cos(2 * Math.PI * b);
     550    let d = Math.sqrt(-2 * Math.log(a)) * Math.sin(2 * Math.PI * b);
    491551
    492552    return [c, d];
    493553};
     
    503563    if (type == "Ranged")
    504564    {
    505565        let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    506         let turnLength = cmpTimer.GetLatestTurnLength()/1000;
     566        let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
    507567        // In the future this could be extended:
    508568        //  * Obstacles like trees could reduce the probability of the target being hit
    509569        //  * Obstacles like walls should block projectiles entirely
     
    620680Attack.prototype.InterpolatedLocation = function(ent, lateness)
    621681{
    622682    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    623     let turnLength = cmpTimer.GetLatestTurnLength()/1000;
     683    let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
    624684    let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
    625685    if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
    626686        return undefined;
     
    658718        let d = Vector3D.sub(point, targetPosition);
    659719        d = Vector2D.from3D(d).rotate(-angle);
    660720
    661         return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);
     721        return d.x < Math.abs(targetShape.width / 2) && d.y < Math.abs(targetShape.depth / 2);
    662722    }
    663723};
    664724
     
    740800        return;
    741801
    742802    for (let type of this.GetAttackTypes())
    743         if (msg.valueNames.indexOf("Attack/"+type+"/MaxRange") !== -1)
     803        if (msg.valueNames.indexOf("Attack/" + type + "/MaxRange") !== -1)
    744804        {
    745805            cmpUnitAI.UpdateRangeQueries();
    746806            return;
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    17281728    return false;
    17291729};
    17301730
     1731GuiInterface.prototype.CanSecondAttack = function(player, data)
     1732{
     1733    let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
     1734    if (!cmpAttack)
     1735        return false;
     1736
     1737    return cmpAttack.CanSecondAttack(data.target)
     1738};
     1739
    17311740/*
    17321741 * Returns batch build time.
    17331742 */
     
    18711880    "GetTradingDetails": 1,
    18721881    "CanCapture": 1,
    18731882    "CanAttack": 1,
     1883    "CanSecondAttack": 1,
    18741884    "GetBatchTime": 1,
    18751885
    18761886    "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
     
    838838        },
    839839
    840840        "Order.Attack": function(msg) {
    841             var target = msg.data.target;
    842             var allowCapture = msg.data.allowCapture;
    843             var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
     841            let target = msg.data.target;
     842            let allowCapture = msg.data.allowCapture;
     843            let prefType = msg.data.prefType;
     844            let cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI);
    844845            if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember())
    845846                target = cmpTargetUnitAI.GetFormationController();
    846847
     
    859860                this.FinishOrder();
    860861                return;
    861862            }
    862             this.CallMemberFunction("Attack", [target, false, allowCapture]);
     863            this.CallMemberFunction("Attack", [target, false, allowCapture, prefType]);
    863864            if (cmpAttack.CanAttackAsFormation())
    864865                this.SetNextState("COMBAT.ATTACKING");
    865866            else
     
    914915                    return;
    915916                }
    916917
    917                 this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false });
     918                this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false, "prefType": undefined });
    918919                return;
    919920            }
    920921
     
    11511152
    11521153                "MoveCompleted": function(msg) {
    11531154                    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    1154                     this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture]);
     1155                    this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture, this.order.data.prefType]);
    11551156                    if (cmpAttack.CanAttackAsFormation())
    11561157                        this.SetNextState("COMBAT.ATTACKING");
    11571158                    else
     
    11621163            "ATTACKING": {
    11631164                // Wait for individual members to finish
    11641165                "enter": function(msg) {
    1165                     var target = this.order.data.target;
    1166                     var allowCapture = this.order.data.allowCapture;
     1166                    let target = this.order.data.target;
     1167                    let allowCapture = this.order.data.allowCapture;
     1168                    let prefType = this.order.data.prefType;
    11671169                    // Check if we are already in range, otherwise walk there
    11681170                    if (!this.CheckTargetAttackRange(target, target))
    11691171                    {
     
    11701172                        if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
    11711173                        {
    11721174                            this.FinishOrder();
    1173                             this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
     1175                            this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType });
    11741176                            return true;
    11751177                        }
    11761178                        this.FinishOrder();
     
    11861188                },
    11871189
    11881190                "Timer": function(msg) {
    1189                     var target = this.order.data.target;
    1190                     var allowCapture = this.order.data.allowCapture;
     1191                    let target = this.order.data.target;
     1192                    let allowCapture = this.order.data.allowCapture;
     1193                    let prefType = this.order.data.prefType;
    11911194                    // Check if we are already in range, otherwise walk there
    11921195                    if (!this.CheckTargetAttackRange(target, target))
    11931196                    {
     
    11941197                        if (this.TargetIsAlive(target) && this.CheckTargetVisible(target))
    11951198                        {
    11961199                            this.FinishOrder();
    1197                             this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture });
     1200                            this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture, "prefType": prefType });
    11981201                            return;
    11991202                        }
    12001203                        this.FinishOrder();
     
    14061409
    14071410            // target the unit
    14081411            if (this.CheckTargetVisible(msg.data.attacker))
    1409                 this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true });
     1412                this.PushOrderFront("Attack", { "target": msg.data.attacker, "force": false, "allowCapture": true, "prefType": undefined });
    14101413            else
    14111414            {
    14121415                var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position);
     
    45204523    return distance < range;
    45214524};
    45224525
    4523 UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
     4526UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture, prefType)
    45244527{
    45254528    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    45264529    if (!cmpAttack)
    45274530        return undefined;
    4528     return cmpAttack.GetBestAttackAgainst(target, allowCapture);
     4531    return cmpAttack.GetBestAttackAgainst(target, allowCapture, prefType);
    45294532};
    45304533
    45314534UnitAI.prototype.GetAttackBonus = function(type, target)
     
    45474550    if (!target)
    45484551        return false;
    45494552
    4550     this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
     4553    this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined });
    45514554    return true;
    45524555};
    45534556
     
    45604563{
    45614564    var target = ents.find(target =>
    45624565        this.CanAttack(target, forceResponse)
    4563         && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true))
     4566        && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true, undefined))
    45644567        && (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target))
    45654568    );
    45664569    if (!target)
    45674570        return false;
    45684571
    4569     this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true });
     4572    this.PushOrderFront("Attack", { "target": target, "force": false, "forceResponse": forceResponse, "allowCapture": true, "prefType": undefined });
    45704573    return true;
    45714574};
    45724575
     
    49864989/**
    49874990 * Adds attack order to the queue, forced by the player.
    49884991 */
    4989 UnitAI.prototype.Attack = function(target, queued, allowCapture)
     4992UnitAI.prototype.Attack = function(target, queued, allowCapture, prefType)
    49904993{
    49914994    if (!this.CanAttack(target))
    49924995    {
     
    49985001            this.WalkToTarget(target, queued);
    49995002        return;
    50005003    }
    5001     this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued);
     5004    this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture, "prefType": prefType }, queued);
    50025005};
    50035006
    50045007/**
     
    54145417                    if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
    54155418                        continue;
    54165419                }
    5417                 this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
     5420                this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined });
    54185421                return true;
    54195422            }
    54205423        }
     
    54405443            if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
    54415444                continue;
    54425445        }
    5443         this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true });
     5446        this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true, "prefType": undefined });
    54445447        return true;
    54455448    }
    54465449    return false;
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    156156        });
    157157    },
    158158
     159    "second-attack-walk": function(player, cmd, data)
     160    {
     161        GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
     162            cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
     163        });
     164    },
     165
    159166    "attack": function(player, cmd, data)
    160167    {
    161168        if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
    162169            warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
    163170
     171        let prefType = "primary"
    164172        let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
     173        if (allowCapture)
     174            prefType = "Capture";
    165175        // See UnitAI.CanAttack for target checks
    166176        GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
    167             cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture);
     177            cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType);
    168178        });
    169179    },
    170180
     181    "second-attack": function(player, cmd, data)
     182    {
     183        if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
     184        {
     185            // This check is for debugging only!
     186            warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
     187        }
     188
     189        let prefType = "secondary"
     190        let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
     191        // See UnitAI.CanAttack for target checks
     192        GetFormationUnitAIs(data.entities, player).forEach(function(cmpUnitAI) {
     193            cmpUnitAI.Attack(cmd.target, cmd.queued, allowCapture, prefType);
     194        });
     195    },
     196
    171197    "heal": function(player, cmd, data)
    172198    {
    173199        if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target)))
  • binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_spearman.xml

     
    55    <Pierce>10</Pierce>
    66  </Armour>
    77  <Attack>
     8    <ChangeDistance>20</ChangeDistance>
    89    <Melee>
     10      <AttackOrder>primary</AttackOrder>
    911      <Hack>6.0</Hack>
    1012      <Pierce>5.0</Pierce>
    1113      <Crush>0.0</Crush>
     
    1820          </BonusCavMelee>
    1921      </Bonuses>
    2022    </Melee>
     23    <Ranged>
     24      <AttackOrder>secondary</AttackOrder>
     25      <Hack>0</Hack>
     26      <Pierce>6.0</Pierce>
     27      <Crush>0</Crush>
     28      <MaxRange>72.0</MaxRange>
     29      <MinRange>0.0</MinRange>
     30      <ProjectileSpeed>120.0</ProjectileSpeed>
     31      <PrepareTime>1000</PrepareTime>
     32      <RepeatTime>1000</RepeatTime>
     33      <Spread>2.0</Spread>
     34    </Ranged>
    2135    <Charge>
    2236      <Hack>15.0</Hack>
    2337      <Pierce>40.0</Pierce>